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, Reference, 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    ///
541    /// Uses finalized block hash to avoid "Transaction Expired" errors when sending transactions
542    /// to load-balanced RPC endpoints where different nodes may be at different chain heights.
543    #[instrument(skip(self, network), fields(account_id = %account_id))]
544    pub async fn fetch_tx_nonce(
545        &self,
546        account_id: AccountId,
547        public_key: PublicKey,
548        network: &NetworkConfig,
549    ) -> Result<(Nonce, CryptoHash, BlockHeight), SignerError> {
550        debug!(target: SIGNER_TARGET, "Fetching transaction nonce");
551
552        let nonce_data = crate::account::Account(account_id.clone())
553            .access_key(public_key)
554            .at(Reference::Final)
555            .fetch_from(network)
556            .await
557            .map_err(|e| SignerError::FetchNonceError(Box::new(e)))?;
558
559        let nonce = {
560            let mut nonce_cache = self.nonce_cache.lock().await;
561            let nonce = nonce_cache.entry((account_id, public_key)).or_default();
562            *nonce = (*nonce).max(nonce_data.data.nonce.0) + 1;
563            *nonce
564        };
565
566        Ok((nonce, nonce_data.block_hash, nonce_data.block_height))
567    }
568
569    /// Creates a [Signer](`Signer`) using seed phrase with default HD path.
570    pub fn from_seed_phrase(
571        seed_phrase: &str,
572        password: Option<&str>,
573    ) -> Result<Arc<Self>, SecretError> {
574        let signer = Self::from_seed_phrase_with_hd_path(
575            seed_phrase,
576            DEFAULT_HD_PATH.parse().expect("Valid HD path"),
577            password,
578        )?;
579        Ok(signer)
580    }
581
582    /// Creates a [Signer](`Signer`) using a secret key.
583    pub fn from_secret_key(secret_key: SecretKey) -> Result<Arc<Self>, PublicKeyError> {
584        let inner = SecretKeySigner::new(secret_key);
585        Self::new(inner)
586    }
587
588    /// Creates a [Signer](`Signer`) using seed phrase with a custom HD path.
589    pub fn from_seed_phrase_with_hd_path(
590        seed_phrase: &str,
591        hd_path: BIP32Path,
592        password: Option<&str>,
593    ) -> Result<Arc<Self>, SecretError> {
594        let secret_key = get_secret_key_from_seed(hd_path, seed_phrase, password)?;
595        let inner = SecretKeySigner::new(secret_key);
596        Self::new(inner).map_err(|_| SecretError::DeriveKeyInvalidIndex)
597    }
598
599    /// Creates a [Signer](`Signer`) using a path to the access key file.
600    pub fn from_access_keyfile(path: PathBuf) -> Result<Arc<Self>, AccessKeyFileError> {
601        let keypair = AccountKeyPair::load_access_key_file(&path)?;
602        debug!(target: SIGNER_TARGET, "Access key file loaded successfully");
603
604        if keypair.public_key != keypair.private_key.public_key() {
605            return Err(AccessKeyFileError::PrivatePublicKeyMismatch);
606        }
607
608        let inner = SecretKeySigner::new(keypair.private_key);
609        Ok(Self::new(inner)?)
610    }
611
612    /// Creates a [Signer](`Signer`) using Ledger hardware wallet with default HD path.
613    #[cfg(feature = "ledger")]
614    pub fn from_ledger() -> Result<Arc<Self>, PublicKeyError> {
615        let inner =
616            ledger::LedgerSigner::new(DEFAULT_LEDGER_HD_PATH.parse().expect("Valid HD path"));
617        Self::new(inner)
618    }
619
620    /// Creates a [Signer](`Signer`) using Ledger hardware wallet with a custom HD path.
621    #[cfg(feature = "ledger")]
622    pub fn from_ledger_with_hd_path(hd_path: BIP32Path) -> Result<Arc<Self>, PublicKeyError> {
623        let inner = ledger::LedgerSigner::new(hd_path);
624        Self::new(inner)
625    }
626
627    /// Creates a [Signer](`Signer`) with keystore using a predefined public key.
628    #[cfg(feature = "keystore")]
629    pub fn from_keystore(pub_key: PublicKey) -> Result<Arc<Self>, PublicKeyError> {
630        let inner = keystore::KeystoreSigner::new_with_pubkey(pub_key);
631        Self::new(inner)
632    }
633
634    /// Creates a [Signer](`Signer`) with keystore. The provided function will query provided account for public keys and search
635    /// in the system keychain for the corresponding secret keys.
636    #[cfg(feature = "keystore")]
637    pub async fn from_keystore_with_search_for_keys(
638        account_id: AccountId,
639        network: &NetworkConfig,
640    ) -> Result<Arc<Self>, crate::errors::KeyStoreError> {
641        let inner = keystore::KeystoreSigner::search_for_keys(account_id, network).await?;
642        Self::new(inner).map_err(|_| {
643            // Convert SignerError into SecretError as a workaround since KeyStoreError doesn't have SignerError variant
644            crate::errors::KeyStoreError::SecretError(
645                crate::errors::SecretError::DeriveKeyInvalidIndex,
646            )
647        })
648    }
649
650    /// Retrieves the public key from the pool of signers.
651    /// The public key is rotated on each call.
652    #[instrument(skip(self))]
653    pub async fn get_public_key(&self) -> Result<PublicKey, PublicKeyError> {
654        let index = self.current_public_key.fetch_add(1, Ordering::SeqCst);
655        let public_key = {
656            let pool = self.pool.read().await;
657            *pool
658                .keys()
659                .nth(index % pool.len())
660                .ok_or(PublicKeyError::PublicKeyIsNotAvailable)?
661        };
662        debug!(target: SIGNER_TARGET, "Public key retrieved");
663        Ok(public_key)
664    }
665
666    #[instrument(skip(self, transaction), fields(signer_id = %transaction.signer_id, receiver_id = %transaction.receiver_id))]
667    pub async fn sign_meta(
668        &self,
669        transaction: PrepopulateTransaction,
670        public_key: PublicKey,
671        nonce: Nonce,
672        block_hash: CryptoHash,
673        max_block_height: BlockHeight,
674    ) -> Result<SignedDelegateAction, MetaSignError> {
675        let signer = self.pool.read().await;
676
677        signer
678            .get(&public_key)
679            .ok_or(PublicKeyError::PublicKeyIsNotAvailable)
680            .map_err(SignerError::from)?
681            .sign_meta(transaction, public_key, nonce, block_hash, max_block_height)
682            .await
683    }
684
685    #[instrument(skip(self, transaction), fields(signer_id = %transaction.signer_id, receiver_id = %transaction.receiver_id))]
686    pub async fn sign(
687        &self,
688        transaction: PrepopulateTransaction,
689        public_key: PublicKey,
690        nonce: Nonce,
691        block_hash: CryptoHash,
692    ) -> Result<SignedTransaction, SignerError> {
693        let pool = self.pool.read().await;
694
695        pool.get(&public_key)
696            .ok_or(PublicKeyError::PublicKeyIsNotAvailable)?
697            .sign(transaction, public_key, nonce, block_hash)
698            .await
699    }
700
701    /// Signs a [NEP413](https://github.com/near/NEPs/blob/master/neps/nep-0413.md) message.
702    ///
703    /// This is used for authentication and off-chain proof of account ownership.
704    #[instrument(skip(self), fields(signer_id = %signer_id, receiver_id = %payload.recipient, message = %payload.message))]
705    pub async fn sign_message_nep413(
706        &self,
707        signer_id: AccountId,
708        public_key: PublicKey,
709        payload: &NEP413Payload,
710    ) -> Result<Signature, SignerError> {
711        let pool = self.pool.read().await;
712
713        pool.get(&public_key)
714            .ok_or(PublicKeyError::PublicKeyIsNotAvailable)?
715            .sign_message_nep413(signer_id, public_key, payload)
716            .await
717    }
718}
719
720#[instrument(skip(unsigned_transaction, private_key))]
721fn get_signed_delegate_action(
722    mut unsigned_transaction: Transaction,
723    private_key: SecretKey,
724    max_block_height: u64,
725) -> core::result::Result<SignedDelegateAction, MetaSignError> {
726    use near_api_types::signable_message::{SignableMessage, SignableMessageType};
727    let actions: Vec<NonDelegateAction> = unsigned_transaction
728        .take_actions()
729        .into_iter()
730        .map(|action| {
731            NonDelegateAction::try_from(action)
732                .map_err(|_| MetaSignError::DelegateActionIsNotSupported)
733        })
734        .collect::<Result<Vec<_>, _>>()?;
735    let delegate_action = near_api_types::transaction::delegate_action::DelegateAction {
736        sender_id: unsigned_transaction.signer_id().clone(),
737        receiver_id: unsigned_transaction.receiver_id().clone(),
738        actions,
739        nonce: unsigned_transaction.nonce(),
740        max_block_height,
741        public_key: unsigned_transaction.public_key(),
742    };
743
744    // create a new signature here signing the delegate action + discriminant
745    let signable = SignableMessage::new(&delegate_action, SignableMessageType::DelegateAction);
746    let bytes = borsh::to_vec(&signable).expect("Failed to serialize");
747    let hash = CryptoHash::hash(&bytes);
748    let signature = private_key.sign(hash);
749
750    Ok(SignedDelegateAction {
751        delegate_action,
752        signature,
753    })
754}
755
756/// Generates a secret key from a seed phrase.
757///
758/// 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.
759#[instrument(skip(seed_phrase_hd_path, master_seed_phrase, password))]
760pub fn get_secret_key_from_seed(
761    seed_phrase_hd_path: BIP32Path,
762    master_seed_phrase: &str,
763    password: Option<&str>,
764) -> Result<SecretKey, SecretError> {
765    let master_seed =
766        bip39::Mnemonic::parse(master_seed_phrase)?.to_seed(password.unwrap_or_default());
767    let derived_private_key = slipped10::derive_key_from_path(
768        &master_seed,
769        slipped10::Curve::Ed25519,
770        &seed_phrase_hd_path,
771    )
772    .map_err(|_| SecretError::DeriveKeyInvalidIndex)?;
773
774    let secret_key = SecretKey::ED25519(
775        near_api_types::crypto::secret_key::ED25519SecretKey::from_secret_key(
776            derived_private_key.key,
777        ),
778    );
779
780    Ok(secret_key)
781}
782
783/// Generates a new seed phrase with optional customization.
784///
785/// 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.
786pub fn generate_seed_phrase_custom(
787    word_count: Option<usize>,
788    hd_path: Option<BIP32Path>,
789    passphrase: Option<&str>,
790) -> Result<(String, PublicKey), SecretError> {
791    let mnemonic = bip39::Mnemonic::generate(word_count.unwrap_or(DEFAULT_WORD_COUNT))?;
792    let seed_phrase = mnemonic.words().collect::<Vec<&str>>().join(" ");
793
794    let secret_key = get_secret_key_from_seed(
795        hd_path.unwrap_or_else(|| DEFAULT_HD_PATH.parse().expect("Valid HD path")),
796        &seed_phrase,
797        passphrase,
798    )?;
799    let public_key = secret_key.public_key();
800
801    Ok((seed_phrase, public_key))
802}
803
804/// Generates a new seed phrase with default settings (12 words, [default HD path](`DEFAULT_HD_PATH`))
805pub fn generate_seed_phrase() -> Result<(String, PublicKey), SecretError> {
806    generate_seed_phrase_custom(None, None, None)
807}
808
809/// Generates a new 12-words seed phrase with a custom HD path
810pub fn generate_seed_phrase_with_hd_path(
811    hd_path: BIP32Path,
812) -> Result<(String, PublicKey), SecretError> {
813    generate_seed_phrase_custom(None, Some(hd_path), None)
814}
815
816/// Generates a new 12-words seed phrase with a custom passphrase and [default HD path](`DEFAULT_HD_PATH`)
817pub fn generate_seed_phrase_with_passphrase(
818    passphrase: &str,
819) -> Result<(String, PublicKey), SecretError> {
820    generate_seed_phrase_custom(None, None, Some(passphrase))
821}
822
823/// Generates a new seed phrase with a custom word count and [default HD path](`DEFAULT_HD_PATH`)
824pub fn generate_seed_phrase_with_word_count(
825    word_count: usize,
826) -> Result<(String, PublicKey), SecretError> {
827    generate_seed_phrase_custom(Some(word_count), None, None)
828}
829
830/// Generates a secret key from a new seed phrase using default settings
831pub fn generate_secret_key() -> Result<SecretKey, SecretError> {
832    let (seed_phrase, _) = generate_seed_phrase()?;
833    let secret_key = get_secret_key_from_seed(
834        DEFAULT_HD_PATH.parse().expect("Valid HD path"),
835        &seed_phrase,
836        None,
837    )?;
838    Ok(secret_key)
839}
840
841/// Generates a secret key from a seed phrase using [default HD path](`DEFAULT_HD_PATH`)
842pub fn generate_secret_key_from_seed_phrase(seed_phrase: String) -> Result<SecretKey, SecretError> {
843    get_secret_key_from_seed(
844        DEFAULT_HD_PATH.parse().expect("Valid HD path"),
845        &seed_phrase,
846        None,
847    )
848}
849
850#[cfg(test)]
851mod nep_413_tests {
852    use base64::{prelude::BASE64_STANDARD, Engine};
853    use near_api_types::{
854        crypto::KeyType, transaction::actions::FunctionCallPermission, AccessKeyPermission,
855        NearToken, Signature,
856    };
857    use near_sandbox::config::{DEFAULT_GENESIS_ACCOUNT, DEFAULT_GENESIS_ACCOUNT_PRIVATE_KEY};
858    use testresult::TestResult;
859
860    use crate::{signer::generate_secret_key, Account, NetworkConfig};
861
862    use super::{NEP413Payload, Signer};
863
864    fn from_base64(base64: &str) -> Vec<u8> {
865        BASE64_STANDARD.decode(base64).unwrap()
866    }
867
868    // The mockup data is created using the sender/my-near-wallet NEP413 implementation
869    // The meteor wallet ignores the callback url on time of writing.
870    #[tokio::test]
871    pub async fn with_callback_url() {
872        let payload: NEP413Payload = NEP413Payload {
873            message: "Hello NEAR!".to_string(),
874            nonce: from_base64("KNV0cOpvJ50D5vfF9pqWom8wo2sliQ4W+Wa7uZ3Uk6Y=")
875                .try_into()
876                .unwrap(),
877            recipient: "example.near".to_string(),
878            // callback_url: None,
879            callback_url: Some("http://localhost:3000".to_string()),
880        };
881
882        let signer = Signer::from_seed_phrase(
883            "fatal edge jacket cash hard pass gallery fabric whisper size rain biology",
884            None,
885        )
886        .unwrap();
887        let public_key = signer.get_public_key().await.unwrap();
888        let signature = signer
889            .sign_message_nep413("round-toad.testnet".parse().unwrap(), public_key, &payload)
890            .await
891            .unwrap();
892
893        let expected_signature = from_base64(
894            "zzZQ/GwAjrZVrTIFlvmmQbDQHllfzrr8urVWHaRt5cPfcXaCSZo35c5LDpPpTKivR6BxLyb3lcPM0FfCW5lcBQ==",
895        );
896        assert_eq!(
897            signature,
898            Signature::from_parts(KeyType::ED25519, expected_signature.as_slice()).unwrap()
899        );
900    }
901
902    // The mockup data is created using the sender/meteor NEP413 implementation.
903    // My near wallet adds the callback url to the payload if it is not provided on time of writing.
904    #[tokio::test]
905    pub async fn without_callback_url() {
906        let payload: NEP413Payload = NEP413Payload {
907            message: "Hello NEAR!".to_string(),
908            nonce: from_base64("KNV0cOpvJ50D5vfF9pqWom8wo2sliQ4W+Wa7uZ3Uk6Y=")
909                .try_into()
910                .unwrap(),
911            recipient: "example.near".to_string(),
912            callback_url: None,
913        };
914
915        let signer = Signer::from_seed_phrase(
916            "fatal edge jacket cash hard pass gallery fabric whisper size rain biology",
917            None,
918        )
919        .unwrap();
920        let public_key = signer.get_public_key().await.unwrap();
921        let signature = signer
922            .sign_message_nep413("round-toad.testnet".parse().unwrap(), public_key, &payload)
923            .await
924            .unwrap();
925
926        let expected_signature = from_base64(
927            "NnJgPU1Ql7ccRTITIoOVsIfElmvH1RV7QAT4a9Vh6ShCOnjIzRwxqX54JzoQ/nK02p7VBMI2vJn48rpImIJwAw==",
928        );
929        assert_eq!(
930            signature,
931            Signature::from_parts(KeyType::ED25519, expected_signature.as_slice()).unwrap()
932        );
933    }
934
935    #[tokio::test]
936    pub async fn test_verify_nep413_payload() -> TestResult {
937        let sandbox = near_sandbox::Sandbox::start_sandbox().await?;
938        let network = NetworkConfig::from_rpc_url("sandbox", sandbox.rpc_addr.parse()?);
939
940        let signer = Signer::from_secret_key(DEFAULT_GENESIS_ACCOUNT_PRIVATE_KEY.parse()?)?;
941        let public_key = signer.get_public_key().await?;
942
943        let payload: NEP413Payload = NEP413Payload {
944            message: "Hello NEAR!".to_string(),
945            nonce: from_base64("KNV0cOpvJ50D5vfF9pqWom8wo2sliQ4W+Wa7uZ3Uk6Y=")
946                .try_into()
947                .unwrap(),
948            recipient: DEFAULT_GENESIS_ACCOUNT.to_string(),
949            callback_url: None,
950        };
951
952        let signature = signer
953            .sign_message_nep413(DEFAULT_GENESIS_ACCOUNT.into(), public_key, &payload)
954            .await?;
955
956        let result = payload
957            .verify(
958                &DEFAULT_GENESIS_ACCOUNT.into(),
959                public_key,
960                &signature,
961                &network,
962            )
963            .await?;
964
965        assert!(result);
966        Ok(())
967    }
968
969    #[tokio::test]
970    pub async fn verification_fails_without_public_key() -> TestResult {
971        let sandbox = near_sandbox::Sandbox::start_sandbox().await?;
972        let network = NetworkConfig::from_rpc_url("sandbox", sandbox.rpc_addr.parse()?);
973        let secret_key = generate_secret_key()?;
974
975        let signer = Signer::from_secret_key(secret_key)?;
976        let public_key = signer.get_public_key().await?;
977
978        let payload: NEP413Payload = NEP413Payload {
979            message: "Hello NEAR!".to_string(),
980            nonce: from_base64("KNV0cOpvJ50D5vfF9pqWom8wo2sliQ4W+Wa7uZ3Uk6Y=")
981                .try_into()
982                .unwrap(),
983            recipient: DEFAULT_GENESIS_ACCOUNT.to_string(),
984            callback_url: None,
985        };
986
987        let signature = signer
988            .sign_message_nep413(DEFAULT_GENESIS_ACCOUNT.into(), public_key, &payload)
989            .await?;
990
991        let result = payload
992            .verify(
993                &DEFAULT_GENESIS_ACCOUNT.into(),
994                public_key,
995                &signature,
996                &network,
997            )
998            .await?;
999        assert!(!result);
1000
1001        Ok(())
1002    }
1003
1004    #[tokio::test]
1005    pub async fn verification_fails_with_function_call_access_key() -> TestResult {
1006        let sandbox = near_sandbox::Sandbox::start_sandbox().await?;
1007        let network = NetworkConfig::from_rpc_url("sandbox", sandbox.rpc_addr.parse()?);
1008        let secret_key = generate_secret_key()?;
1009
1010        let msg_signer = Signer::from_secret_key(secret_key)?;
1011        let tx_signer = Signer::from_secret_key(DEFAULT_GENESIS_ACCOUNT_PRIVATE_KEY.parse()?)?;
1012        let public_key = msg_signer.get_public_key().await?;
1013
1014        Account(DEFAULT_GENESIS_ACCOUNT.into())
1015            .add_key(
1016                AccessKeyPermission::FunctionCall(FunctionCallPermission {
1017                    allowance: Some(NearToken::from_near(1)),
1018                    receiver_id: "test".to_string(),
1019                    method_names: vec!["test".to_string()],
1020                }),
1021                public_key,
1022            )
1023            .with_signer(tx_signer.clone())
1024            .send_to(&network)
1025            .await?
1026            .assert_success();
1027
1028        let payload: NEP413Payload = NEP413Payload {
1029            message: "Hello NEAR!".to_string(),
1030            nonce: from_base64("KNV0cOpvJ50D5vfF9pqWom8wo2sliQ4W+Wa7uZ3Uk6Y=")
1031                .try_into()
1032                .unwrap(),
1033            recipient: DEFAULT_GENESIS_ACCOUNT.to_string(),
1034            callback_url: None,
1035        };
1036
1037        let signature = msg_signer
1038            .sign_message_nep413(DEFAULT_GENESIS_ACCOUNT.into(), public_key, &payload)
1039            .await?;
1040
1041        let result = payload
1042            .verify(
1043                &DEFAULT_GENESIS_ACCOUNT.into(),
1044                public_key,
1045                &signature,
1046                &network,
1047            )
1048            .await?;
1049        assert!(!result);
1050
1051        Ok(())
1052    }
1053}