use alloy::sol_types::{Eip712Domain, SolStruct}; use near_sdk::{ near, serde::{self, de::DeserializeOwned, Serialize}, }; use super::{ verify_key, with_raw_string::WithRawString, CheckSignatureError, ExecutionContextProvider, Key, MessageWithValidSignature, Payload, SignableMessage, }; verify_key!(crate::encoding::ethereum::Address); #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] #[near(serializers = [json])] #[serde(bound = "T: DeserializeOwned")] pub struct Message(pub WithRawString>); impl Message { pub fn from_parsed(payload: Payload) -> Self where T: Serialize, { Self(WithRawString::from_parsed(payload)) } } impl SignableMessage for Message { type Key = VerifyKey; type Signature = crate::encoding::ethereum::Signature; type Auxiliary = (); } impl Key> for VerifyKey { fn check_signature( &self, mws: &super::MessageWithSignature>, ) -> Result<(), CheckSignatureError> { let calculated_domain = Eip712Domain::from(mws.message.0.parsed.parameters()); let prehash = mws.message.eip712_prehash(&calculated_domain); let recovered_address = mws .signature .0 .recover_address_from_prehash(&prehash) .map_err(CheckSignatureError::other)?; (recovered_address == self.0 .0) .then_some(()) .ok_or(CheckSignatureError::InvalidSignature) } } impl Message { /// # Errors /// /// - If serialization of `T` to bytes fails. pub fn eip712_prehash(&self, domain: &Eip712Domain) -> alloy::primitives::FixedBytes<32> { super::solidity::Payload { payload: self.0.raw.clone(), } .eip712_signing_hash(domain) } /// # Errors /// /// - Signing errors #[cfg(any(test, feature = "signing"))] pub fn sign( self, key: &alloy::signers::local::PrivateKeySigner, ) -> Result, alloy::signers::Error> { use alloy::signers::SignerSync; let domain = Eip712Domain::from(self.0.parsed.parameters()); let signature = key.sign_hash_sync(&self.eip712_prehash(&domain))?; Ok(super::MessageWithSignature { message: self, signature: signature.into(), auxiliary: (), }) } } impl ExecutionContextProvider for MessageWithValidSignature> { type Payload = T; fn payload(self) -> Payload { self.0.message.0.parsed } fn origin(&self) -> Option<&str> { None } } #[cfg(test)] mod tests { use std::str::FromStr; use alloy::signers::local::PrivateKeySigner; use near_sdk::{serde_json, AccountId}; use crate::{ authentication::payload::Payload, transaction::{Action, Transaction}, PayloadExecutionParameters, NEAR_TESTNET_CHAIN_ID, }; use super::*; #[test] fn serialization() { let m = Message::from_parsed(Payload::new( PayloadExecutionParameters::builder(NEAR_TESTNET_CHAIN_ID) .zero() .verifying_contract(AccountId::from_str("account_id").unwrap()) .build_salt(), "hello, world".to_string(), )); let json = serde_json::to_string(&m).unwrap(); eprintln!("{json:?}"); let parsed: Message = serde_json::from_str(&json).unwrap(); assert_eq!(m, parsed); } fn signer() -> PrivateKeySigner { PrivateKeySigner::from_bytes(&[0x55_u8; 32].into()).unwrap() } fn signer2() -> PrivateKeySigner { PrivateKeySigner::from_bytes(&[0x66_u8; 32].into()).unwrap() } fn message() -> Message> { Message::from_parsed(Payload::new( PayloadExecutionParameters::new_empty("account_id".parse().unwrap()), vec![Transaction { receiver_id: "receiver".parse().unwrap(), actions: vec![Action::CreateAccount].into_boxed_slice(), }] .into_boxed_slice(), )) } #[test] fn sign_message() { let signer = signer(); let message = message(); let mws = message.sign(&signer).unwrap(); let verify_key = VerifyKey(signer.address().into()); verify_key.verify_signature(mws).unwrap(); } #[test] #[should_panic = "InvalidSignature"] fn sign_message_fail_signer() { let signer = signer(); let message = message(); let mws = message.sign(&signer).unwrap(); let verify_key = VerifyKey(signer2().address().into()); verify_key.verify_signature(mws).unwrap(); } #[test] #[should_panic = "InvalidSignature"] fn sign_message_fail_message() { let signer = signer(); let message = message(); let mut mws = message.sign(&signer).unwrap(); let verify_key = VerifyKey(signer.address().into()); let mut payload_parsed = mws.message.0.parsed; payload_parsed.payload_mut()[0].receiver_id = "different".parse().unwrap(); mws.message.0 = WithRawString::from_parsed(payload_parsed); verify_key.verify_signature(mws).unwrap(); } #[test] #[should_panic = "InvalidSignature"] fn sign_message_fail_domain() { let signer = signer(); let message = message(); let mut mws = message.sign(&signer).unwrap(); let verify_key = VerifyKey(signer.address().into()); let mut parameters = mws.message.0.parsed.parameters(); parameters.name = Some("different".to_string()); mws.message.0 = WithRawString::from_parsed(Payload::new(parameters, mws.message.0.parsed.payload())); verify_key.verify_signature(mws).unwrap(); } }