near_api/signer/
keystore.rs

1use futures::future::join_all;
2use near_api_types::{AccessKeyPermission, AccountId, PublicKey, SecretKey};
3use tracing::{debug, info, instrument, trace, warn};
4
5use crate::{
6    config::NetworkConfig,
7    errors::{KeyStoreError, PublicKeyError, SignerError},
8};
9
10use super::{AccountKeyPair, SignerTrait};
11
12const KEYSTORE_SIGNER_TARGET: &str = "near_api::signer::keystore";
13
14#[derive(Debug, Clone)]
15pub struct KeystoreSigner {
16    potential_pubkeys: Vec<PublicKey>,
17}
18
19#[async_trait::async_trait]
20impl SignerTrait for KeystoreSigner {
21    #[instrument(skip(self))]
22    async fn get_secret_key(
23        &self,
24        signer_id: &AccountId,
25        public_key: PublicKey,
26    ) -> Result<SecretKey, SignerError> {
27        debug!(target: KEYSTORE_SIGNER_TARGET, "Searching for matching public key");
28        self.potential_pubkeys
29            .iter()
30            .find(|key| **key == public_key)
31            .ok_or(PublicKeyError::PublicKeyIsNotAvailable)?;
32
33        info!(target: KEYSTORE_SIGNER_TARGET, "Retrieving secret key");
34        // TODO: fix this. Well the search is a bit suboptimal, but it's not a big deal for now
35        let secret =
36            if let Ok(secret) = Self::get_secret_key(signer_id, public_key, "mainnet").await {
37                secret
38            } else {
39                Self::get_secret_key(signer_id, public_key, "testnet")
40                    .await
41                    .map_err(|_| SignerError::SecretKeyIsNotAvailable)?
42            };
43
44        info!(target: KEYSTORE_SIGNER_TARGET, "Secret key prepared successfully");
45        Ok(secret.private_key)
46    }
47
48    #[instrument(skip(self))]
49    fn get_public_key(&self) -> Result<PublicKey, PublicKeyError> {
50        debug!(target: KEYSTORE_SIGNER_TARGET, "Retrieving first public key");
51        self.potential_pubkeys
52            .first()
53            .cloned()
54            .ok_or(PublicKeyError::PublicKeyIsNotAvailable)
55    }
56}
57
58impl KeystoreSigner {
59    pub fn new_with_pubkey(pub_key: PublicKey) -> Self {
60        debug!(target: KEYSTORE_SIGNER_TARGET, "Creating new KeystoreSigner with public key");
61        Self {
62            potential_pubkeys: vec![pub_key],
63        }
64    }
65
66    #[instrument(skip(network), fields(account_id = %account_id, network_name = %network.network_name))]
67    pub async fn search_for_keys(
68        account_id: AccountId,
69        network: &NetworkConfig,
70    ) -> Result<Self, KeyStoreError> {
71        info!(target: KEYSTORE_SIGNER_TARGET, "Searching for keys for account");
72        let account_keys = crate::account::Account(account_id.clone())
73            .list_keys()
74            .fetch_from(network)
75            .await
76            .map_err(KeyStoreError::QueryError)?;
77
78        debug!(target: KEYSTORE_SIGNER_TARGET, "Filtering and collecting potential public keys");
79        let potential_pubkeys = account_keys
80            .data
81            .iter()
82            // TODO: support functional access keys
83            .filter(|(_, access_key)| {
84                matches!(access_key.permission, AccessKeyPermission::FullAccess)
85            })
86            .map(|(public_key, _)| *public_key)
87            .map(|key| Self::get_secret_key(&account_id, key, &network.network_name));
88        let potential_pubkeys: Vec<PublicKey> = join_all(potential_pubkeys)
89            .await
90            .into_iter()
91            .flat_map(|result| result.map(|keypair| keypair.public_key).ok())
92            .collect();
93
94        info!(target: KEYSTORE_SIGNER_TARGET, "KeystoreSigner created with {} potential public keys", potential_pubkeys.len());
95        Ok(Self { potential_pubkeys })
96    }
97
98    #[instrument(skip(public_key), fields(account_id = %account_id, network_name = %network_name))]
99    async fn get_secret_key(
100        account_id: &AccountId,
101        public_key: PublicKey,
102        network_name: &str,
103    ) -> Result<AccountKeyPair, KeyStoreError> {
104        trace!(target: KEYSTORE_SIGNER_TARGET, "Retrieving secret key from keyring");
105        let service_name =
106            std::borrow::Cow::Owned(format!("near-{}-{}", network_name, account_id.as_str()));
107        let user = format!("{account_id}:{public_key}");
108
109        // This can be a blocking operation (for example, if the keyring is locked in the OS and user needs to unlock it),
110        // so we need to spawn a new task to get the password
111        let password = tokio::task::spawn_blocking(move || {
112            let password = keyring::Entry::new(&service_name, &user)?.get_password()?;
113
114            Ok::<_, KeyStoreError>(password)
115        })
116        .await
117        .unwrap_or_else(|tokio_join_error| Err(KeyStoreError::from(tokio_join_error)))?;
118
119        debug!(target: KEYSTORE_SIGNER_TARGET, "Deserializing account key pair");
120        Ok(serde_json::from_str(&password)?)
121    }
122}