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