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