use near_sdk::serde::de::DeserializeOwned; use near_sdk::serde::{Deserialize, Serialize}; use near_sdk::{env, near}; use p256::ecdsa::signature::{SignerMut, Verifier}; use p256::ecdsa::{SigningKey, VerifyingKey}; use schemars::JsonSchema; use super::with_raw_string::WithRawString; use super::{ verify_key, CheckSignatureError, ExecutionContextProvider, HashForSigning, Key, MessageWithSignature, MessageWithValidSignature, Payload, }; use data::{AuthenticatorData, ClientDataJson}; use signature::Signature; pub mod data; pub mod signature; fn sig_base( authenticator_data: &AuthenticatorData, client_data_json: &WithRawString, ) -> Vec { [ &**authenticator_data, &env::sha256(client_data_json.raw.as_bytes()), ] .concat() } verify_key!(crate::encoding::p256::PublicKey); impl Key> for VerifyKey { fn check_signature( &self, mws: &super::MessageWithSignature>, ) -> Result<(), CheckSignatureError> { if mws.message.hash_for_signing() != mws.auxiliary.client_data_json.parsed.challenge.as_slice() { return Err(CheckSignatureError::Other( "Computed hash does not match clientDataJSON.challenge".into(), )); } let payload_prehash = sig_base( &mws.auxiliary.authenticator_data, &mws.auxiliary.client_data_json, ); if VerifyingKey::from(*self.0) .verify(&payload_prehash, &*mws.signature) .is_ok() { Ok(()) } else { Err(CheckSignatureError::InvalidSignature) } } } #[derive(Clone, Debug)] #[near(serializers = [json])] #[serde(bound = "T: DeserializeOwned", deny_unknown_fields)] pub struct Message(pub WithRawString>); impl super::SignableMessage for Message { type Key = VerifyKey; type Signature = Signature; type Auxiliary = PasskeySignatureData; } #[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] #[serde(crate = "near_sdk::serde")] pub struct PasskeySignatureData { pub authenticator_data: AuthenticatorData, pub client_data_json: WithRawString, } impl Message { pub fn from_parsed(payload: Payload) -> Self where T: near_sdk::serde::Serialize, { Self(WithRawString::from_parsed(payload)) } pub fn sign( self, key: &p256::SecretKey, authenticator_data: AuthenticatorData, client_data_json: ClientDataJson, ) -> MessageWithSignature { let client_data_json = WithRawString::from_parsed(client_data_json); let signature = Signature( SigningKey::from(key).sign(&sig_base(&authenticator_data, &client_data_json)), ); MessageWithSignature { message: self, signature, auxiliary: PasskeySignatureData { authenticator_data, client_data_json, }, } } } impl HashForSigning for Message { const MAGIC_NUMBER: &'static [u8] = b"\x19UAccount Signed Message:\n"; fn content_bytes(&self) -> Vec { self.0.raw.as_bytes().to_vec() } } impl

ExecutionContextProvider for MessageWithValidSignature> { type Payload = P; fn payload(self) -> Payload { self.0.message.0.parsed } fn origin(&self) -> Option<&str> { Some(self.0.auxiliary.client_data_json.parsed.origin.as_str()) } } #[cfg(test)] mod tests { use crate::PayloadExecutionParameters; use super::*; fn signer() -> p256::SecretKey { p256::SecretKey::from_bytes( &[ 70, 48, 167, 111, 21, 39, 70, 169, 116, 174, 182, 153, 87, 171, 62, 105, 44, 200, 187, 37, 7, 63, 69, 153, 92, 134, 167, 206, 188, 7, 118, 35, ] .into(), ) .unwrap() } fn message() -> Message { Message::from_parsed(Payload::new( PayloadExecutionParameters::new_empty("account_id".parse().unwrap()), "payload".to_string(), )) } fn authenticator_data() -> AuthenticatorData { AuthenticatorData([0u8; 32].into()) } fn client_data_json(challenge: data::Challenge) -> ClientDataJson { ClientDataJson { r#type: "type".to_string(), challenge, origin: "origin".to_string(), cross_origin: None, top_origin: None, } } #[test] fn check_signature() { let signer = signer(); let message = message(); let challenge = data::Challenge(message.hash_for_signing()); let mws = message.sign(&signer, authenticator_data(), client_data_json(challenge)); let verify_key = VerifyKey(signer.public_key().into()); verify_key.verify_signature(mws).unwrap(); } #[test] #[should_panic = "Computed hash does not match clientDataJSON.challenge"] fn check_signature_fail_hash_mismatch() { let signer = signer(); let message = message(); let challenge = data::Challenge([0x99_u8; 32]); let mws = message.sign(&signer, authenticator_data(), client_data_json(challenge)); let verify_key = VerifyKey(signer.public_key().into()); verify_key.verify_signature(mws).unwrap(); } }