#[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()
}
}