use std::{
collections::HashMap,
path::{Path, PathBuf},
sync::{
Arc,
atomic::{AtomicUsize, Ordering},
},
};
use near_api_types::{
AccountId, BlockHeight, CryptoHash, Nonce, PublicKey, Reference, SecretKey, Signature,
transaction::{
PrepopulateTransaction, SignedTransaction, Transaction, TransactionV0,
delegate_action::{NonDelegateAction, SignedDelegateAction},
},
};
use borsh::{BorshDeserialize, BorshSerialize};
use near_slip10::BIP32Path;
use serde::{Deserialize, Serialize};
use tracing::{debug, instrument, warn};
use crate::{
config::NetworkConfig,
errors::{AccessKeyFileError, MetaSignError, PublicKeyError, SecretError, SignerError},
};
use secret_key::SecretKeySigner;
#[cfg(feature = "keystore")]
pub mod keystore;
#[cfg(feature = "ledger")]
pub mod ledger;
pub mod secret_key;
const SIGNER_TARGET: &str = "near_api::signer";
pub const DEFAULT_HD_PATH: &str = "m/44'/397'/0'";
pub const DEFAULT_LEDGER_HD_PATH: &str = "44'/397'/0'/0'/1'";
pub const DEFAULT_WORD_COUNT: usize = 12;
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct AccountKeyPair {
pub public_key: PublicKey,
pub private_key: SecretKey,
}
impl AccountKeyPair {
fn load_access_key_file(path: &Path) -> Result<Self, AccessKeyFileError> {
let data = std::fs::read_to_string(path)?;
Ok(serde_json::from_str(&data)?)
}
}
#[derive(Debug, Clone, Serialize, Deserialize, BorshDeserialize, BorshSerialize)]
pub struct NEP413Payload {
pub message: String,
pub nonce: [u8; 32],
pub recipient: String,
pub callback_url: Option<String>,
}
impl NEP413Payload {
const MESSAGE_PREFIX: u32 = (1u32 << 31) + 413;
pub fn compute_hash(&self) -> Result<CryptoHash, std::io::Error> {
let mut bytes = Self::MESSAGE_PREFIX.to_le_bytes().to_vec();
borsh::to_writer(&mut bytes, self)?;
Ok(CryptoHash::hash(&bytes))
}
pub fn extract_timestamp_from_nonce(&self) -> u64 {
let mut timestamp: [u8; 8] = [0; 8];
timestamp.copy_from_slice(&self.nonce[..8]);
u64::from_be_bytes(timestamp)
}
pub async fn verify(
&self,
account_id: &AccountId,
public_key: PublicKey,
signature: &Signature,
network: &NetworkConfig,
) -> Result<bool, SignerError> {
use near_api_types::AccessKeyPermission;
let hash = self.compute_hash()?;
if !signature.verify(hash, public_key) {
return Ok(false);
}
let access_key = crate::Account(account_id.clone())
.access_key(public_key)
.fetch_from(network)
.await;
match access_key {
Ok(data) => Ok(data.data.permission == AccessKeyPermission::FullAccess),
Err(_) => Ok(false),
}
}
}
#[cfg(feature = "ledger")]
impl From<NEP413Payload> for near_ledger::NEP413Payload {
fn from(payload: NEP413Payload) -> Self {
Self {
message: payload.message,
nonce: payload.nonce,
recipient: payload.recipient,
callback_url: payload.callback_url,
}
}
}
#[async_trait::async_trait]
pub trait SignerTrait {
#[instrument(skip(self, transaction), fields(signer_id = %transaction.signer_id, receiver_id = %transaction.receiver_id))]
async fn sign_meta(
&self,
transaction: PrepopulateTransaction,
public_key: PublicKey,
nonce: Nonce,
block_hash: CryptoHash,
max_block_height: BlockHeight,
) -> Result<SignedDelegateAction, MetaSignError> {
let signer_secret_key = self
.get_secret_key(&transaction.signer_id, public_key)
.await?;
let unsigned_transaction = Transaction::V0(TransactionV0 {
signer_id: transaction.signer_id.clone(),
public_key,
nonce,
receiver_id: transaction.receiver_id,
block_hash,
actions: transaction.actions,
});
get_signed_delegate_action(unsigned_transaction, signer_secret_key, max_block_height)
}
#[instrument(skip(self, transaction), fields(signer_id = %transaction.signer_id, receiver_id = %transaction.receiver_id))]
async fn sign(
&self,
transaction: PrepopulateTransaction,
public_key: PublicKey,
nonce: Nonce,
block_hash: CryptoHash,
) -> Result<SignedTransaction, SignerError> {
let signer_secret_key = self
.get_secret_key(&transaction.signer_id, public_key)
.await?;
let unsigned_transaction = Transaction::V0(TransactionV0 {
signer_id: transaction.signer_id.clone(),
public_key,
nonce,
receiver_id: transaction.receiver_id,
block_hash,
actions: transaction.actions,
});
let signature = signer_secret_key.sign(unsigned_transaction.get_hash());
Ok(SignedTransaction::new(signature, unsigned_transaction))
}
#[instrument(skip(self), fields(signer_id = %signer_id, receiver_id = %payload.recipient, message = %payload.message))]
async fn sign_message_nep413(
&self,
signer_id: AccountId,
public_key: PublicKey,
payload: &NEP413Payload,
) -> Result<Signature, SignerError> {
let hash = payload.compute_hash()?;
let secret = self.get_secret_key(&signer_id, public_key).await?;
Ok(secret.sign(hash))
}
async fn get_secret_key(
&self,
signer_id: &AccountId,
public_key: PublicKey,
) -> Result<SecretKey, SignerError>;
fn get_public_key(&self) -> Result<PublicKey, PublicKeyError>;
}
pub struct Signer {
pool: tokio::sync::RwLock<HashMap<PublicKey, Box<dyn SignerTrait + Send + Sync + 'static>>>,
nonce_cache: futures::lock::Mutex<HashMap<(AccountId, PublicKey), u64>>,
current_public_key: AtomicUsize,
}
impl Signer {
#[instrument(skip(signer))]
pub fn new<T: SignerTrait + Send + Sync + 'static>(
signer: T,
) -> Result<Arc<Self>, PublicKeyError> {
let public_key = signer.get_public_key()?;
Ok(Arc::new(Self {
pool: tokio::sync::RwLock::new(HashMap::from([(
public_key,
Box::new(signer) as Box<dyn SignerTrait + Send + Sync + 'static>,
)])),
nonce_cache: futures::lock::Mutex::new(HashMap::new()),
current_public_key: AtomicUsize::new(0),
}))
}
#[instrument(skip(self, signer))]
pub async fn add_signer_to_pool<T: SignerTrait + Send + Sync + 'static>(
&self,
signer: T,
) -> Result<(), PublicKeyError> {
let public_key = signer.get_public_key()?;
debug!(target: SIGNER_TARGET, "Adding signer to pool");
self.pool.write().await.insert(public_key, Box::new(signer));
Ok(())
}
#[instrument(skip(self, secret_key))]
pub async fn add_secret_key_to_pool(
&self,
secret_key: SecretKey,
) -> Result<(), PublicKeyError> {
let signer = SecretKeySigner::new(secret_key);
self.add_signer_to_pool(signer).await
}
#[instrument(skip(self, seed_phrase, password))]
pub async fn add_seed_phrase_to_pool(
&self,
seed_phrase: &str,
password: Option<&str>,
) -> Result<(), SignerError> {
let secret_key = get_secret_key_from_seed(
DEFAULT_HD_PATH.parse().expect("Valid HD path"),
seed_phrase,
password,
)
.map_err(|_| SignerError::SecretKeyIsNotAvailable)?;
let signer = SecretKeySigner::new(secret_key);
Ok(self.add_signer_to_pool(signer).await?)
}
#[instrument(skip(self, seed_phrase, password))]
pub async fn add_seed_phrase_to_pool_with_hd_path(
&self,
seed_phrase: &str,
hd_path: BIP32Path,
password: Option<&str>,
) -> Result<(), SignerError> {
let secret_key = get_secret_key_from_seed(hd_path, seed_phrase, password)
.map_err(|_| SignerError::SecretKeyIsNotAvailable)?;
let signer = SecretKeySigner::new(secret_key);
Ok(self.add_signer_to_pool(signer).await?)
}
#[instrument(skip(self))]
pub async fn add_access_keyfile_to_pool(
&self,
path: PathBuf,
) -> Result<(), AccessKeyFileError> {
let keypair = AccountKeyPair::load_access_key_file(&path)?;
if keypair.public_key != keypair.private_key.public_key() {
return Err(AccessKeyFileError::PrivatePublicKeyMismatch);
}
let signer = SecretKeySigner::new(keypair.private_key);
Ok(self.add_signer_to_pool(signer).await?)
}
#[cfg(feature = "ledger")]
#[instrument(skip(self))]
pub async fn add_ledger_to_pool(&self) -> Result<(), PublicKeyError> {
let signer =
ledger::LedgerSigner::new(DEFAULT_LEDGER_HD_PATH.parse().expect("Valid HD path"));
self.add_signer_to_pool(signer).await
}
#[cfg(feature = "ledger")]
#[instrument(skip(self))]
pub async fn add_ledger_to_pool_with_hd_path(
&self,
hd_path: BIP32Path,
) -> Result<(), PublicKeyError> {
let signer = ledger::LedgerSigner::new(hd_path);
self.add_signer_to_pool(signer).await
}
#[cfg(feature = "keystore")]
#[instrument(skip(self))]
pub async fn add_keystore_to_pool(&self, pub_key: PublicKey) -> Result<(), PublicKeyError> {
let signer = keystore::KeystoreSigner::new_with_pubkey(pub_key);
self.add_signer_to_pool(signer).await
}
#[allow(clippy::significant_drop_tightening)]
#[instrument(skip(self, network), fields(account_id = %account_id))]
pub async fn fetch_tx_nonce(
&self,
account_id: AccountId,
public_key: PublicKey,
network: &NetworkConfig,
) -> Result<(Nonce, CryptoHash, BlockHeight), SignerError> {
debug!(target: SIGNER_TARGET, "Fetching transaction nonce");
let nonce_data = crate::account::Account(account_id.clone())
.access_key(public_key)
.at(Reference::Final)
.fetch_from(network)
.await
.map_err(|e| SignerError::FetchNonceError(Box::new(e)))?;
let nonce = {
let mut nonce_cache = self.nonce_cache.lock().await;
let nonce = nonce_cache.entry((account_id, public_key)).or_default();
*nonce = (*nonce).max(nonce_data.data.nonce.0) + 1;
*nonce
};
Ok((nonce, nonce_data.block_hash, nonce_data.block_height))
}
pub fn from_seed_phrase(
seed_phrase: &str,
password: Option<&str>,
) -> Result<Arc<Self>, SecretError> {
let signer = Self::from_seed_phrase_with_hd_path(
seed_phrase,
DEFAULT_HD_PATH.parse().expect("Valid HD path"),
password,
)?;
Ok(signer)
}
pub fn from_secret_key(secret_key: SecretKey) -> Result<Arc<Self>, PublicKeyError> {
let inner = SecretKeySigner::new(secret_key);
Self::new(inner)
}
pub fn from_seed_phrase_with_hd_path(
seed_phrase: &str,
hd_path: BIP32Path,
password: Option<&str>,
) -> Result<Arc<Self>, SecretError> {
let secret_key = get_secret_key_from_seed(hd_path, seed_phrase, password)?;
let inner = SecretKeySigner::new(secret_key);
Self::new(inner).map_err(|_| SecretError::DeriveKeyInvalidIndex)
}
pub fn from_access_keyfile(path: PathBuf) -> Result<Arc<Self>, AccessKeyFileError> {
let keypair = AccountKeyPair::load_access_key_file(&path)?;
debug!(target: SIGNER_TARGET, "Access key file loaded successfully");
if keypair.public_key != keypair.private_key.public_key() {
return Err(AccessKeyFileError::PrivatePublicKeyMismatch);
}
let inner = SecretKeySigner::new(keypair.private_key);
Ok(Self::new(inner)?)
}
#[cfg(feature = "ledger")]
pub fn from_ledger() -> Result<Arc<Self>, PublicKeyError> {
let inner =
ledger::LedgerSigner::new(DEFAULT_LEDGER_HD_PATH.parse().expect("Valid HD path"));
Self::new(inner)
}
#[cfg(feature = "ledger")]
pub fn from_ledger_with_hd_path(hd_path: BIP32Path) -> Result<Arc<Self>, PublicKeyError> {
let inner = ledger::LedgerSigner::new(hd_path);
Self::new(inner)
}
#[cfg(feature = "keystore")]
pub fn from_keystore(pub_key: PublicKey) -> Result<Arc<Self>, PublicKeyError> {
let inner = keystore::KeystoreSigner::new_with_pubkey(pub_key);
Self::new(inner)
}
#[cfg(feature = "keystore")]
pub async fn from_keystore_with_search_for_keys(
account_id: AccountId,
network: &NetworkConfig,
) -> Result<Arc<Self>, crate::errors::KeyStoreError> {
let inner = keystore::KeystoreSigner::search_for_keys(account_id, network).await?;
Self::new(inner).map_err(|_| {
crate::errors::KeyStoreError::SecretError(
crate::errors::SecretError::DeriveKeyInvalidIndex,
)
})
}
#[instrument(skip(self))]
pub async fn get_public_key(&self) -> Result<PublicKey, PublicKeyError> {
let index = self.current_public_key.fetch_add(1, Ordering::SeqCst);
let public_key = {
let pool = self.pool.read().await;
*pool
.keys()
.nth(index % pool.len())
.ok_or(PublicKeyError::PublicKeyIsNotAvailable)?
};
debug!(target: SIGNER_TARGET, "Public key retrieved");
Ok(public_key)
}
#[instrument(skip(self, transaction), fields(signer_id = %transaction.signer_id, receiver_id = %transaction.receiver_id))]
pub async fn sign_meta(
&self,
transaction: PrepopulateTransaction,
public_key: PublicKey,
nonce: Nonce,
block_hash: CryptoHash,
max_block_height: BlockHeight,
) -> Result<SignedDelegateAction, MetaSignError> {
let signer = self.pool.read().await;
signer
.get(&public_key)
.ok_or(PublicKeyError::PublicKeyIsNotAvailable)
.map_err(SignerError::from)?
.sign_meta(transaction, public_key, nonce, block_hash, max_block_height)
.await
}
#[instrument(skip(self, transaction), fields(signer_id = %transaction.signer_id, receiver_id = %transaction.receiver_id))]
pub async fn sign(
&self,
transaction: PrepopulateTransaction,
public_key: PublicKey,
nonce: Nonce,
block_hash: CryptoHash,
) -> Result<SignedTransaction, SignerError> {
let pool = self.pool.read().await;
pool.get(&public_key)
.ok_or(PublicKeyError::PublicKeyIsNotAvailable)?
.sign(transaction, public_key, nonce, block_hash)
.await
}
#[instrument(skip(self), fields(signer_id = %signer_id, receiver_id = %payload.recipient, message = %payload.message))]
pub async fn sign_message_nep413(
&self,
signer_id: AccountId,
public_key: PublicKey,
payload: &NEP413Payload,
) -> Result<Signature, SignerError> {
let pool = self.pool.read().await;
pool.get(&public_key)
.ok_or(PublicKeyError::PublicKeyIsNotAvailable)?
.sign_message_nep413(signer_id, public_key, payload)
.await
}
}
#[instrument(skip(unsigned_transaction, private_key))]
fn get_signed_delegate_action(
mut unsigned_transaction: Transaction,
private_key: SecretKey,
max_block_height: u64,
) -> core::result::Result<SignedDelegateAction, MetaSignError> {
use near_api_types::signable_message::{SignableMessage, SignableMessageType};
let actions: Vec<NonDelegateAction> = unsigned_transaction
.take_actions()
.into_iter()
.map(|action| {
NonDelegateAction::try_from(action)
.map_err(|_| MetaSignError::DelegateActionIsNotSupported)
})
.collect::<Result<Vec<_>, _>>()?;
let delegate_action = near_api_types::transaction::delegate_action::DelegateAction {
sender_id: unsigned_transaction.signer_id().clone(),
receiver_id: unsigned_transaction.receiver_id().clone(),
actions,
nonce: unsigned_transaction.nonce(),
max_block_height,
public_key: unsigned_transaction.public_key(),
};
let signable = SignableMessage::new(&delegate_action, SignableMessageType::DelegateAction);
let bytes = borsh::to_vec(&signable).expect("Failed to serialize");
let hash = CryptoHash::hash(&bytes);
let signature = private_key.sign(hash);
Ok(SignedDelegateAction {
delegate_action,
signature,
})
}
#[instrument(skip(seed_phrase_hd_path, master_seed_phrase, password))]
pub fn get_secret_key_from_seed(
seed_phrase_hd_path: BIP32Path,
master_seed_phrase: &str,
password: Option<&str>,
) -> Result<SecretKey, SecretError> {
let master_seed =
bip39::Mnemonic::parse(master_seed_phrase)?.to_seed(password.unwrap_or_default());
let derived_private_key = near_slip10::derive_key_from_path(
&master_seed,
near_slip10::Curve::Ed25519,
&seed_phrase_hd_path,
)
.map_err(|_| SecretError::DeriveKeyInvalidIndex)?;
let secret_key = SecretKey::ED25519(
near_api_types::crypto::secret_key::ED25519SecretKey::from_secret_key(
derived_private_key.key,
),
);
Ok(secret_key)
}
pub fn generate_seed_phrase_custom(
word_count: Option<usize>,
hd_path: Option<BIP32Path>,
passphrase: Option<&str>,
) -> Result<(String, PublicKey), SecretError> {
let mnemonic = bip39::Mnemonic::generate(word_count.unwrap_or(DEFAULT_WORD_COUNT))?;
let seed_phrase = mnemonic.words().collect::<Vec<&str>>().join(" ");
let secret_key = get_secret_key_from_seed(
hd_path.unwrap_or_else(|| DEFAULT_HD_PATH.parse().expect("Valid HD path")),
&seed_phrase,
passphrase,
)?;
let public_key = secret_key.public_key();
Ok((seed_phrase, public_key))
}
pub fn generate_seed_phrase() -> Result<(String, PublicKey), SecretError> {
generate_seed_phrase_custom(None, None, None)
}
pub fn generate_seed_phrase_with_hd_path(
hd_path: BIP32Path,
) -> Result<(String, PublicKey), SecretError> {
generate_seed_phrase_custom(None, Some(hd_path), None)
}
pub fn generate_seed_phrase_with_passphrase(
passphrase: &str,
) -> Result<(String, PublicKey), SecretError> {
generate_seed_phrase_custom(None, None, Some(passphrase))
}
pub fn generate_seed_phrase_with_word_count(
word_count: usize,
) -> Result<(String, PublicKey), SecretError> {
generate_seed_phrase_custom(Some(word_count), None, None)
}
pub fn generate_secret_key() -> Result<SecretKey, SecretError> {
let (seed_phrase, _) = generate_seed_phrase()?;
let secret_key = get_secret_key_from_seed(
DEFAULT_HD_PATH.parse().expect("Valid HD path"),
&seed_phrase,
None,
)?;
Ok(secret_key)
}
pub fn generate_secret_key_from_seed_phrase(seed_phrase: String) -> Result<SecretKey, SecretError> {
get_secret_key_from_seed(
DEFAULT_HD_PATH.parse().expect("Valid HD path"),
&seed_phrase,
None,
)
}
#[cfg(test)]
mod nep_413_tests {
use base64::{Engine, prelude::BASE64_STANDARD};
use near_api_types::{
AccessKeyPermission, NearToken, Signature, crypto::KeyType,
transaction::actions::FunctionCallPermission,
};
use near_sandbox::config::{DEFAULT_GENESIS_ACCOUNT, DEFAULT_GENESIS_ACCOUNT_PRIVATE_KEY};
use testresult::TestResult;
use crate::{Account, NetworkConfig, signer::generate_secret_key};
use super::{NEP413Payload, Signer};
fn from_base64(base64: &str) -> Vec<u8> {
BASE64_STANDARD.decode(base64).unwrap()
}
#[tokio::test]
pub async fn with_callback_url() {
let payload: NEP413Payload = NEP413Payload {
message: "Hello NEAR!".to_string(),
nonce: from_base64("KNV0cOpvJ50D5vfF9pqWom8wo2sliQ4W+Wa7uZ3Uk6Y=")
.try_into()
.unwrap(),
recipient: "example.near".to_string(),
callback_url: Some("http://localhost:3000".to_string()),
};
let signer = Signer::from_seed_phrase(
"fatal edge jacket cash hard pass gallery fabric whisper size rain biology",
None,
)
.unwrap();
let public_key = signer.get_public_key().await.unwrap();
let signature = signer
.sign_message_nep413("round-toad.testnet".parse().unwrap(), public_key, &payload)
.await
.unwrap();
let expected_signature = from_base64(
"zzZQ/GwAjrZVrTIFlvmmQbDQHllfzrr8urVWHaRt5cPfcXaCSZo35c5LDpPpTKivR6BxLyb3lcPM0FfCW5lcBQ==",
);
assert_eq!(
signature,
Signature::from_parts(KeyType::ED25519, expected_signature.as_slice()).unwrap()
);
}
#[tokio::test]
pub async fn without_callback_url() {
let payload: NEP413Payload = NEP413Payload {
message: "Hello NEAR!".to_string(),
nonce: from_base64("KNV0cOpvJ50D5vfF9pqWom8wo2sliQ4W+Wa7uZ3Uk6Y=")
.try_into()
.unwrap(),
recipient: "example.near".to_string(),
callback_url: None,
};
let signer = Signer::from_seed_phrase(
"fatal edge jacket cash hard pass gallery fabric whisper size rain biology",
None,
)
.unwrap();
let public_key = signer.get_public_key().await.unwrap();
let signature = signer
.sign_message_nep413("round-toad.testnet".parse().unwrap(), public_key, &payload)
.await
.unwrap();
let expected_signature = from_base64(
"NnJgPU1Ql7ccRTITIoOVsIfElmvH1RV7QAT4a9Vh6ShCOnjIzRwxqX54JzoQ/nK02p7VBMI2vJn48rpImIJwAw==",
);
assert_eq!(
signature,
Signature::from_parts(KeyType::ED25519, expected_signature.as_slice()).unwrap()
);
}
#[tokio::test]
pub async fn test_verify_nep413_payload() -> TestResult {
let sandbox = near_sandbox::Sandbox::start_sandbox().await?;
let network = NetworkConfig::from_rpc_url("sandbox", sandbox.rpc_addr.parse()?);
let signer = Signer::from_secret_key(DEFAULT_GENESIS_ACCOUNT_PRIVATE_KEY.parse()?)?;
let public_key = signer.get_public_key().await?;
let payload: NEP413Payload = NEP413Payload {
message: "Hello NEAR!".to_string(),
nonce: from_base64("KNV0cOpvJ50D5vfF9pqWom8wo2sliQ4W+Wa7uZ3Uk6Y=")
.try_into()
.unwrap(),
recipient: DEFAULT_GENESIS_ACCOUNT.to_string(),
callback_url: None,
};
let signature = signer
.sign_message_nep413(DEFAULT_GENESIS_ACCOUNT.into(), public_key, &payload)
.await?;
let result = payload
.verify(
&DEFAULT_GENESIS_ACCOUNT.into(),
public_key,
&signature,
&network,
)
.await?;
assert!(result);
Ok(())
}
#[tokio::test]
pub async fn verification_fails_without_public_key() -> TestResult {
let sandbox = near_sandbox::Sandbox::start_sandbox().await?;
let network = NetworkConfig::from_rpc_url("sandbox", sandbox.rpc_addr.parse()?);
let secret_key = generate_secret_key()?;
let signer = Signer::from_secret_key(secret_key)?;
let public_key = signer.get_public_key().await?;
let payload: NEP413Payload = NEP413Payload {
message: "Hello NEAR!".to_string(),
nonce: from_base64("KNV0cOpvJ50D5vfF9pqWom8wo2sliQ4W+Wa7uZ3Uk6Y=")
.try_into()
.unwrap(),
recipient: DEFAULT_GENESIS_ACCOUNT.to_string(),
callback_url: None,
};
let signature = signer
.sign_message_nep413(DEFAULT_GENESIS_ACCOUNT.into(), public_key, &payload)
.await?;
let result = payload
.verify(
&DEFAULT_GENESIS_ACCOUNT.into(),
public_key,
&signature,
&network,
)
.await?;
assert!(!result);
Ok(())
}
#[tokio::test]
pub async fn verification_fails_with_function_call_access_key() -> TestResult {
let sandbox = near_sandbox::Sandbox::start_sandbox().await?;
let network = NetworkConfig::from_rpc_url("sandbox", sandbox.rpc_addr.parse()?);
let secret_key = generate_secret_key()?;
let msg_signer = Signer::from_secret_key(secret_key)?;
let tx_signer = Signer::from_secret_key(DEFAULT_GENESIS_ACCOUNT_PRIVATE_KEY.parse()?)?;
let public_key = msg_signer.get_public_key().await?;
Account(DEFAULT_GENESIS_ACCOUNT.into())
.add_key(
AccessKeyPermission::FunctionCall(FunctionCallPermission {
allowance: Some(NearToken::from_near(1)),
receiver_id: "test".to_string(),
method_names: vec!["test".to_string()],
}),
public_key,
)
.with_signer(tx_signer.clone())
.send_to(&network)
.await?
.assert_success();
let payload: NEP413Payload = NEP413Payload {
message: "Hello NEAR!".to_string(),
nonce: from_base64("KNV0cOpvJ50D5vfF9pqWom8wo2sliQ4W+Wa7uZ3Uk6Y=")
.try_into()
.unwrap(),
recipient: DEFAULT_GENESIS_ACCOUNT.to_string(),
callback_url: None,
};
let signature = msg_signer
.sign_message_nep413(DEFAULT_GENESIS_ACCOUNT.into(), public_key, &payload)
.await?;
let result = payload
.verify(
&DEFAULT_GENESIS_ACCOUNT.into(),
public_key,
&signature,
&network,
)
.await?;
assert!(!result);
Ok(())
}
}