saa-auth 0.27.4

Custom credentials built on top of curves and their verification logic for smart account auth
Documentation

#[cfg(any(feature = "cosmwasm", feature = "native"))]
use {
    super::utils::{get_recovery_param, hash_eth_personal}, 
    saa_common::{ensure, CredentialAddress}
};
use saa_common::{
    CredentialId, CredentialName, CredentialInfo, 
    AuthError, Binary, String, ToString, Verifiable, Identifiable
};


#[saa_schema::saa_type]
pub struct EthPersonalSign {
    pub message:   Binary,
    pub signature: Binary,
    pub signer:    String,
}


impl Identifiable for EthPersonalSign {
    fn cred_id(&self) -> CredentialId {
        self.signer.to_lowercase()
    }
    
    fn name(&self) -> CredentialName {
        CredentialName::EthPersonalSign
    }
    
}


impl Verifiable for EthPersonalSign {
    
    fn message(&self) -> std::borrow::Cow<'_, [u8]> {
        std::borrow::Cow::Borrowed(self.message.as_slice())
    }

    fn validate(&self) -> Result<(), AuthError> {
        if !self.signer.starts_with("0x") {
            return Err(AuthError::MissingData("Ethereum `signer` address must start with 0x".to_string()));
        }
        if self.signature.len() < 65 {
            return Err(AuthError::MissingData("Signature must be at least 65 bytes".to_string()));
        }
        let signer_bytes = hex::decode(&self.signer[2..])
            .map_err(|e| AuthError::generic(e.to_string()))?;

        if signer_bytes.len() != 20 {
            return Err(AuthError::MissingData("Signer must be 20 bytes".to_string()));
        }
        Ok(())
    }

    #[cfg(any(feature = "native", feature = "cosmwasm"))]
    fn verify(&self,
        #[cfg(feature = "cosmwasm")]
        deps: saa_common::wasm::Deps
    ) -> Result<CredentialInfo, AuthError> {
        let address = self.cred_id();
        let signature = &self.signature.to_vec();
        #[cfg(all(feature = "native", not(feature = "cosmwasm")))]
        let key_data = saa_crypto::secp256k1_recover_pubkey(
            &hash_eth_personal(&self.message), 
            &signature[..64], 
            get_recovery_param(signature[64])?
        )?;
        #[cfg(feature = "cosmwasm")]
        let key_data = deps.api.secp256k1_recover_pubkey(
            &hash_eth_personal(&self.message), 
            &signature[..64], 
            get_recovery_param(signature[64])?
        )?;
        let hash = saa_crypto::hashes::keccak256(&key_data[1..]);

        let addr_bytes = hex::decode(&address[2..])
            .map_err(|e| AuthError::generic(e.to_string()))?;

        ensure!(addr_bytes == hash[12..], AuthError::RecoveryMismatch);
        Ok(CredentialInfo {
            hrp: None,
            extension: None,
            address: Some(CredentialAddress::Evm(address)),
            name: CredentialName::EthPersonalSign,
        })
    }


}

#[cfg(feature = "replay")]
impl saa_crypto::ReplayProtection for EthPersonalSign {
    fn hash_message(&self, bytes: &[u8]) -> Vec<u8> {
        crate::ethereum::utils::hash_eth_personal(&bytes).to_vec()
    }
}