use std::error::Error;
use easy_hasher::easy_hasher;
type DynamicResult<T> = Result<T, Box<dyn Error>>;
pub fn validate_ecdsa_signature(
signature_hex: &String,
message: &String,
public_key_hex: &String,
) -> DynamicResult<bool> {
let signature_hex = signature_hex.replace("0x", "");
let public_key_hex = public_key_hex.replace("0x", "");
let recovered_key = recover_address_from_eth_signature(signature_hex, message.clone())?;
let is_equal = recovered_key.to_lowercase() == public_key_hex.to_lowercase();
Ok(is_equal)
}
fn recover_address_from_eth_signature(
metamask_signature: String,
message: String,
) -> DynamicResult<String> {
let metamask_signature = hex::decode(metamask_signature)?;
let signature_bytes: [u8; 64] = metamask_signature[0..64].try_into()?;
let signature_bytes_64 = libsecp256k1::Signature::parse_standard(&signature_bytes)?;
let recovery_id = metamask_signature[64];
let recovery_id_byte = libsecp256k1::RecoveryId::parse_rpc(recovery_id)?;
let message_bytes: [u8; 32] = hash_eth_message(message).try_into().unwrap();
let message_bytes_32 = libsecp256k1::Message::parse(&message_bytes);
let public_key =
libsecp256k1::recover(&message_bytes_32, &signature_bytes_64, &recovery_id_byte)?;
let address = get_address_from_public_key(public_key.serialize_compressed().to_vec()).unwrap();
Ok(address)
}
fn hash_eth_message<T: AsRef<[u8]>>(message: T) -> Vec<u8> {
const PREFIX: &str = "\x19Ethereum Signed Message:\n";
let msg = message.as_ref();
let full = [PREFIX.as_bytes(), msg.len().to_string().as_bytes(), msg].concat();
easy_hasher::raw_keccak256(full).to_vec()
}
fn get_address_from_public_key(public_key: Vec<u8>) -> Result<String, String> {
if public_key.len() != 33 {
return Err("INVALID_PK_LENGTH".to_string());
}
let pub_key_arr: [u8; 33] = public_key[..].try_into().unwrap();
let pub_key = libsecp256k1::PublicKey::parse_compressed(&pub_key_arr)
.map_err(|e| format!("{}", e))?
.serialize();
let keccak256 = easy_hasher::raw_keccak256(pub_key[1..].to_vec());
let keccak256_hex = keccak256.to_hex_string();
let address: String = keccak256_hex[24..].to_string();
Ok(address)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_validate_signature() {
let message =
"4RvWUp3E9YerY78Kn5UyyEQPTiFs0tIr/mhAeCbwIpY=".to_string();
let public_key = "0xd1798d6b74ef965d6a60f45e0036f44aed3dfa1b".to_string();
let expected_signature =
"0x88bd1f104e132178aea55731be455a5c91b3e15b46f2599e9472d926270d458f4116eea0273fb5dc36238992154afc652aa7c1d91569b596db00146b4e5443fa1b".to_string();
let is_valid =
validate_ecdsa_signature(&expected_signature, &message, &public_key).unwrap();
assert!(is_valid, "invalid message or signature");
}
}