near_api/signer/
mod.rs

1//! Transaction signing functionality for NEAR Protocol
2//!
3//! The [`Signer`] provides various ways to sign transactions on NEAR, including:
4//! - Secret key signing
5//! - Seed phrase (mnemonic) signing
6//! - Access key file signing
7//! - Hardware wallet (`Ledger`) signing
8//! - System keychain signing
9//!
10//! # Examples
11//!
12//! ## Creating a signer using a secret key
13//! ```rust,no_run
14//! use near_api::{*, types::SecretKey};
15//!
16//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
17//! let secret_key: SecretKey = "ed25519:2vVTQWpoZvYZBS4HYFZtzU2rxpoQSrhyFWdaHLqSdyaEfgjefbSKiFpuVatuRqax3HFvVq2tkkqWH2h7tso2nK8q".parse()?;
18//! let signer = Signer::from_secret_key(secret_key)?;
19//! # Ok(())
20//! # }
21//! ```
22//!
23//! ## Creating a signer using a seed phrase
24//! ```rust,no_run
25//! use near_api::*;
26//!
27//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
28//! let seed_phrase = "witch collapse practice feed shame open despair creek road again ice least";
29//! let signer = Signer::from_seed_phrase(seed_phrase, None)?;
30//! # Ok(())
31//! # }
32//! ```
33//!
34//! ## Creating a `Ledger` signer
35//! ```rust,no_run
36//! # #[cfg(feature = "ledger")]
37//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
38//! use near_api::*;
39//!
40//! let signer = Signer::from_ledger()?;
41//! # Ok(())
42//! # }
43//! ```
44//!
45//! ## Creating a `keystore` signer
46//! ```rust,no_run
47//! # #[cfg(feature = "keystore")]
48//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
49//! use near_api::*;
50//!
51//! let signer = Signer::from_keystore_with_search_for_keys("account_id.testnet".parse()?, &NetworkConfig::testnet()).await?;
52//! # Ok(())
53//! # }
54//! ```
55//!
56//! ## Example signing with [Signer](`Signer`)
57//!
58//! ```rust,no_run
59//! # use near_api::*;
60//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
61//! # let signer = Signer::from_secret_key("ed25519:2vVTQWpoZvYZBS4HYFZtzU2rxpoQSrhyFWdaHLqSdyaEfgjefbSKiFpuVatuRqax3HFvVq2tkkqWH2h7tso2nK8q".parse()?)?;
62//! let transaction_result = Tokens::account("alice.testnet".parse()?)
63//!     .send_to("bob.testnet".parse()?)
64//!     .near(NearToken::from_near(1))
65//!     .with_signer(signer)
66//!     .send_to_testnet()
67//!     .await?;
68//!
69//! # Ok(())
70//! # }
71//! ```
72//!
73//! # Advanced: [Access Key Pooling](https://github.com/akorchyn/near-api/issues/2)
74//!
75//! The signer supports pooling multiple access keys for improved transaction throughput.
76//! It helps to mitigate concurrency issues that arise when multiple transactions are signed but the
77//! transaction with the highest nonce arrives first which would fail transaction with a lower nonce.
78//!
79//! By using, account key pooling, each transaction is signed with a different key, so that the nonce issue
80//! is mitigated as long as the keys are more or equal to the number of signed transactions.
81//! ```rust,no_run
82//! use near_api::{*, types::SecretKey};
83//!
84//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
85//! let signer = Signer::from_secret_key("ed25519:2vVTQWpoZvYZBS4HYFZtzU2rxpoQSrhyFWdaHLqSdyaEfgjefbSKiFpuVatuRqax3HFvVq2tkkqWH2h7tso2nK8q".parse()?)?;
86//!
87//! // Add additional keys to the pool using convenient methods
88//! signer.add_seed_phrase_to_pool("witch collapse practice feed shame open despair creek road again ice least", None).await?;
89//! signer.add_seed_phrase_to_pool("return cactus real attack meat pitch trash found autumn upgrade mystery pupil", None).await?;
90//! # Ok(())
91//! # }
92//! ```
93//!
94//! # Nonce Management
95//!
96//! The signer automatically manages nonces for transactions:
97//! - Caches nonces per (account_id, public_key) pair
98//! - Automatically increments nonces for sequential transactions
99//! - Supports concurrent transactions as long as the `Arc<Signer>` is same
100//!
101//! # Secret generation
102//! The crate provides utility functions to generate new secret keys and seed phrases
103//!
104//! See [functions](#functions) section for details
105//!
106//! # Custom signer
107//! The user can instantiate [`Signer`] with a custom signing logic by utilizing the [`SignerTrait`] trait.
108
109use std::{
110    collections::HashMap,
111    path::{Path, PathBuf},
112    sync::{
113        atomic::{AtomicUsize, Ordering},
114        Arc,
115    },
116};
117
118use near_api_types::{
119    transaction::{
120        delegate_action::{NonDelegateAction, SignedDelegateAction},
121        PrepopulateTransaction, SignedTransaction, Transaction, TransactionV0,
122    },
123    AccountId, BlockHeight, CryptoHash, Nonce, PublicKey, SecretKey, Signature,
124};
125
126use borsh::{BorshDeserialize, BorshSerialize};
127use serde::{Deserialize, Serialize};
128use slipped10::BIP32Path;
129use tracing::{debug, instrument, warn};
130
131use crate::{
132    config::NetworkConfig,
133    errors::{AccessKeyFileError, MetaSignError, PublicKeyError, SecretError, SignerError},
134};
135
136use secret_key::SecretKeySigner;
137
138#[cfg(feature = "keystore")]
139pub mod keystore;
140#[cfg(feature = "ledger")]
141pub mod ledger;
142pub mod secret_key;
143
144const SIGNER_TARGET: &str = "near_api::signer";
145/// Default HD path for seed phrases and secret keys generation
146pub const DEFAULT_HD_PATH: &str = "m/44'/397'/0'";
147/// Default HD path for ledger signing
148pub const DEFAULT_LEDGER_HD_PATH: &str = "44'/397'/0'/0'/1'";
149/// Default word count for seed phrases generation
150pub const DEFAULT_WORD_COUNT: usize = 12;
151
152/// A struct representing a pair of public and private keys for an account.
153/// This might be useful for getting keys from a file. E.g. `~/.near-credentials`.
154#[derive(Debug, Serialize, Deserialize, Clone)]
155pub struct AccountKeyPair {
156    pub public_key: PublicKey,
157    pub private_key: SecretKey,
158}
159
160impl AccountKeyPair {
161    fn load_access_key_file(path: &Path) -> Result<Self, AccessKeyFileError> {
162        let data = std::fs::read_to_string(path)?;
163        Ok(serde_json::from_str(&data)?)
164    }
165}
166
167/// [NEP413](https://github.com/near/NEPs/blob/master/neps/nep-0413.md) input for the signing message.
168#[derive(Debug, Clone, Serialize, Deserialize, BorshDeserialize, BorshSerialize)]
169pub struct NEP413Payload {
170    /// The message that wants to be transmitted.
171    pub message: String,
172    /// A nonce that uniquely identifies this instance of the message, denoted as a 32 bytes array.
173    pub nonce: [u8; 32],
174    /// The recipient to whom the message is destined (e.g. "alice.near" or "myapp.com").
175    pub recipient: String,
176    /// A callback URL that will be called with the signed message as a query parameter.
177    pub callback_url: Option<String>,
178}
179
180impl NEP413Payload {
181    const MESSAGE_PREFIX: u32 = (1u32 << 31) + 413;
182
183    /// Compute the NEP-413 hash for this payload.
184    pub fn compute_hash(&self) -> Result<CryptoHash, std::io::Error> {
185        let mut bytes = Self::MESSAGE_PREFIX.to_le_bytes().to_vec();
186        borsh::to_writer(&mut bytes, self)?;
187        Ok(CryptoHash::hash(&bytes))
188    }
189
190    /// Extract timestamp from nonce (first 8 bytes as big-endian u64 milliseconds).
191    pub fn extract_timestamp_from_nonce(&self) -> u64 {
192        let mut timestamp: [u8; 8] = [0; 8];
193        timestamp.copy_from_slice(&self.nonce[..8]);
194        u64::from_be_bytes(timestamp)
195    }
196
197    /// Verify signature and that the public key belongs to the account as a full access key.
198    ///
199    /// According to NEP-413, the signature must be made with a full access key,
200    /// not a function call access key.
201    pub async fn verify(
202        &self,
203        account_id: &AccountId,
204        public_key: PublicKey,
205        signature: &Signature,
206        network: &NetworkConfig,
207    ) -> Result<bool, SignerError> {
208        use near_api_types::AccessKeyPermission;
209
210        let hash = self.compute_hash()?;
211        if !signature.verify(hash, public_key) {
212            return Ok(false);
213        }
214
215        let access_key = crate::Account(account_id.clone())
216            .access_key(public_key)
217            .fetch_from(network)
218            .await;
219
220        match access_key {
221            Ok(data) => Ok(data.data.permission == AccessKeyPermission::FullAccess),
222            Err(_) => Ok(false),
223        }
224    }
225}
226
227#[cfg(feature = "ledger")]
228impl From<NEP413Payload> for near_ledger::NEP413Payload {
229    fn from(payload: NEP413Payload) -> Self {
230        Self {
231            message: payload.message,
232            nonce: payload.nonce,
233            recipient: payload.recipient,
234            callback_url: payload.callback_url,
235        }
236    }
237}
238
239/// A trait for implementing custom signing logic.
240///
241/// This trait provides the core functionality needed to sign transactions and delegate actions.
242/// It is used by the [`Signer`] to abstract over different signing methods (secret key, `ledger`, `keystore`, etc.).
243///
244/// # Examples
245///
246/// ## Implementing a custom signer
247/// ```rust,no_run
248/// use near_api::{*, signer::*, types::transaction::{PrepopulateTransaction, Transaction}, errors::{PublicKeyError, SignerError}};
249///
250/// struct CustomSigner {
251///     secret_key: SecretKey,
252/// }
253///
254/// #[async_trait::async_trait]
255/// impl SignerTrait for CustomSigner {
256///     async fn get_secret_key(
257///         &self,
258///         _signer_id: &AccountId,
259///         _public_key: PublicKey
260///     ) -> Result<SecretKey, SignerError> {
261///         Ok(self.secret_key.clone())
262///     }
263///
264///     fn get_public_key(&self) -> Result<PublicKey, PublicKeyError> {
265///         Ok(self.secret_key.public_key().into())
266///     }
267/// }
268/// ```
269///
270/// ## Using a custom signer
271/// ```rust,no_run
272/// # use near_api::{AccountId, signer::*, types::{transaction::{Transaction, PrepopulateTransaction}, PublicKey, SecretKey}, errors::{PublicKeyError, SignerError}};
273/// # struct CustomSigner;
274/// # impl CustomSigner {
275/// #     fn new(_: SecretKey) -> Self { Self }
276/// # }
277/// # #[async_trait::async_trait]
278/// # impl SignerTrait for CustomSigner {
279/// #     async fn get_secret_key(&self, _: &AccountId, _: PublicKey) -> Result<SecretKey, near_api::errors::SignerError> { unimplemented!() }
280/// #     fn get_public_key(&self) -> Result<PublicKey, PublicKeyError> { unimplemented!() }
281/// # }
282/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
283/// let secret_key = "ed25519:2vVTQWpoZvYZBS4HYFZtzU2rxpoQSrhyFWdaHLqSdyaEfgjefbSKiFpuVatuRqax3HFvVq2tkkqWH2h7tso2nK8q".parse()?;
284/// let custom_signer = CustomSigner::new(secret_key);
285/// let signer = Signer::new(custom_signer)?;
286/// # Ok(())
287/// # }
288/// ```
289///
290/// ## Example of implementing `sign_meta` and `sign` methods
291/// The default implementation of `sign_meta` and `sign` methods should work for most cases.
292/// If you need to implement custom logic, you can override these methods.
293/// See [`near_ledger`](`ledger::LedgerSigner`) implementation for an example.
294#[async_trait::async_trait]
295pub trait SignerTrait {
296    /// Signs a delegate action for meta transactions.
297    ///
298    /// This method is used for meta-transactions where one account can delegate transaction delivery and gas payment to another account.
299    /// The delegate action is signed with a maximum block height to ensure the delegation expiration after some point in time.
300    ///
301    /// The default implementation should work for most cases.
302    #[instrument(skip(self, transaction), fields(signer_id = %transaction.signer_id, receiver_id = %transaction.receiver_id))]
303    async fn sign_meta(
304        &self,
305        transaction: PrepopulateTransaction,
306        public_key: PublicKey,
307        nonce: Nonce,
308        block_hash: CryptoHash,
309        max_block_height: BlockHeight,
310    ) -> Result<SignedDelegateAction, MetaSignError> {
311        let signer_secret_key = self
312            .get_secret_key(&transaction.signer_id, public_key)
313            .await?;
314        let unsigned_transaction = Transaction::V0(TransactionV0 {
315            signer_id: transaction.signer_id.clone(),
316            public_key,
317            nonce,
318            receiver_id: transaction.receiver_id,
319            block_hash,
320            actions: transaction.actions,
321        });
322
323        get_signed_delegate_action(unsigned_transaction, signer_secret_key, max_block_height)
324    }
325
326    /// Signs a regular transaction.
327    ///
328    /// This method is used for standard transactions. It creates a signed transaction
329    /// that can be sent to the `NEAR` network.
330    ///
331    /// The default implementation should work for most cases.
332    #[instrument(skip(self, transaction), fields(signer_id = %transaction.signer_id, receiver_id = %transaction.receiver_id))]
333    async fn sign(
334        &self,
335        transaction: PrepopulateTransaction,
336        public_key: PublicKey,
337        nonce: Nonce,
338        block_hash: CryptoHash,
339    ) -> Result<SignedTransaction, SignerError> {
340        let signer_secret_key = self
341            .get_secret_key(&transaction.signer_id, public_key)
342            .await?;
343        let unsigned_transaction = Transaction::V0(TransactionV0 {
344            signer_id: transaction.signer_id.clone(),
345            public_key,
346            nonce,
347            receiver_id: transaction.receiver_id,
348            block_hash,
349            actions: transaction.actions,
350        });
351
352        let signature = signer_secret_key.sign(unsigned_transaction.get_hash());
353
354        Ok(SignedTransaction::new(signature, unsigned_transaction))
355    }
356
357    /// Signs a [NEP413](https://github.com/near/NEPs/blob/master/neps/nep-0413.md) message that is widely used for the [authentication](https://docs.near.org/build/web3-apps/backend/)
358    /// and off-chain proof of account ownership.
359    ///
360    /// The default implementation should work for most cases.
361    #[instrument(skip(self), fields(signer_id = %signer_id, receiver_id = %payload.recipient, message = %payload.message))]
362    async fn sign_message_nep413(
363        &self,
364        signer_id: AccountId,
365        public_key: PublicKey,
366        payload: &NEP413Payload,
367    ) -> Result<Signature, SignerError> {
368        let hash = payload.compute_hash()?;
369        let secret = self.get_secret_key(&signer_id, public_key).await?;
370        Ok(secret.sign(hash))
371    }
372
373    /// Returns the secret key associated with this signer.
374    /// This is a `helper` method that should be implemented by the signer or fail with [`SignerError`].
375    /// As long as this method works, the default implementation of the [sign_meta](`SignerTrait::sign_meta`) and [sign](`SignerTrait::sign`) methods should work.
376    ///
377    /// If you can't provide a [`SecretKey`] for some reason (E.g. `Ledger``),
378    /// you can fail with SignerError and override `sign_meta` and `sign`, `sign_message_nep413` methods.
379    async fn get_secret_key(
380        &self,
381        signer_id: &AccountId,
382        public_key: PublicKey,
383    ) -> Result<SecretKey, SignerError>;
384
385    /// Returns the public key associated with this signer.
386    ///
387    /// This method is used by the [`Signer`] to manage the pool of signing keys.
388    fn get_public_key(&self) -> Result<PublicKey, PublicKeyError>;
389}
390
391/// A [Signer](`Signer`) is a wrapper around a single or multiple signer implementations
392/// of [SignerTrait](`SignerTrait`).
393///
394/// It provides an access key pooling and a nonce caching mechanism to improve transaction throughput.
395pub struct Signer {
396    pool: tokio::sync::RwLock<HashMap<PublicKey, Box<dyn SignerTrait + Send + Sync + 'static>>>,
397    nonce_cache: futures::lock::Mutex<HashMap<(AccountId, PublicKey), u64>>,
398    current_public_key: AtomicUsize,
399}
400
401impl Signer {
402    /// Creates a new signer and instantiates nonce cache.
403    #[instrument(skip(signer))]
404    pub fn new<T: SignerTrait + Send + Sync + 'static>(
405        signer: T,
406    ) -> Result<Arc<Self>, PublicKeyError> {
407        let public_key = signer.get_public_key()?;
408        Ok(Arc::new(Self {
409            pool: tokio::sync::RwLock::new(HashMap::from([(
410                public_key,
411                Box::new(signer) as Box<dyn SignerTrait + Send + Sync + 'static>,
412            )])),
413            nonce_cache: futures::lock::Mutex::new(HashMap::new()),
414            current_public_key: AtomicUsize::new(0),
415        }))
416    }
417
418    /// Adds a signer to the pool of signers.
419    /// The [Signer](`Signer`) will rotate the provided implementation of [SignerTrait](`SignerTrait`) on each call to [get_public_key](`Signer::get_public_key`).
420    #[instrument(skip(self, signer))]
421    pub async fn add_signer_to_pool<T: SignerTrait + Send + Sync + 'static>(
422        &self,
423        signer: T,
424    ) -> Result<(), PublicKeyError> {
425        let public_key = signer.get_public_key()?;
426        debug!(target: SIGNER_TARGET, "Adding signer to pool");
427        self.pool.write().await.insert(public_key, Box::new(signer));
428        Ok(())
429    }
430
431    /// Adds a secret key to the signing pool.
432    ///
433    /// This is a convenience method for adding additional keys to the pool to enable
434    /// concurrent transaction signing and nonce management across multiple keys.
435    #[instrument(skip(self, secret_key))]
436    pub async fn add_secret_key_to_pool(
437        &self,
438        secret_key: SecretKey,
439    ) -> Result<(), PublicKeyError> {
440        let signer = SecretKeySigner::new(secret_key);
441        self.add_signer_to_pool(signer).await
442    }
443
444    /// Adds a seed phrase-derived key to the signing pool with default HD path.
445    ///
446    /// This is a convenience method for adding additional keys to the pool to enable
447    /// concurrent transaction signing and nonce management across multiple keys.
448    #[instrument(skip(self, seed_phrase, password))]
449    pub async fn add_seed_phrase_to_pool(
450        &self,
451        seed_phrase: &str,
452        password: Option<&str>,
453    ) -> Result<(), SignerError> {
454        let secret_key = get_secret_key_from_seed(
455            DEFAULT_HD_PATH.parse().expect("Valid HD path"),
456            seed_phrase,
457            password,
458        )
459        .map_err(|_| SignerError::SecretKeyIsNotAvailable)?;
460        let signer = SecretKeySigner::new(secret_key);
461        Ok(self.add_signer_to_pool(signer).await?)
462    }
463
464    /// Adds a seed phrase-derived key to the signing pool with a custom HD path.
465    ///
466    /// This is a convenience method for adding additional keys to the pool to enable
467    /// concurrent transaction signing and nonce management across multiple keys.
468    #[instrument(skip(self, seed_phrase, password))]
469    pub async fn add_seed_phrase_to_pool_with_hd_path(
470        &self,
471        seed_phrase: &str,
472        hd_path: BIP32Path,
473        password: Option<&str>,
474    ) -> Result<(), SignerError> {
475        let secret_key = get_secret_key_from_seed(hd_path, seed_phrase, password)
476            .map_err(|_| SignerError::SecretKeyIsNotAvailable)?;
477        let signer = SecretKeySigner::new(secret_key);
478        Ok(self.add_signer_to_pool(signer).await?)
479    }
480
481    /// Adds a key from an access key file to the signing pool.
482    ///
483    /// This is a convenience method for adding additional keys to the pool to enable
484    /// concurrent transaction signing and nonce management across multiple keys.
485    #[instrument(skip(self))]
486    pub async fn add_access_keyfile_to_pool(
487        &self,
488        path: PathBuf,
489    ) -> Result<(), AccessKeyFileError> {
490        let keypair = AccountKeyPair::load_access_key_file(&path)?;
491
492        if keypair.public_key != keypair.private_key.public_key() {
493            return Err(AccessKeyFileError::PrivatePublicKeyMismatch);
494        }
495
496        let signer = SecretKeySigner::new(keypair.private_key);
497        Ok(self.add_signer_to_pool(signer).await?)
498    }
499
500    /// Adds a Ledger hardware wallet signer to the pool with default HD path.
501    ///
502    /// This is a convenience method for adding additional keys to the pool to enable
503    /// concurrent transaction signing and nonce management across multiple keys.
504    #[cfg(feature = "ledger")]
505    #[instrument(skip(self))]
506    pub async fn add_ledger_to_pool(&self) -> Result<(), PublicKeyError> {
507        let signer =
508            ledger::LedgerSigner::new(DEFAULT_LEDGER_HD_PATH.parse().expect("Valid HD path"));
509        self.add_signer_to_pool(signer).await
510    }
511
512    /// Adds a Ledger hardware wallet signer to the pool with a custom HD path.
513    ///
514    /// This is a convenience method for adding additional keys to the pool to enable
515    /// concurrent transaction signing and nonce management across multiple keys.
516    #[cfg(feature = "ledger")]
517    #[instrument(skip(self))]
518    pub async fn add_ledger_to_pool_with_hd_path(
519        &self,
520        hd_path: BIP32Path,
521    ) -> Result<(), PublicKeyError> {
522        let signer = ledger::LedgerSigner::new(hd_path);
523        self.add_signer_to_pool(signer).await
524    }
525
526    /// Adds a keystore signer to the pool with a predefined public key.
527    ///
528    /// This is a convenience method for adding additional keys to the pool to enable
529    /// concurrent transaction signing and nonce management across multiple keys.
530    #[cfg(feature = "keystore")]
531    #[instrument(skip(self))]
532    pub async fn add_keystore_to_pool(&self, pub_key: PublicKey) -> Result<(), PublicKeyError> {
533        let signer = keystore::KeystoreSigner::new_with_pubkey(pub_key);
534        self.add_signer_to_pool(signer).await
535    }
536
537    /// Fetches the transaction nonce and block hash associated to the access key. Internally
538    /// caches the nonce as to not need to query for it every time, and ending up having to run
539    /// into contention with others.
540    #[instrument(skip(self, network), fields(account_id = %account_id))]
541    pub async fn fetch_tx_nonce(
542        &self,
543        account_id: AccountId,
544        public_key: PublicKey,
545        network: &NetworkConfig,
546    ) -> Result<(Nonce, CryptoHash, BlockHeight), SignerError> {
547        debug!(target: SIGNER_TARGET, "Fetching transaction nonce");
548
549        let nonce_data = crate::account::Account(account_id.clone())
550            .access_key(public_key)
551            .fetch_from(network)
552            .await
553            .map_err(|e| SignerError::FetchNonceError(Box::new(e)))?;
554
555        let nonce = {
556            let mut nonce_cache = self.nonce_cache.lock().await;
557            let nonce = nonce_cache.entry((account_id, public_key)).or_default();
558            *nonce = (*nonce).max(nonce_data.data.nonce.0) + 1;
559            *nonce
560        };
561
562        Ok((nonce, nonce_data.block_hash, nonce_data.block_height))
563    }
564
565    /// Creates a [Signer](`Signer`) using seed phrase with default HD path.
566    pub fn from_seed_phrase(
567        seed_phrase: &str,
568        password: Option<&str>,
569    ) -> Result<Arc<Self>, SecretError> {
570        let signer = Self::from_seed_phrase_with_hd_path(
571            seed_phrase,
572            DEFAULT_HD_PATH.parse().expect("Valid HD path"),
573            password,
574        )?;
575        Ok(signer)
576    }
577
578    /// Creates a [Signer](`Signer`) using a secret key.
579    pub fn from_secret_key(secret_key: SecretKey) -> Result<Arc<Self>, PublicKeyError> {
580        let inner = SecretKeySigner::new(secret_key);
581        Self::new(inner)
582    }
583
584    /// Creates a [Signer](`Signer`) using seed phrase with a custom HD path.
585    pub fn from_seed_phrase_with_hd_path(
586        seed_phrase: &str,
587        hd_path: BIP32Path,
588        password: Option<&str>,
589    ) -> Result<Arc<Self>, SecretError> {
590        let secret_key = get_secret_key_from_seed(hd_path, seed_phrase, password)?;
591        let inner = SecretKeySigner::new(secret_key);
592        Self::new(inner).map_err(|_| SecretError::DeriveKeyInvalidIndex)
593    }
594
595    /// Creates a [Signer](`Signer`) using a path to the access key file.
596    pub fn from_access_keyfile(path: PathBuf) -> Result<Arc<Self>, AccessKeyFileError> {
597        let keypair = AccountKeyPair::load_access_key_file(&path)?;
598        debug!(target: SIGNER_TARGET, "Access key file loaded successfully");
599
600        if keypair.public_key != keypair.private_key.public_key() {
601            return Err(AccessKeyFileError::PrivatePublicKeyMismatch);
602        }
603
604        let inner = SecretKeySigner::new(keypair.private_key);
605        Ok(Self::new(inner)?)
606    }
607
608    /// Creates a [Signer](`Signer`) using Ledger hardware wallet with default HD path.
609    #[cfg(feature = "ledger")]
610    pub fn from_ledger() -> Result<Arc<Self>, PublicKeyError> {
611        let inner =
612            ledger::LedgerSigner::new(DEFAULT_LEDGER_HD_PATH.parse().expect("Valid HD path"));
613        Self::new(inner)
614    }
615
616    /// Creates a [Signer](`Signer`) using Ledger hardware wallet with a custom HD path.
617    #[cfg(feature = "ledger")]
618    pub fn from_ledger_with_hd_path(hd_path: BIP32Path) -> Result<Arc<Self>, PublicKeyError> {
619        let inner = ledger::LedgerSigner::new(hd_path);
620        Self::new(inner)
621    }
622
623    /// Creates a [Signer](`Signer`) with keystore using a predefined public key.
624    #[cfg(feature = "keystore")]
625    pub fn from_keystore(pub_key: PublicKey) -> Result<Arc<Self>, PublicKeyError> {
626        let inner = keystore::KeystoreSigner::new_with_pubkey(pub_key);
627        Self::new(inner)
628    }
629
630    /// Creates a [Signer](`Signer`) with keystore. The provided function will query provided account for public keys and search
631    /// in the system keychain for the corresponding secret keys.
632    #[cfg(feature = "keystore")]
633    pub async fn from_keystore_with_search_for_keys(
634        account_id: AccountId,
635        network: &NetworkConfig,
636    ) -> Result<Arc<Self>, crate::errors::KeyStoreError> {
637        let inner = keystore::KeystoreSigner::search_for_keys(account_id, network).await?;
638        Self::new(inner).map_err(|_| {
639            // Convert SignerError into SecretError as a workaround since KeyStoreError doesn't have SignerError variant
640            crate::errors::KeyStoreError::SecretError(
641                crate::errors::SecretError::DeriveKeyInvalidIndex,
642            )
643        })
644    }
645
646    /// Retrieves the public key from the pool of signers.
647    /// The public key is rotated on each call.
648    #[instrument(skip(self))]
649    pub async fn get_public_key(&self) -> Result<PublicKey, PublicKeyError> {
650        let index = self.current_public_key.fetch_add(1, Ordering::SeqCst);
651        let public_key = {
652            let pool = self.pool.read().await;
653            *pool
654                .keys()
655                .nth(index % pool.len())
656                .ok_or(PublicKeyError::PublicKeyIsNotAvailable)?
657        };
658        debug!(target: SIGNER_TARGET, "Public key retrieved");
659        Ok(public_key)
660    }
661
662    #[instrument(skip(self, transaction), fields(signer_id = %transaction.signer_id, receiver_id = %transaction.receiver_id))]
663    pub async fn sign_meta(
664        &self,
665        transaction: PrepopulateTransaction,
666        public_key: PublicKey,
667        nonce: Nonce,
668        block_hash: CryptoHash,
669        max_block_height: BlockHeight,
670    ) -> Result<SignedDelegateAction, MetaSignError> {
671        let signer = self.pool.read().await;
672
673        signer
674            .get(&public_key)
675            .ok_or(PublicKeyError::PublicKeyIsNotAvailable)
676            .map_err(SignerError::from)?
677            .sign_meta(transaction, public_key, nonce, block_hash, max_block_height)
678            .await
679    }
680
681    #[instrument(skip(self, transaction), fields(signer_id = %transaction.signer_id, receiver_id = %transaction.receiver_id))]
682    pub async fn sign(
683        &self,
684        transaction: PrepopulateTransaction,
685        public_key: PublicKey,
686        nonce: Nonce,
687        block_hash: CryptoHash,
688    ) -> Result<SignedTransaction, SignerError> {
689        let pool = self.pool.read().await;
690
691        pool.get(&public_key)
692            .ok_or(PublicKeyError::PublicKeyIsNotAvailable)?
693            .sign(transaction, public_key, nonce, block_hash)
694            .await
695    }
696
697    /// Signs a [NEP413](https://github.com/near/NEPs/blob/master/neps/nep-0413.md) message.
698    ///
699    /// This is used for authentication and off-chain proof of account ownership.
700    #[instrument(skip(self), fields(signer_id = %signer_id, receiver_id = %payload.recipient, message = %payload.message))]
701    pub async fn sign_message_nep413(
702        &self,
703        signer_id: AccountId,
704        public_key: PublicKey,
705        payload: &NEP413Payload,
706    ) -> Result<Signature, SignerError> {
707        let pool = self.pool.read().await;
708
709        pool.get(&public_key)
710            .ok_or(PublicKeyError::PublicKeyIsNotAvailable)?
711            .sign_message_nep413(signer_id, public_key, payload)
712            .await
713    }
714}
715
716#[instrument(skip(unsigned_transaction, private_key))]
717fn get_signed_delegate_action(
718    mut unsigned_transaction: Transaction,
719    private_key: SecretKey,
720    max_block_height: u64,
721) -> core::result::Result<SignedDelegateAction, MetaSignError> {
722    use near_api_types::signable_message::{SignableMessage, SignableMessageType};
723    let actions: Vec<NonDelegateAction> = unsigned_transaction
724        .take_actions()
725        .into_iter()
726        .map(|action| {
727            NonDelegateAction::try_from(action)
728                .map_err(|_| MetaSignError::DelegateActionIsNotSupported)
729        })
730        .collect::<Result<Vec<_>, _>>()?;
731    let delegate_action = near_api_types::transaction::delegate_action::DelegateAction {
732        sender_id: unsigned_transaction.signer_id().clone(),
733        receiver_id: unsigned_transaction.receiver_id().clone(),
734        actions,
735        nonce: unsigned_transaction.nonce(),
736        max_block_height,
737        public_key: unsigned_transaction.public_key(),
738    };
739
740    // create a new signature here signing the delegate action + discriminant
741    let signable = SignableMessage::new(&delegate_action, SignableMessageType::DelegateAction);
742    let bytes = borsh::to_vec(&signable).expect("Failed to serialize");
743    let hash = CryptoHash::hash(&bytes);
744    let signature = private_key.sign(hash);
745
746    Ok(SignedDelegateAction {
747        delegate_action,
748        signature,
749    })
750}
751
752/// Generates a secret key from a seed phrase.
753///
754/// Prefer using [generate_secret_key_from_seed_phrase](`generate_secret_key_from_seed_phrase`) if you don't need to customize the HD path and passphrase.
755#[instrument(skip(seed_phrase_hd_path, master_seed_phrase, password))]
756pub fn get_secret_key_from_seed(
757    seed_phrase_hd_path: BIP32Path,
758    master_seed_phrase: &str,
759    password: Option<&str>,
760) -> Result<SecretKey, SecretError> {
761    let master_seed =
762        bip39::Mnemonic::parse(master_seed_phrase)?.to_seed(password.unwrap_or_default());
763    let derived_private_key = slipped10::derive_key_from_path(
764        &master_seed,
765        slipped10::Curve::Ed25519,
766        &seed_phrase_hd_path,
767    )
768    .map_err(|_| SecretError::DeriveKeyInvalidIndex)?;
769
770    let secret_key = SecretKey::ED25519(
771        near_api_types::crypto::secret_key::ED25519SecretKey::from_secret_key(
772            derived_private_key.key,
773        ),
774    );
775
776    Ok(secret_key)
777}
778
779/// Generates a new seed phrase with optional customization.
780///
781/// Prefer using [generate_seed_phrase](`generate_seed_phrase`) or [generate_secret_key](`generate_secret_key`) if you don't need to customize the seed phrase.
782pub fn generate_seed_phrase_custom(
783    word_count: Option<usize>,
784    hd_path: Option<BIP32Path>,
785    passphrase: Option<&str>,
786) -> Result<(String, PublicKey), SecretError> {
787    let mnemonic = bip39::Mnemonic::generate(word_count.unwrap_or(DEFAULT_WORD_COUNT))?;
788    let seed_phrase = mnemonic.words().collect::<Vec<&str>>().join(" ");
789
790    let secret_key = get_secret_key_from_seed(
791        hd_path.unwrap_or_else(|| DEFAULT_HD_PATH.parse().expect("Valid HD path")),
792        &seed_phrase,
793        passphrase,
794    )?;
795    let public_key = secret_key.public_key();
796
797    Ok((seed_phrase, public_key))
798}
799
800/// Generates a new seed phrase with default settings (12 words, [default HD path](`DEFAULT_HD_PATH`))
801pub fn generate_seed_phrase() -> Result<(String, PublicKey), SecretError> {
802    generate_seed_phrase_custom(None, None, None)
803}
804
805/// Generates a new 12-words seed phrase with a custom HD path
806pub fn generate_seed_phrase_with_hd_path(
807    hd_path: BIP32Path,
808) -> Result<(String, PublicKey), SecretError> {
809    generate_seed_phrase_custom(None, Some(hd_path), None)
810}
811
812/// Generates a new 12-words seed phrase with a custom passphrase and [default HD path](`DEFAULT_HD_PATH`)
813pub fn generate_seed_phrase_with_passphrase(
814    passphrase: &str,
815) -> Result<(String, PublicKey), SecretError> {
816    generate_seed_phrase_custom(None, None, Some(passphrase))
817}
818
819/// Generates a new seed phrase with a custom word count and [default HD path](`DEFAULT_HD_PATH`)
820pub fn generate_seed_phrase_with_word_count(
821    word_count: usize,
822) -> Result<(String, PublicKey), SecretError> {
823    generate_seed_phrase_custom(Some(word_count), None, None)
824}
825
826/// Generates a secret key from a new seed phrase using default settings
827pub fn generate_secret_key() -> Result<SecretKey, SecretError> {
828    let (seed_phrase, _) = generate_seed_phrase()?;
829    let secret_key = get_secret_key_from_seed(
830        DEFAULT_HD_PATH.parse().expect("Valid HD path"),
831        &seed_phrase,
832        None,
833    )?;
834    Ok(secret_key)
835}
836
837/// Generates a secret key from a seed phrase using [default HD path](`DEFAULT_HD_PATH`)
838pub fn generate_secret_key_from_seed_phrase(seed_phrase: String) -> Result<SecretKey, SecretError> {
839    get_secret_key_from_seed(
840        DEFAULT_HD_PATH.parse().expect("Valid HD path"),
841        &seed_phrase,
842        None,
843    )
844}
845
846#[cfg(test)]
847mod nep_413_tests {
848    use base64::{prelude::BASE64_STANDARD, Engine};
849    use near_api_types::{
850        crypto::KeyType, transaction::actions::FunctionCallPermission, AccessKeyPermission,
851        NearToken, Signature,
852    };
853    use near_sandbox::config::{DEFAULT_GENESIS_ACCOUNT, DEFAULT_GENESIS_ACCOUNT_PRIVATE_KEY};
854    use testresult::TestResult;
855
856    use crate::{signer::generate_secret_key, Account, NetworkConfig};
857
858    use super::{NEP413Payload, Signer};
859
860    fn from_base64(base64: &str) -> Vec<u8> {
861        BASE64_STANDARD.decode(base64).unwrap()
862    }
863
864    // The mockup data is created using the sender/my-near-wallet NEP413 implementation
865    // The meteor wallet ignores the callback url on time of writing.
866    #[tokio::test]
867    pub async fn with_callback_url() {
868        let payload: NEP413Payload = NEP413Payload {
869            message: "Hello NEAR!".to_string(),
870            nonce: from_base64("KNV0cOpvJ50D5vfF9pqWom8wo2sliQ4W+Wa7uZ3Uk6Y=")
871                .try_into()
872                .unwrap(),
873            recipient: "example.near".to_string(),
874            // callback_url: None,
875            callback_url: Some("http://localhost:3000".to_string()),
876        };
877
878        let signer = Signer::from_seed_phrase(
879            "fatal edge jacket cash hard pass gallery fabric whisper size rain biology",
880            None,
881        )
882        .unwrap();
883        let public_key = signer.get_public_key().await.unwrap();
884        let signature = signer
885            .sign_message_nep413("round-toad.testnet".parse().unwrap(), public_key, &payload)
886            .await
887            .unwrap();
888
889        let expected_signature = from_base64(
890            "zzZQ/GwAjrZVrTIFlvmmQbDQHllfzrr8urVWHaRt5cPfcXaCSZo35c5LDpPpTKivR6BxLyb3lcPM0FfCW5lcBQ==",
891        );
892        assert_eq!(
893            signature,
894            Signature::from_parts(KeyType::ED25519, expected_signature.as_slice()).unwrap()
895        );
896    }
897
898    // The mockup data is created using the sender/meteor NEP413 implementation.
899    // My near wallet adds the callback url to the payload if it is not provided on time of writing.
900    #[tokio::test]
901    pub async fn without_callback_url() {
902        let payload: NEP413Payload = NEP413Payload {
903            message: "Hello NEAR!".to_string(),
904            nonce: from_base64("KNV0cOpvJ50D5vfF9pqWom8wo2sliQ4W+Wa7uZ3Uk6Y=")
905                .try_into()
906                .unwrap(),
907            recipient: "example.near".to_string(),
908            callback_url: None,
909        };
910
911        let signer = Signer::from_seed_phrase(
912            "fatal edge jacket cash hard pass gallery fabric whisper size rain biology",
913            None,
914        )
915        .unwrap();
916        let public_key = signer.get_public_key().await.unwrap();
917        let signature = signer
918            .sign_message_nep413("round-toad.testnet".parse().unwrap(), public_key, &payload)
919            .await
920            .unwrap();
921
922        let expected_signature = from_base64(
923            "NnJgPU1Ql7ccRTITIoOVsIfElmvH1RV7QAT4a9Vh6ShCOnjIzRwxqX54JzoQ/nK02p7VBMI2vJn48rpImIJwAw==",
924        );
925        assert_eq!(
926            signature,
927            Signature::from_parts(KeyType::ED25519, expected_signature.as_slice()).unwrap()
928        );
929    }
930
931    #[tokio::test]
932    pub async fn test_verify_nep413_payload() -> TestResult {
933        let sandbox = near_sandbox::Sandbox::start_sandbox().await?;
934        let network = NetworkConfig::from_rpc_url("sandbox", sandbox.rpc_addr.parse()?);
935
936        let signer = Signer::from_secret_key(DEFAULT_GENESIS_ACCOUNT_PRIVATE_KEY.parse()?)?;
937        let public_key = signer.get_public_key().await?;
938
939        let payload: NEP413Payload = NEP413Payload {
940            message: "Hello NEAR!".to_string(),
941            nonce: from_base64("KNV0cOpvJ50D5vfF9pqWom8wo2sliQ4W+Wa7uZ3Uk6Y=")
942                .try_into()
943                .unwrap(),
944            recipient: DEFAULT_GENESIS_ACCOUNT.to_string(),
945            callback_url: None,
946        };
947
948        let signature = signer
949            .sign_message_nep413(DEFAULT_GENESIS_ACCOUNT.into(), public_key, &payload)
950            .await?;
951
952        let result = payload
953            .verify(
954                &DEFAULT_GENESIS_ACCOUNT.into(),
955                public_key,
956                &signature,
957                &network,
958            )
959            .await?;
960
961        assert!(result);
962        Ok(())
963    }
964
965    #[tokio::test]
966    pub async fn verification_fails_without_public_key() -> TestResult {
967        let sandbox = near_sandbox::Sandbox::start_sandbox().await?;
968        let network = NetworkConfig::from_rpc_url("sandbox", sandbox.rpc_addr.parse()?);
969        let secret_key = generate_secret_key()?;
970
971        let signer = Signer::from_secret_key(secret_key)?;
972        let public_key = signer.get_public_key().await?;
973
974        let payload: NEP413Payload = NEP413Payload {
975            message: "Hello NEAR!".to_string(),
976            nonce: from_base64("KNV0cOpvJ50D5vfF9pqWom8wo2sliQ4W+Wa7uZ3Uk6Y=")
977                .try_into()
978                .unwrap(),
979            recipient: DEFAULT_GENESIS_ACCOUNT.to_string(),
980            callback_url: None,
981        };
982
983        let signature = signer
984            .sign_message_nep413(DEFAULT_GENESIS_ACCOUNT.into(), public_key, &payload)
985            .await?;
986
987        let result = payload
988            .verify(
989                &DEFAULT_GENESIS_ACCOUNT.into(),
990                public_key,
991                &signature,
992                &network,
993            )
994            .await?;
995        assert!(!result);
996
997        Ok(())
998    }
999
1000    #[tokio::test]
1001    pub async fn verification_fails_with_function_call_access_key() -> TestResult {
1002        let sandbox = near_sandbox::Sandbox::start_sandbox().await?;
1003        let network = NetworkConfig::from_rpc_url("sandbox", sandbox.rpc_addr.parse()?);
1004        let secret_key = generate_secret_key()?;
1005
1006        let msg_signer = Signer::from_secret_key(secret_key)?;
1007        let tx_signer = Signer::from_secret_key(DEFAULT_GENESIS_ACCOUNT_PRIVATE_KEY.parse()?)?;
1008        let public_key = msg_signer.get_public_key().await?;
1009
1010        Account(DEFAULT_GENESIS_ACCOUNT.into())
1011            .add_key(
1012                AccessKeyPermission::FunctionCall(FunctionCallPermission {
1013                    allowance: Some(NearToken::from_near(1)),
1014                    receiver_id: "test".to_string(),
1015                    method_names: vec!["test".to_string()],
1016                }),
1017                public_key,
1018            )
1019            .with_signer(tx_signer.clone())
1020            .send_to(&network)
1021            .await?
1022            .assert_success();
1023
1024        let payload: NEP413Payload = NEP413Payload {
1025            message: "Hello NEAR!".to_string(),
1026            nonce: from_base64("KNV0cOpvJ50D5vfF9pqWom8wo2sliQ4W+Wa7uZ3Uk6Y=")
1027                .try_into()
1028                .unwrap(),
1029            recipient: DEFAULT_GENESIS_ACCOUNT.to_string(),
1030            callback_url: None,
1031        };
1032
1033        let signature = msg_signer
1034            .sign_message_nep413(DEFAULT_GENESIS_ACCOUNT.into(), public_key, &payload)
1035            .await?;
1036
1037        let result = payload
1038            .verify(
1039                &DEFAULT_GENESIS_ACCOUNT.into(),
1040                public_key,
1041                &signature,
1042                &network,
1043            )
1044            .await?;
1045        assert!(!result);
1046
1047        Ok(())
1048    }
1049}