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