blueprint_networking/discovery/utils.rs
1use alloy_primitives::{Address, keccak256};
2use thiserror::Error;
3
4#[derive(Error, Debug)]
5pub enum EcdsaVerifyError {
6 #[error("Bad V")]
7 BadV,
8 #[error("Bad RS")]
9 BadRS,
10 #[error("Bad Signature")]
11 BadSignature,
12}
13
14/// Recovers the public key from a secp256k1 ECDSA signature and message hash
15///
16/// # Arguments
17///
18/// * `sig` - The 65-byte signature (r, s, v)
19/// * `msg` - The 32-byte message hash that was signed
20///
21/// # Returns
22///
23/// The uncompressed 64-byte public key (without the 0x04 prefix) on success
24///
25/// # Errors
26///
27/// Returns `EcdsaVerifyError` if:
28/// * The recovery ID (v) is invalid
29/// * The r,s values are invalid
30/// * The signature is invalid or malformed
31pub fn secp256k1_ecdsa_recover(
32 sig: &[u8; 65],
33 msg: &[u8; 32],
34) -> Result<[u8; 64], EcdsaVerifyError> {
35 let rid = libsecp256k1::RecoveryId::parse(if sig[64] > 26 { sig[64] - 27 } else { sig[64] })
36 .map_err(|_| EcdsaVerifyError::BadV)?;
37 let sig = libsecp256k1::Signature::parse_overflowing_slice(&sig[..64])
38 .map_err(|_| EcdsaVerifyError::BadRS)?;
39 let msg = libsecp256k1::Message::parse(msg);
40 let pubkey =
41 libsecp256k1::recover(&msg, &sig, &rid).map_err(|_| EcdsaVerifyError::BadSignature)?;
42 let mut res = [0u8; 64];
43 res.copy_from_slice(&pubkey.serialize()[1..65]);
44 Ok(res)
45}
46
47/// Derives an Ethereum address from an uncompressed public key.
48///
49/// Takes a 64-byte uncompressed public key (without the 0x04 prefix) and returns the corresponding
50/// 20-byte Ethereum address by:
51/// 1. Computing the Keccak-256 hash of the public key
52/// 2. Taking the last 20 bytes of the hash
53///
54/// # Arguments
55///
56/// * `pubkey` - The 64-byte uncompressed public key bytes
57///
58/// # Returns
59///
60/// The 20-byte Ethereum address derived from the public key
61#[must_use]
62pub fn get_address_from_pubkey(pubkey: &[u8; 64]) -> Address {
63 let hash = keccak256(pubkey);
64 let mut address = [0u8; 20];
65 address.copy_from_slice(&hash[12..32]);
66 Address::from_slice(&address)
67}
68
69/// Derives an Ethereum address from a compressed public key.
70///
71/// Takes a compressed public key (33 bytes with 0x02/0x03 prefix), decompresses it to the full
72/// uncompressed form, and derives the corresponding 20-byte Ethereum address by:
73/// 1. Decompressing the public key to 65 bytes
74/// 2. Computing the Keccak-256 hash of the uncompressed key (minus the 0x04 prefix)
75/// 3. Taking the last 20 bytes of the hash
76///
77/// # Arguments
78///
79/// * `pubkey` - The compressed public key bytes
80///
81/// # Returns
82///
83/// The 20-byte Ethereum address derived from the public key
84///
85/// # Panics
86///
87/// Panics if the compressed public key is invalid and cannot be parsed
88#[must_use]
89pub fn get_address_from_compressed_pubkey(pubkey: &[u8]) -> Address {
90 let mut pk = [0u8; 33];
91 pk[..pubkey.len()].copy_from_slice(pubkey);
92 let pk = libsecp256k1::PublicKey::parse_compressed(&pk).unwrap();
93 let decompressed: [u8; 65] = pk.serialize();
94 let hash = keccak256(&decompressed[1..65]);
95 let mut address = [0u8; 20];
96 address.copy_from_slice(&hash[12..32]);
97 Address::from_slice(&address)
98}