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
11pub fn recover_public_key(pk: &[u8], sig: &Signature, bytes: &[u8]) -> Result<u8, Secp256k1Error> {
13 let mut hasher = Keccak256::new();
14 hasher.update(bytes);
15 for v in 0..2 {
16 let rec_id = match RecoveryId::try_from(v) {
17 Ok(id) => id,
18 Err(_) => continue,
19 };
20
21 let recovered_key = match VerifyingKey::recover_from_digest(hasher.clone(), sig, rec_id) {
22 Ok(key) => key.to_encoded_point(false).as_bytes().to_vec(),
23 Err(_) => {
24 continue;
25 }
26 };
27 if recovered_key[1..] == pk[..] {
28 return Ok(v);
29 }
30 }
31
32 Err(Secp256k1Error::RecoveryError(
33 "No valid v point was found".to_string(),
34 ))
35}
36
37#[cfg(test)]
38mod tests {
39 use super::*;
40
41 use alloy::primitives::utils::eip191_message;
42 use k256::{ecdsa::SigningKey, elliptic_curve::rand_core::OsRng};
43
44 #[test]
45 fn test_recover_public_key() {
46 let signing_key = SigningKey::random(&mut OsRng);
48 let verifying_key = signing_key.verifying_key();
49 let public_key_bytes = &verifying_key.to_encoded_point(false).as_bytes().to_vec()[1..];
50 println!("Pub key length: {}", public_key_bytes.len());
51
52 let eip_message = eip191_message(b"Hello World");
54
55 let mut hasher = Keccak256::new();
57 hasher.update(eip_message.clone());
58
59 let (signature, rec_id) = signing_key.sign_digest_recoverable(hasher).unwrap();
61
62 let recovery_result = recover_public_key(public_key_bytes, &signature, &eip_message);
64
65 match recovery_result {
67 Ok(v) => {
68 assert!(v == 0 || v == 1, "Recovery ID should be 0 or 1, got {}", v);
69 assert_eq!(rec_id.to_byte(), v, "Recovery ID should match")
70 }
71 Err(e) => panic!("Failed to recover public key: {:?}", e),
72 }
73 }
74}