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, 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(SignerError::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 = if let Ok(secret) =
36            Self::get_secret_key(signer_id, public_key.clone(), "mainnet").await
37        {
38            secret
39        } else {
40            Self::get_secret_key(signer_id, public_key.clone(), "testnet")
41                .await
42                .map_err(|_| SignerError::SecretKeyIsNotAvailable)?
43        };
44
45        info!(target: KEYSTORE_SIGNER_TARGET, "Secret key prepared successfully");
46        Ok(secret.private_key)
47    }
48
49    #[instrument(skip(self))]
50    fn get_public_key(&self) -> Result<PublicKey, SignerError> {
51        debug!(target: KEYSTORE_SIGNER_TARGET, "Retrieving first public key");
52        self.potential_pubkeys
53            .first()
54            .cloned()
55            .ok_or(SignerError::PublicKeyIsNotAvailable)
56    }
57}
58
59impl KeystoreSigner {
60    pub fn new_with_pubkey(pub_key: PublicKey) -> Self {
61        debug!(target: KEYSTORE_SIGNER_TARGET, "Creating new KeystoreSigner with public key");
62        Self {
63            potential_pubkeys: vec![pub_key],
64        }
65    }
66
67    #[instrument(skip(network), fields(account_id = %account_id, network_name = %network.network_name))]
68    pub async fn search_for_keys(
69        account_id: AccountId,
70        network: &NetworkConfig,
71    ) -> Result<Self, KeyStoreError> {
72        info!(target: KEYSTORE_SIGNER_TARGET, "Searching for keys for account");
73        let account_keys = crate::account::Account(account_id.clone())
74            .list_keys()
75            .fetch_from(network)
76            .await
77            .map_err(KeyStoreError::QueryError)?;
78
79        debug!(target: KEYSTORE_SIGNER_TARGET, "Filtering and collecting potential public keys");
80        let potential_pubkeys = account_keys
81            .data
82            .iter()
83            // TODO: support functional access keys
84            .filter(|(_, access_key)| {
85                matches!(access_key.permission, AccessKeyPermission::FullAccess)
86            })
87            .map(|(public_key, _)| public_key.clone())
88            .map(|key| Self::get_secret_key(&account_id, key, &network.network_name));
89        let potential_pubkeys: Vec<PublicKey> = join_all(potential_pubkeys)
90            .await
91            .into_iter()
92            .flat_map(|result| result.map(|keypair| keypair.public_key).ok())
93            .collect();
94
95        info!(target: KEYSTORE_SIGNER_TARGET, "KeystoreSigner created with {} potential public keys", potential_pubkeys.len());
96        Ok(Self { potential_pubkeys })
97    }
98
99    #[instrument(skip(public_key), fields(account_id = %account_id, network_name = %network_name))]
100    async fn get_secret_key(
101        account_id: &AccountId,
102        public_key: PublicKey,
103        network_name: &str,
104    ) -> Result<AccountKeyPair, KeyStoreError> {
105        trace!(target: KEYSTORE_SIGNER_TARGET, "Retrieving secret key from keyring");
106        let service_name =
107            std::borrow::Cow::Owned(format!("near-{}-{}", network_name, account_id.as_str()));
108        let user = format!("{account_id}:{public_key}");
109
110        // This can be a blocking operation (for example, if the keyring is locked in the OS and user needs to unlock it),
111        // so we need to spawn a new task to get the password
112        let password = tokio::task::spawn_blocking(move || {
113            let password = keyring::Entry::new(&service_name, &user)?.get_password()?;
114
115            Ok::<_, KeyStoreError>(password)
116        })
117        .await
118        .unwrap_or_else(|tokio_join_error| Err(KeyStoreError::from(tokio_join_error)))?;
119
120        debug!(target: KEYSTORE_SIGNER_TARGET, "Deserializing account key pair");
121        Ok(serde_json::from_str(&password)?)
122    }
123}