openzeppelin_relayer/utils/
secp256k.rs

1use 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
11/// Recover `v` point from a signature and from the message contents
12pub 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        // Generate keypair
47        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        // EIP-191 style of a message
53        let eip_message = eip191_message(b"Hello World");
54
55        // Ethereum-style hash: keccak256 of message
56        let mut hasher = Keccak256::new();
57        hasher.update(eip_message.clone());
58
59        // Sign the message pre-hash
60        let (signature, rec_id) = signing_key.sign_digest_recoverable(hasher).unwrap();
61
62        // Try to recover the public key
63        let recovery_result = recover_public_key(public_key_bytes, &signature, &eip_message);
64
65        // Check that a valid recovery ID (0 or 1) is returned
66        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}