use futures::future::join_all;
use near_api_types::{AccessKeyPermission, AccountId, PublicKey, SecretKey};
use tracing::{debug, info, instrument, trace, warn};
use crate::{
config::NetworkConfig,
errors::{KeyStoreError, PublicKeyError, SignerError},
};
use super::{AccountKeyPair, SignerTrait};
const KEYSTORE_SIGNER_TARGET: &str = "near_api::signer::keystore";
#[derive(Debug, Clone)]
pub struct KeystoreSigner {
potential_pubkeys: Vec<PublicKey>,
}
#[async_trait::async_trait]
impl SignerTrait for KeystoreSigner {
#[instrument(skip(self))]
async fn get_secret_key(
&self,
signer_id: &AccountId,
public_key: PublicKey,
) -> Result<SecretKey, SignerError> {
debug!(target: KEYSTORE_SIGNER_TARGET, "Searching for matching public key");
self.potential_pubkeys
.iter()
.find(|key| **key == public_key)
.ok_or(PublicKeyError::PublicKeyIsNotAvailable)?;
info!(target: KEYSTORE_SIGNER_TARGET, "Retrieving secret key");
let secret =
if let Ok(secret) = Self::get_secret_key(signer_id, public_key, "mainnet").await {
secret
} else {
Self::get_secret_key(signer_id, public_key, "testnet")
.await
.map_err(|_| SignerError::SecretKeyIsNotAvailable)?
};
info!(target: KEYSTORE_SIGNER_TARGET, "Secret key prepared successfully");
Ok(secret.private_key)
}
#[instrument(skip(self))]
fn get_public_key(&self) -> Result<PublicKey, PublicKeyError> {
debug!(target: KEYSTORE_SIGNER_TARGET, "Retrieving first public key");
self.potential_pubkeys
.first()
.cloned()
.ok_or(PublicKeyError::PublicKeyIsNotAvailable)
}
}
impl KeystoreSigner {
pub fn new_with_pubkey(pub_key: PublicKey) -> Self {
debug!(target: KEYSTORE_SIGNER_TARGET, "Creating new KeystoreSigner with public key");
Self {
potential_pubkeys: vec![pub_key],
}
}
#[instrument(skip(network), fields(account_id = %account_id, network_name = %network.network_name))]
pub async fn search_for_keys(
account_id: AccountId,
network: &NetworkConfig,
) -> Result<Self, KeyStoreError> {
info!(target: KEYSTORE_SIGNER_TARGET, "Searching for keys for account");
let account_keys = crate::account::Account(account_id.clone())
.list_keys()
.fetch_from(network)
.await
.map_err(KeyStoreError::QueryError)?;
debug!(target: KEYSTORE_SIGNER_TARGET, "Filtering and collecting potential public keys");
let potential_pubkeys = account_keys
.data
.iter()
.filter(|(_, access_key)| {
matches!(access_key.permission, AccessKeyPermission::FullAccess)
})
.map(|(public_key, _)| *public_key)
.map(|key| Self::get_secret_key(&account_id, key, &network.network_name));
let potential_pubkeys: Vec<PublicKey> = join_all(potential_pubkeys)
.await
.into_iter()
.flat_map(|result| result.map(|keypair| keypair.public_key).ok())
.collect();
info!(target: KEYSTORE_SIGNER_TARGET, "KeystoreSigner created with {} potential public keys", potential_pubkeys.len());
Ok(Self { potential_pubkeys })
}
#[instrument(skip(public_key), fields(account_id = %account_id, network_name = %network_name))]
async fn get_secret_key(
account_id: &AccountId,
public_key: PublicKey,
network_name: &str,
) -> Result<AccountKeyPair, KeyStoreError> {
trace!(target: KEYSTORE_SIGNER_TARGET, "Retrieving secret key from keyring");
let service_name =
std::borrow::Cow::Owned(format!("near-{}-{}", network_name, account_id.as_str()));
let user = format!("{account_id}:{public_key}");
let password = tokio::task::spawn_blocking(move || {
let password = keyring::Entry::new(&service_name, &user)?.get_password()?;
Ok::<_, KeyStoreError>(password)
})
.await
.unwrap_or_else(|tokio_join_error| Err(KeyStoreError::from(tokio_join_error)))?;
debug!(target: KEYSTORE_SIGNER_TARGET, "Deserializing account key pair");
Ok(serde_json::from_str(&password)?)
}
}