openzeppelin_relayer/utils/
secp256k.rs1use k256::ecdsa::{RecoveryId, Signature, VerifyingKey};
2use serde::Serialize;
3use sha3::{Digest, Keccak256};
4
5#[derive(Debug, Clone, thiserror::Error, Serialize)]
6pub enum Secp256k1Error {
7 #[error("Secp256k1 recovery error: {0}")]
8 RecoveryError(String),
9}
10
11fn recover_v_internal<F>(pk: &[u8], _sig: &Signature, recover_fn: F) -> Result<u8, Secp256k1Error>
16where
17 F: Fn(u8, RecoveryId) -> Option<Vec<u8>>,
18{
19 for v in 0..2 {
20 let rec_id = match RecoveryId::try_from(v) {
21 Ok(id) => id,
22 Err(_) => continue,
23 };
24
25 if let Some(recovered_key) = recover_fn(v, rec_id) {
26 if recovered_key.len() != 65 || recovered_key[0] != 0x04 {
28 continue;
29 }
30
31 if recovered_key[1..] == pk[..] {
33 return Ok(v);
34 }
35 }
36 }
37
38 Err(Secp256k1Error::RecoveryError(format!(
39 "Failed to recover v value: no valid recovery ID found. \
40 This usually indicates a signature/public key mismatch. \
41 Public key length: {} bytes, tried recovery IDs: 0, 1",
42 pk.len()
43 )))
44}
45
46pub fn recover_public_key(pk: &[u8], sig: &Signature, bytes: &[u8]) -> Result<u8, Secp256k1Error> {
48 if pk.len() != 64 {
50 return Err(Secp256k1Error::RecoveryError(format!(
51 "Invalid public key length: expected 64 bytes, got {}",
52 pk.len()
53 )));
54 }
55
56 let mut hasher = Keccak256::new();
57 hasher.update(bytes);
58
59 recover_v_internal(pk, sig, |_, rec_id| {
60 VerifyingKey::recover_from_digest(hasher.clone(), sig, rec_id)
61 .ok()
62 .map(|key| key.to_encoded_point(false).as_bytes().to_vec())
63 })
64}
65
66pub fn recover_public_key_from_hash(
69 pk: &[u8],
70 sig: &Signature,
71 hash: &[u8; 32],
72) -> Result<u8, Secp256k1Error> {
73 if pk.len() != 64 {
75 return Err(Secp256k1Error::RecoveryError(format!(
76 "Invalid public key length: expected 64 bytes, got {}",
77 pk.len()
78 )));
79 }
80
81 recover_v_internal(pk, sig, |_, rec_id| {
82 VerifyingKey::recover_from_prehash(hash, sig, rec_id)
83 .ok()
84 .map(|key| key.to_encoded_point(false).as_bytes().to_vec())
85 })
86}
87
88#[cfg(test)]
89mod tests {
90 use super::*;
91
92 use alloy::primitives::utils::eip191_message;
93 use k256::{ecdsa::SigningKey, elliptic_curve::rand_core::OsRng};
94
95 #[test]
96 fn test_recover_public_key() {
97 let signing_key = SigningKey::random(&mut OsRng);
99 let verifying_key = signing_key.verifying_key();
100 let public_key_vec = verifying_key.to_encoded_point(false).as_bytes().to_vec();
101 let public_key_bytes = &public_key_vec[1..];
102 println!("Pub key length: {}", public_key_bytes.len());
103
104 let eip_message = eip191_message(b"Hello World");
106
107 let mut hasher = Keccak256::new();
109 hasher.update(eip_message.clone());
110
111 let (signature, rec_id) = signing_key.sign_digest_recoverable(hasher).unwrap();
113
114 let recovery_result = recover_public_key(public_key_bytes, &signature, &eip_message);
116
117 match recovery_result {
119 Ok(v) => {
120 assert!(v == 0 || v == 1, "Recovery ID should be 0 or 1, got {}", v);
121 assert_eq!(rec_id.to_byte(), v, "Recovery ID should match")
122 }
123 Err(e) => panic!("Failed to recover public key: {:?}", e),
124 }
125 }
126
127 #[test]
128 fn test_recover_public_key_from_hash() {
129 let signing_key = SigningKey::random(&mut OsRng);
131 let verifying_key = signing_key.verifying_key();
132 let public_key_vec = verifying_key.to_encoded_point(false).as_bytes().to_vec();
133 let public_key_bytes = &public_key_vec[1..];
134
135 let message = b"Test message for EIP-712";
137 let mut hasher = Keccak256::new();
138 hasher.update(message);
139 let hash: [u8; 32] = hasher.finalize().into();
140
141 let (signature, rec_id) = signing_key.sign_prehash_recoverable(&hash).unwrap();
143
144 let recovery_result = recover_public_key_from_hash(public_key_bytes, &signature, &hash);
146
147 match recovery_result {
149 Ok(v) => {
150 assert!(v == 0 || v == 1, "Recovery ID should be 0 or 1, got {}", v);
151 assert_eq!(rec_id.to_byte(), v, "Recovery ID should match")
152 }
153 Err(e) => panic!("Failed to recover public key from hash: {:?}", e),
154 }
155 }
156
157 #[test]
158 fn test_recover_public_key_from_hash_deterministic() {
159 let signing_key = SigningKey::random(&mut OsRng);
161 let verifying_key = signing_key.verifying_key();
162 let public_key_vec = verifying_key.to_encoded_point(false).as_bytes().to_vec();
163 let public_key_bytes = &public_key_vec[1..];
164
165 let hash: [u8; 32] = [0x42; 32];
167
168 let (sig1, _) = signing_key.sign_prehash_recoverable(&hash).unwrap();
170 let (sig2, _) = signing_key.sign_prehash_recoverable(&hash).unwrap();
171
172 let v1 = recover_public_key_from_hash(public_key_bytes, &sig1, &hash).unwrap();
174 let v2 = recover_public_key_from_hash(public_key_bytes, &sig2, &hash).unwrap();
175
176 assert_eq!(v1, v2, "V values should be consistent for the same hash");
178 }
179}