1use std::{
111 collections::HashMap,
112 path::{Path, PathBuf},
113 str::FromStr,
114 sync::{
115 Arc,
116 atomic::{AtomicU64, AtomicUsize, Ordering},
117 },
118};
119
120use near_api_types::{
121 AccountId, BlockHeight, CryptoHash, Nonce, PublicKey, SecretKey, Signature,
122 transaction::{
123 PrepopulateTransaction, SignedTransaction, Transaction, TransactionV0,
124 delegate_action::{NonDelegateAction, SignedDelegateAction},
125 },
126};
127
128use borsh::{BorshDeserialize, BorshSerialize};
129use serde::{Deserialize, Serialize};
130use slipped10::BIP32Path;
131use tracing::{debug, info, instrument, trace, warn};
132
133use crate::{
134 config::NetworkConfig,
135 errors::{AccessKeyFileError, MetaSignError, SecretError, SignerError},
136};
137
138use secret_key::SecretKeySigner;
139
140#[cfg(feature = "keystore")]
141pub mod keystore;
142#[cfg(feature = "ledger")]
143pub mod ledger;
144pub mod secret_key;
145
146const SIGNER_TARGET: &str = "near_api::signer";
147pub const DEFAULT_HD_PATH: &str = "m/44'/397'/0'";
149pub const DEFAULT_WORD_COUNT: usize = 12;
151
152#[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#[derive(Debug, Clone, Serialize, Deserialize, BorshDeserialize, BorshSerialize)]
169pub struct NEP413Payload {
170 pub message: String,
172 pub nonce: [u8; 32],
174 pub recipient: String,
176 pub callback_url: Option<String>,
178}
179
180#[cfg(feature = "ledger")]
181impl From<NEP413Payload> for near_ledger::NEP413Payload {
182 fn from(payload: NEP413Payload) -> Self {
183 Self {
184 message: payload.message,
185 nonce: payload.nonce,
186 recipient: payload.recipient,
187 callback_url: payload.callback_url,
188 }
189 }
190}
191
192#[async_trait::async_trait]
248pub trait SignerTrait {
249 #[instrument(skip(self, tr), fields(signer_id = %tr.signer_id, receiver_id = %tr.receiver_id))]
256 async fn sign_meta(
257 &self,
258 tr: PrepopulateTransaction,
259 public_key: PublicKey,
260 nonce: Nonce,
261 block_hash: CryptoHash,
262 max_block_height: BlockHeight,
263 ) -> Result<SignedDelegateAction, MetaSignError> {
264 let signer_secret_key = self.get_secret_key(&tr.signer_id, &public_key).await?;
265 let unsigned_transaction = Transaction::V0(TransactionV0 {
266 signer_id: tr.signer_id.clone(),
267 public_key,
268 nonce,
269 receiver_id: tr.receiver_id,
270 block_hash,
271 actions: tr.actions,
272 });
273
274 get_signed_delegate_action(unsigned_transaction, signer_secret_key, max_block_height)
275 }
276
277 #[instrument(skip(self, tr), fields(signer_id = %tr.signer_id, receiver_id = %tr.receiver_id))]
284 async fn sign(
285 &self,
286 tr: PrepopulateTransaction,
287 public_key: PublicKey,
288 nonce: Nonce,
289 block_hash: CryptoHash,
290 ) -> Result<SignedTransaction, SignerError> {
291 let signer_secret_key = self.get_secret_key(&tr.signer_id, &public_key).await?;
292 let unsigned_transaction = Transaction::V0(TransactionV0 {
293 signer_id: tr.signer_id.clone(),
294 public_key,
295 nonce,
296 receiver_id: tr.receiver_id,
297 block_hash,
298 actions: tr.actions,
299 });
300
301 let signature = signer_secret_key.sign(unsigned_transaction.get_hash().0.as_ref());
302
303 Ok(SignedTransaction::new(signature, unsigned_transaction))
304 }
305
306 #[instrument(skip(self), fields(signer_id = %signer_id, receiver_id = %payload.recipient, message = %payload.message))]
311 async fn sign_message_nep413(
312 &self,
313 signer_id: AccountId,
314 public_key: PublicKey,
315 payload: NEP413Payload,
316 ) -> Result<Signature, SignerError> {
317 const NEP413_413_SIGN_MESSAGE_PREFIX: u32 = (1u32 << 31u32) + 413u32;
318 let mut bytes = NEP413_413_SIGN_MESSAGE_PREFIX.to_le_bytes().to_vec();
319 borsh::to_writer(&mut bytes, &payload)?;
320 let hash = CryptoHash::hash(&bytes);
321 let secret = self.get_secret_key(&signer_id, &public_key).await?;
322 let signature = secret.sign(hash.0.as_ref());
323 Ok(signature)
324 }
325
326 async fn get_secret_key(
333 &self,
334 signer_id: &AccountId,
335 public_key: &PublicKey,
336 ) -> Result<SecretKey, SignerError>;
337
338 fn get_public_key(&self) -> Result<PublicKey, SignerError>;
342}
343
344pub struct Signer {
349 pool: tokio::sync::RwLock<HashMap<PublicKey, Box<dyn SignerTrait + Send + Sync + 'static>>>,
350 nonce_cache: tokio::sync::RwLock<HashMap<(AccountId, PublicKey), AtomicU64>>,
351 current_public_key: AtomicUsize,
352}
353
354impl Signer {
355 #[instrument(skip(signer))]
357 pub fn new<T: SignerTrait + Send + Sync + 'static>(
358 signer: T,
359 ) -> Result<Arc<Self>, SignerError> {
360 let public_key = signer.get_public_key()?;
361 Ok(Arc::new(Self {
362 pool: tokio::sync::RwLock::new(HashMap::from([(
363 public_key,
364 Box::new(signer) as Box<dyn SignerTrait + Send + Sync + 'static>,
365 )])),
366 nonce_cache: tokio::sync::RwLock::new(HashMap::new()),
367 current_public_key: AtomicUsize::new(0),
368 }))
369 }
370
371 #[instrument(skip(self, signer))]
374 pub async fn add_signer_to_pool<T: SignerTrait + Send + Sync + 'static>(
375 &self,
376 signer: T,
377 ) -> Result<(), SignerError> {
378 let public_key = signer.get_public_key()?;
379 debug!(target: SIGNER_TARGET, "Adding signer to pool");
380 self.pool.write().await.insert(public_key, Box::new(signer));
381 Ok(())
382 }
383
384 #[instrument(skip(self, network), fields(account_id = %account_id))]
388 pub async fn fetch_tx_nonce(
389 &self,
390 account_id: AccountId,
391 public_key: PublicKey,
392 network: &NetworkConfig,
393 ) -> Result<(Nonce, CryptoHash, BlockHeight), SignerError> {
394 debug!(target: SIGNER_TARGET, "Fetching transaction nonce");
395 let nonce_data = crate::account::Account(account_id.clone())
396 .access_key(public_key.clone())
397 .fetch_from(network)
398 .await
399 .map_err(|e| SignerError::FetchNonceError(Box::new(e)))?;
400 let nonce_cache = self.nonce_cache.read().await;
401
402 if let Some(nonce) = nonce_cache.get(&(account_id.clone(), public_key.clone())) {
403 let nonce = nonce.fetch_add(1, Ordering::SeqCst);
404 drop(nonce_cache);
405 trace!(target: SIGNER_TARGET, "Nonce fetched from cache");
406 return Ok((nonce + 1, nonce_data.block_hash, nonce_data.block_height));
407 } else {
408 drop(nonce_cache);
409 }
410
411 let nonce = self
416 .nonce_cache
417 .write()
418 .await
419 .entry((account_id.clone(), public_key))
420 .or_insert_with(|| AtomicU64::new(nonce_data.data.nonce.0 + 1))
421 .fetch_max(nonce_data.data.nonce.0 + 1, Ordering::SeqCst)
422 .max(nonce_data.data.nonce.0 + 1);
423
424 info!(target: SIGNER_TARGET, "Nonce fetched and cached");
425 Ok((nonce, nonce_data.block_hash, nonce_data.block_height))
426 }
427
428 pub fn from_seed_phrase(
430 seed_phrase: &str,
431 password: Option<&str>,
432 ) -> Result<SecretKeySigner, SecretError> {
433 Self::from_seed_phrase_with_hd_path(
434 seed_phrase,
435 BIP32Path::from_str("m/44'/397'/0'").expect("Valid HD path"),
436 password,
437 )
438 }
439
440 pub fn from_secret_key(secret_key: SecretKey) -> SecretKeySigner {
442 SecretKeySigner::new(secret_key)
443 }
444
445 pub fn from_seed_phrase_with_hd_path(
447 seed_phrase: &str,
448 hd_path: BIP32Path,
449 password: Option<&str>,
450 ) -> Result<SecretKeySigner, SecretError> {
451 let secret_key = get_secret_key_from_seed(hd_path, seed_phrase, password)?;
452 Ok(SecretKeySigner::new(secret_key))
453 }
454
455 pub fn from_access_keyfile(path: PathBuf) -> Result<SecretKeySigner, AccessKeyFileError> {
457 let keypair = AccountKeyPair::load_access_key_file(&path)?;
458 debug!(target: SIGNER_TARGET, "Access key file loaded successfully");
459
460 if keypair.public_key != keypair.private_key.public_key() {
461 return Err(AccessKeyFileError::PrivatePublicKeyMismatch);
462 }
463
464 Ok(SecretKeySigner::new(keypair.private_key))
465 }
466
467 #[cfg(feature = "ledger")]
469 pub fn from_ledger() -> ledger::LedgerSigner {
470 ledger::LedgerSigner::new(BIP32Path::from_str("44'/397'/0'/0'/1'").expect("Valid HD path"))
471 }
472
473 #[cfg(feature = "ledger")]
475 pub const fn from_ledger_with_hd_path(hd_path: BIP32Path) -> ledger::LedgerSigner {
476 ledger::LedgerSigner::new(hd_path)
477 }
478
479 #[cfg(feature = "keystore")]
481 pub fn from_keystore(pub_key: PublicKey) -> keystore::KeystoreSigner {
482 keystore::KeystoreSigner::new_with_pubkey(pub_key)
483 }
484
485 #[cfg(feature = "keystore")]
488 pub async fn from_keystore_with_search_for_keys(
489 account_id: AccountId,
490 network: &NetworkConfig,
491 ) -> Result<keystore::KeystoreSigner, crate::errors::KeyStoreError> {
492 keystore::KeystoreSigner::search_for_keys(account_id, network).await
493 }
494
495 #[instrument(skip(self))]
498 pub async fn get_public_key(&self) -> Result<PublicKey, SignerError> {
499 let index = self.current_public_key.fetch_add(1, Ordering::SeqCst);
500 let public_key = {
501 let pool = self.pool.read().await;
502 pool.keys()
503 .nth(index % pool.len())
504 .ok_or(SignerError::PublicKeyIsNotAvailable)?
505 .clone()
506 };
507 debug!(target: SIGNER_TARGET, "Public key retrieved");
508 Ok(public_key)
509 }
510
511 #[instrument(skip(self, tr), fields(signer_id = %tr.signer_id, receiver_id = %tr.receiver_id))]
512 pub async fn sign_meta(
513 &self,
514 tr: PrepopulateTransaction,
515 public_key: PublicKey,
516 nonce: Nonce,
517 block_hash: CryptoHash,
518 max_block_height: BlockHeight,
519 ) -> Result<SignedDelegateAction, MetaSignError> {
520 let signer = self.pool.read().await;
521
522 signer
523 .get(&public_key)
524 .ok_or(SignerError::PublicKeyIsNotAvailable)?
525 .sign_meta(tr, public_key, nonce, block_hash, max_block_height)
526 .await
527 }
528
529 #[instrument(skip(self, tr), fields(signer_id = %tr.signer_id, receiver_id = %tr.receiver_id))]
530 pub async fn sign(
531 &self,
532 tr: PrepopulateTransaction,
533 public_key: PublicKey,
534 nonce: Nonce,
535 block_hash: CryptoHash,
536 ) -> Result<SignedTransaction, SignerError> {
537 let pool = self.pool.read().await;
538
539 pool.get(&public_key)
540 .ok_or(SignerError::PublicKeyIsNotAvailable)?
541 .sign(tr, public_key, nonce, block_hash)
542 .await
543 }
544}
545
546#[instrument(skip(unsigned_transaction, private_key))]
547fn get_signed_delegate_action(
548 mut unsigned_transaction: Transaction,
549 private_key: SecretKey,
550 max_block_height: u64,
551) -> core::result::Result<SignedDelegateAction, MetaSignError> {
552 use near_api_types::signable_message::{SignableMessage, SignableMessageType};
553 let actions: Vec<NonDelegateAction> = unsigned_transaction
554 .take_actions()
555 .into_iter()
556 .map(|action| {
557 NonDelegateAction::try_from(action)
558 .map_err(|_| MetaSignError::DelegateActionIsNotSupported)
559 })
560 .collect::<Result<Vec<_>, _>>()?;
561 let delegate_action = near_api_types::transaction::delegate_action::DelegateAction {
562 sender_id: unsigned_transaction.signer_id().clone(),
563 receiver_id: unsigned_transaction.receiver_id().clone(),
564 actions,
565 nonce: unsigned_transaction.nonce(),
566 max_block_height,
567 public_key: unsigned_transaction.public_key().clone(),
568 };
569
570 let signable = SignableMessage::new(&delegate_action, SignableMessageType::DelegateAction);
572 let bytes = borsh::to_vec(&signable).expect("Failed to serialize");
573 let hash = CryptoHash::hash(&bytes);
574 let signature = private_key.sign(hash.0.as_ref());
575
576 Ok(SignedDelegateAction {
577 delegate_action,
578 signature,
579 })
580}
581
582#[instrument(skip(seed_phrase_hd_path, master_seed_phrase, password))]
586pub fn get_secret_key_from_seed(
587 seed_phrase_hd_path: BIP32Path,
588 master_seed_phrase: &str,
589 password: Option<&str>,
590) -> Result<SecretKey, SecretError> {
591 let master_seed =
592 bip39::Mnemonic::parse(master_seed_phrase)?.to_seed(password.unwrap_or_default());
593 let derived_private_key = slipped10::derive_key_from_path(
594 &master_seed,
595 slipped10::Curve::Ed25519,
596 &seed_phrase_hd_path,
597 )
598 .map_err(|_| SecretError::DeriveKeyInvalidIndex)?;
599
600 let secret_key = SecretKey::ED25519(
601 near_api_types::crypto::secret_key::ED25519SecretKey::from_secret_key(
602 derived_private_key.key,
603 ),
604 );
605
606 Ok(secret_key)
607}
608
609pub fn generate_seed_phrase_custom(
613 word_count: Option<usize>,
614 hd_path: Option<BIP32Path>,
615 passphrase: Option<&str>,
616) -> Result<(String, PublicKey), SecretError> {
617 let mnemonic = bip39::Mnemonic::generate(word_count.unwrap_or(DEFAULT_WORD_COUNT))?;
618 let seed_phrase = mnemonic.words().collect::<Vec<&str>>().join(" ");
619
620 let secret_key = get_secret_key_from_seed(
621 hd_path.unwrap_or_else(|| DEFAULT_HD_PATH.parse().expect("Valid HD path")),
622 &seed_phrase,
623 passphrase,
624 )?;
625 let public_key = secret_key.public_key();
626
627 Ok((seed_phrase, public_key))
628}
629
630pub fn generate_seed_phrase() -> Result<(String, PublicKey), SecretError> {
632 generate_seed_phrase_custom(None, None, None)
633}
634
635pub fn generate_seed_phrase_with_hd_path(
637 hd_path: BIP32Path,
638) -> Result<(String, PublicKey), SecretError> {
639 generate_seed_phrase_custom(None, Some(hd_path), None)
640}
641
642pub fn generate_seed_phrase_with_passphrase(
644 passphrase: &str,
645) -> Result<(String, PublicKey), SecretError> {
646 generate_seed_phrase_custom(None, None, Some(passphrase))
647}
648
649pub fn generate_seed_phrase_with_word_count(
651 word_count: usize,
652) -> Result<(String, PublicKey), SecretError> {
653 generate_seed_phrase_custom(Some(word_count), None, None)
654}
655
656pub fn generate_secret_key() -> Result<SecretKey, SecretError> {
658 let (seed_phrase, _) = generate_seed_phrase()?;
659 let secret_key = get_secret_key_from_seed(
660 DEFAULT_HD_PATH.parse().expect("Valid HD path"),
661 &seed_phrase,
662 None,
663 )?;
664 Ok(secret_key)
665}
666
667pub fn generate_secret_key_from_seed_phrase(seed_phrase: String) -> Result<SecretKey, SecretError> {
669 get_secret_key_from_seed(
670 DEFAULT_HD_PATH.parse().expect("Valid HD path"),
671 &seed_phrase,
672 None,
673 )
674}
675
676#[cfg(test)]
677mod nep_413_tests {
678 use base64::{Engine, prelude::BASE64_STANDARD};
679 use near_api_types::{Signature, crypto::KeyType};
680
681 use crate::SignerTrait;
682
683 use super::{NEP413Payload, Signer};
684
685 fn from_base64(base64: &str) -> Vec<u8> {
686 BASE64_STANDARD.decode(base64).unwrap()
687 }
688
689 #[tokio::test]
692 pub async fn with_callback_url() {
693 let payload: NEP413Payload = NEP413Payload {
694 message: "Hello NEAR!".to_string(),
695 nonce: from_base64("KNV0cOpvJ50D5vfF9pqWom8wo2sliQ4W+Wa7uZ3Uk6Y=")
696 .try_into()
697 .unwrap(),
698 recipient: "example.near".to_string(),
699 callback_url: Some("http://localhost:3000".to_string()),
701 };
702
703 let signer = Signer::from_seed_phrase(
704 "fatal edge jacket cash hard pass gallery fabric whisper size rain biology",
705 None,
706 )
707 .unwrap();
708 let signature = signer
709 .sign_message_nep413(
710 "round-toad.testnet".parse().unwrap(),
711 signer.get_public_key().unwrap(),
712 payload,
713 )
714 .await
715 .unwrap();
716
717 let expected_signature = from_base64(
718 "zzZQ/GwAjrZVrTIFlvmmQbDQHllfzrr8urVWHaRt5cPfcXaCSZo35c5LDpPpTKivR6BxLyb3lcPM0FfCW5lcBQ==",
719 );
720 assert_eq!(
721 signature,
722 Signature::from_parts(KeyType::ED25519, expected_signature.as_slice()).unwrap()
723 );
724 }
725
726 #[tokio::test]
729 pub async fn without_callback_url() {
730 let payload: NEP413Payload = NEP413Payload {
731 message: "Hello NEAR!".to_string(),
732 nonce: from_base64("KNV0cOpvJ50D5vfF9pqWom8wo2sliQ4W+Wa7uZ3Uk6Y=")
733 .try_into()
734 .unwrap(),
735 recipient: "example.near".to_string(),
736 callback_url: None,
737 };
738
739 let signer = Signer::from_seed_phrase(
740 "fatal edge jacket cash hard pass gallery fabric whisper size rain biology",
741 None,
742 )
743 .unwrap();
744 let signature = signer
745 .sign_message_nep413(
746 "round-toad.testnet".parse().unwrap(),
747 signer.get_public_key().unwrap(),
748 payload,
749 )
750 .await
751 .unwrap();
752
753 let expected_signature = from_base64(
754 "NnJgPU1Ql7ccRTITIoOVsIfElmvH1RV7QAT4a9Vh6ShCOnjIzRwxqX54JzoQ/nK02p7VBMI2vJn48rpImIJwAw==",
755 );
756 assert_eq!(
757 signature,
758 Signature::from_parts(KeyType::ED25519, expected_signature.as_slice()).unwrap()
759 );
760 }
761}