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