1use 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";
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 #[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 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 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 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 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 #[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 #[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 #[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 #[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 crate::errors::KeyStoreError::SecretError(
646 crate::errors::SecretError::DeriveKeyInvalidIndex,
647 )
648 })
649 }
650
651 #[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 #[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 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#[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
784pub 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
805pub fn generate_seed_phrase() -> Result<(String, PublicKey), SecretError> {
807 generate_seed_phrase_custom(None, None, None)
808}
809
810pub 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
817pub 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
824pub 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
831pub 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
842pub 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 #[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: 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 #[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}