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