eth_ecdsa_verifier/
lib.rs1use std::error::Error;
2
3use easy_hasher::easy_hasher;
4
5type DynamicResult<T> = Result<T, Box<dyn Error>>;
6
7pub fn validate_ecdsa_signature(
19 public_key: &str,
20 message: &[u8],
21 signature_hex: &[u8],
22) -> DynamicResult<bool> {
23 let public_key_hex = public_key.replace("0x", "");
25
26 let recovered_key = recover_address_from_eth_signature(signature_hex, message)?;
28 let recovered_key_hex = hex::encode(recovered_key);
29
30 Ok(recovered_key_hex.to_lowercase() == public_key_hex.to_lowercase())
32}
33
34pub fn validate_ecdsa_signature_string(
35 public_key: String,
36 message: String,
37 signature_hex: String,
38) -> DynamicResult<bool> {
39 let public_key_hex = public_key.replace("0x", "");
41 let signature_hex = signature_hex.replace("0x", "");
42
43 let signature_bytes = hex::decode(signature_hex)?;
45 let message_bytes = message.as_bytes();
46
47 let recovered_key = recover_address_from_eth_signature(&signature_bytes, message_bytes)?;
49 let recovered_key_hex = hex::encode(recovered_key);
50
51 Ok(recovered_key_hex.to_lowercase() == public_key_hex.to_lowercase())
53}
54
55fn recover_address_from_eth_signature(
65 metamask_signature: &[u8],
66 message: &[u8],
67) -> DynamicResult<Vec<u8>> {
68 let signature_bytes: [u8; 64] = metamask_signature[0..64].try_into()?;
70 let signature_bytes_64 = libsecp256k1::Signature::parse_standard(&signature_bytes)?;
71
72 let recovery_id = metamask_signature[64];
74 let recovery_id_byte = libsecp256k1::RecoveryId::parse_rpc(recovery_id)?;
75
76 let message_bytes: [u8; 32] = hash_eth_message(message)
78 .try_into()
79 .map_err(|e| format!("{e:?}"))?;
80 let message_bytes_32 = libsecp256k1::Message::parse(&message_bytes);
81
82 let public_key =
84 libsecp256k1::recover(&message_bytes_32, &signature_bytes_64, &recovery_id_byte)?;
85
86 get_address_from_public_key(
88 public_key
89 .serialize_compressed()
90 .to_vec()
91 .try_into()
92 .map_err(|e| format!("{e:?}"))?,
93 )
94}
95
96fn hash_eth_message<T: AsRef<[u8]>>(message: T) -> Vec<u8> {
104 const PREFIX: &str = "\x19Ethereum Signed Message:\n";
105 let msg = message.as_ref();
106 let full = [PREFIX.as_bytes(), msg.len().to_string().as_bytes(), msg].concat();
107 easy_hasher::raw_keccak256(full).to_vec()
108}
109
110fn get_address_from_public_key(public_key: [u8; 33]) -> DynamicResult<Vec<u8>> {
119 let pub_key_arr: [u8; 33] = public_key[..].try_into()?;
121 let pub_key = libsecp256k1::PublicKey::parse_compressed(&pub_key_arr)?.serialize();
122
123 let hash = easy_hasher::raw_keccak256(pub_key[1..].to_vec()).to_vec();
125
126 let address_bytes: [u8; 20] = hash[12..]
128 .try_into()
129 .map_err(|_| "Invalid address length")?;
130
131 Ok(address_bytes.into())
132}
133
134#[cfg(test)]
135mod tests {
136 use super::*;
137
138 #[test]
139 fn test_valid_signature() {
140 let message = b"4RvWUp3E9YerY78Kn5UyyEQPTiFs0tIr/mhAeCbwIpY=";
141 let public_key = "0xd1798d6b74ef965d6a60f45e0036f44aed3dfa1b".to_string();
142 let expected_signature = hex::decode(
143 "88bd1f104e132178aea55731be455a5c91b3e15b46f2599e9472d926270d458f4116eea0273fb5dc36238992154afc652aa7c1d91569b596db00146b4e5443fa1b"
144 ).unwrap();
145
146 let is_valid = validate_ecdsa_signature(&public_key, message, &expected_signature).unwrap();
148 assert!(is_valid, "invalid message or signature");
149 }
150
151 #[test]
152 fn test_valid_string_signature() {
153 let message = "4RvWUp3E9YerY78Kn5UyyEQPTiFs0tIr/mhAeCbwIpY=".to_string();
154 let public_key = "0xd1798d6b74ef965d6a60f45e0036f44aed3dfa1b".to_string();
155 let expected_signature = "0x88bd1f104e132178aea55731be455a5c91b3e15b46f2599e9472d926270d458f4116eea0273fb5dc36238992154afc652aa7c1d91569b596db00146b4e5443fa1b".to_string();
156
157 let is_valid =
159 validate_ecdsa_signature_string(public_key, message, expected_signature).unwrap();
160 assert!(is_valid, "invalid message or signature");
161 }
162
163 #[test]
164 fn test_invalid_signature() {
165 let message = b"4RvWUp3E9YerY78Kn5UyyEQPTiFs0tIr/mhAeCbwIpY=";
166 let public_key = "0xd1798d6b74ef965d6a60f45e0036f44aed3dfa1b".to_string();
167 let invalid_signature = hex::decode(
168 "98bd1f104e132178aea55731be455a5c91b3e15b46f2599e9472d926270d458f4116eea0273fb5dc36238992154afc652aa7c1d91569b596db00146b4e5443fa1b"
169 ).unwrap();
170
171 let is_valid = validate_ecdsa_signature(&public_key, message, &invalid_signature).unwrap();
173 assert!(!is_valid);
174 }
175}