verity_ic/crypto/
ethereum.rs1use crate::crypto::{
2 ecdsa::{self, derive_pk},
3 ethereum,
4};
5use candid::Principal;
6use easy_hasher::easy_hasher;
7
8use super::{config::Config, remove_leading, string_to_vec_u8, vec_u8_to_string};
9
10pub fn hash_eth_message<T: AsRef<[u8]>>(message: T) -> Vec<u8> {
12 const PREFIX: &str = "\x19Ethereum Signed Message:\n";
13
14 let message = message.as_ref();
15 let len = message.len();
16 let len_string = len.to_string();
17
18 let mut eth_message = Vec::with_capacity(PREFIX.len() + len_string.len() + len);
19 eth_message.extend_from_slice(PREFIX.as_bytes());
20 eth_message.extend_from_slice(len_string.as_bytes());
21 eth_message.extend_from_slice(message);
22
23 easy_hasher::raw_keccak256(eth_message)
24 .to_vec()
25 .try_into()
26 .unwrap()
27}
28
29pub fn recover_address_from_eth_signature(
31 metamask_signature: String,
32 message: String,
33) -> Result<String, String> {
34 let metamask_signature = string_to_vec_u8(&metamask_signature);
35 if metamask_signature.len() != 65 {
36 return Err("INVALID_ETH_SIGNATURE".to_string());
37 }
38
39 let signature_bytes: [u8; 64] = metamask_signature[0..64].try_into().unwrap();
40 let signature_bytes_64 = libsecp256k1::Signature::parse_standard(&signature_bytes).unwrap();
41
42 let recovery_id = metamask_signature[64];
43 let recovery_id_byte = libsecp256k1::RecoveryId::parse_rpc(recovery_id).unwrap();
44
45 let message_bytes: [u8; 32] = hash_eth_message(message).try_into().unwrap();
46 let message_bytes_32 = libsecp256k1::Message::parse(&message_bytes);
47
48 let public_key =
49 libsecp256k1::recover(&message_bytes_32, &signature_bytes_64, &recovery_id_byte).unwrap();
50
51 let address = get_address_from_public_key(public_key.serialize_compressed().to_vec()).unwrap();
52
53 Ok(address)
54}
55
56pub fn get_signature(signature: &Vec<u8>, message: &Vec<u8>, public_key: &Vec<u8>) -> Vec<u8> {
58 let r = remove_leading(&signature[..32].to_vec(), 0);
59 let s = remove_leading(&signature[32..].to_vec(), 0);
60 let recovery_id = get_recovery_id(message, signature, public_key).unwrap();
61
62 let v = if recovery_id == 0 {
63 hex::decode(format!("{:X}", 27)).unwrap()
64 } else {
65 hex::decode(format!("{:X}", 28)).unwrap()
66 };
67
68 let eth_sig = [&r[..], &s[..], &v[..]].concat();
69
70 eth_sig
71}
72
73pub fn get_recovery_id(
76 message: &Vec<u8>,
77 signature: &Vec<u8>,
78 public_key: &Vec<u8>,
79) -> Result<u8, String> {
80 if signature.len() != 64 {
81 return Err("INVALID_SIGNATURE".to_string());
82 }
83 if message.len() != 32 {
84 return Err("INVALID_MESSAGE".to_string());
85 }
86 if public_key.len() != 33 {
87 return Err("INVALID_PUBLIC_KEY".to_string());
88 }
89
90 for i in 0..3 {
91 let recovery_id = libsecp256k1::RecoveryId::parse_rpc(27 + i).unwrap();
92
93 let signature_bytes: [u8; 64] = signature[..].try_into().unwrap();
94 let signature_bytes_64 = libsecp256k1::Signature::parse_standard(&signature_bytes).unwrap();
95
96 let message_bytes: [u8; 32] = message[..].try_into().unwrap();
97 let message_bytes_32 = libsecp256k1::Message::parse(&message_bytes);
98
99 let key =
100 libsecp256k1::recover(&message_bytes_32, &signature_bytes_64, &recovery_id).unwrap();
101 if key.serialize_compressed() == public_key[..] {
102 return Ok(i as u8);
103 }
104 }
105 return Err("DISCRIMINATOR_NOT_FOUND".to_string());
106}
107
108pub fn get_address_from_public_key(public_key: Vec<u8>) -> Result<String, String> {
110 if public_key.len() != 33 {
111 return Err("INVALID_PK_LENGTH".to_string());
112 }
113
114 let pub_key_arr: [u8; 33] = public_key[..].try_into().unwrap();
115 let pub_key = libsecp256k1::PublicKey::parse_compressed(&pub_key_arr)
116 .map_err(|e| format!("{}", e))?
117 .serialize();
118
119 let keccak256 = easy_hasher::raw_keccak256(pub_key[1..].to_vec());
120 let keccak256_hex = keccak256.to_hex_string();
121 let address: String = "".to_owned() + &keccak256_hex[24..];
122
123 Ok(address)
124}
125
126pub async fn sign_message(
128 message: &Vec<u8>,
129 config: &Config,
130) -> Result<ecdsa::SignatureReply, String> {
131 let message_hash = ethereum::hash_eth_message(&message);
133
134 let public_key = derive_pk(config).await;
136 let request = ecdsa::SignWithECDSA {
137 message_hash: message_hash.clone(),
138 derivation_path: vec![],
139 key_id: config.key.to_key_id(),
140 };
141
142 let (response,): (ecdsa::SignWithECDSAReply,) = ic_cdk::api::call::call_with_payment(
143 Principal::management_canister(),
144 "sign_with_ecdsa",
145 (request,),
146 config.sign_cycles,
147 )
148 .await
149 .map_err(|e| format!("SIGN_WITH_ECDSA_FAILED {}", e.1))?;
150
151 let full_signature = ethereum::get_signature(&response.signature, &message_hash, &public_key);
152 Ok(ecdsa::SignatureReply {
153 signature_hex: vec_u8_to_string(&full_signature),
154 })
155}
156
157#[cfg(test)]
158mod tests {
159 use super::*;
160
161 #[test]
162 fn test_recover_address_from_eth_signature() {
163 let message = "hello".to_string();
164 let metamask_signature =
165 "0xc49581525ffdb136f2cbf6c2c113bce4b80c5147ac72038aef2ef5393dc3c3a8077f253152d6821396db30f8e4230cf931a0820d90fec40634af3a913e6aff5c1b".to_string();
166 let expected_address = "5c8e3a7c16fa5cdde9f74751d6b2395176f05c55";
167
168 let recovered_address =
169 recover_address_from_eth_signature(metamask_signature, message).unwrap();
170 assert_eq!(recovered_address, expected_address);
171 }
172}