Skip to main content

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