1use 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";
145pub const DEFAULT_HD_PATH: &str = "m/44'/397'/0'";
147pub const DEFAULT_LEDGER_HD_PATH: &str = "44'/397'/0'/0'/1'";
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
180impl NEP413Payload {
181 const MESSAGE_PREFIX: u32 = (1u32 << 31) + 413;
182
183 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 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 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#[async_trait::async_trait]
295pub trait SignerTrait {
296 #[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 #[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 #[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 async fn get_secret_key(
380 &self,
381 signer_id: &AccountId,
382 public_key: PublicKey,
383 ) -> Result<SecretKey, SignerError>;
384
385 fn get_public_key(&self) -> Result<PublicKey, PublicKeyError>;
389}
390
391pub 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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 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 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 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 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 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 #[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 #[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 #[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 #[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 crate::errors::KeyStoreError::SecretError(
657 crate::errors::SecretError::DeriveKeyInvalidIndex,
658 )
659 })
660 }
661
662 #[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 #[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 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#[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
795pub 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
816pub fn generate_seed_phrase() -> Result<(String, PublicKey), SecretError> {
818 generate_seed_phrase_custom(None, None, None)
819}
820
821pub 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
828pub 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
835pub 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
842pub 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
853pub 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 #[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: 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 #[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}