reth_primitives_traits/
crypto.rs

1//! Crypto utilities.
2
3use crate::transaction::signature::Signature;
4use alloy_primitives::U256;
5
6/// The order of the secp256k1 curve, divided by two. Signatures that should be checked according
7/// to EIP-2 should have an S value less than or equal to this.
8///
9/// `57896044618658097711785492504343953926418782139537452191302581570759080747168`
10pub const SECP256K1N_HALF: U256 = U256::from_be_bytes([
11    0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
12    0x5D, 0x57, 0x6E, 0x73, 0x57, 0xA4, 0x50, 0x1D, 0xDF, 0xE9, 0x2F, 0x46, 0x68, 0x1B, 0x20, 0xA0,
13]);
14
15/// Secp256k1 utility functions.
16pub mod secp256k1 {
17    use super::*;
18    use revm_primitives::{Address, B256};
19
20    #[cfg(not(feature = "secp256k1"))]
21    use super::impl_k256 as imp;
22    #[cfg(feature = "secp256k1")]
23    use super::impl_secp256k1 as imp;
24
25    use crate::transaction::signed::RecoveryError;
26    pub use imp::{public_key_to_address, sign_message};
27
28    /// Recover signer from message hash, _without ensuring that the signature has a low `s`
29    /// value_.
30    ///
31    /// Using this for signature validation will succeed, even if the signature is malleable or not
32    /// compliant with EIP-2. This is provided for compatibility with old signatures which have
33    /// large `s` values.
34    pub fn recover_signer_unchecked(
35        signature: &Signature,
36        hash: B256,
37    ) -> Result<Address, RecoveryError> {
38        let mut sig: [u8; 65] = [0; 65];
39
40        sig[0..32].copy_from_slice(&signature.r().to_be_bytes::<32>());
41        sig[32..64].copy_from_slice(&signature.s().to_be_bytes::<32>());
42        sig[64] = signature.v() as u8;
43
44        // NOTE: we are removing error from underlying crypto library as it will restrain primitive
45        // errors and we care only if recovery is passing or not.
46        imp::recover_signer_unchecked(&sig, &hash.0).map_err(|_| RecoveryError)
47    }
48
49    /// Recover signer address from message hash. This ensures that the signature S value is
50    /// greater than `secp256k1n / 2`, as specified in
51    /// [EIP-2](https://eips.ethereum.org/EIPS/eip-2).
52    ///
53    /// If the S value is too large, then this will return `None`
54    pub fn recover_signer(signature: &Signature, hash: B256) -> Result<Address, RecoveryError> {
55        if signature.s() > SECP256K1N_HALF {
56            return Err(RecoveryError)
57        }
58        recover_signer_unchecked(signature, hash)
59    }
60}
61
62#[cfg(any(test, feature = "secp256k1"))]
63#[allow(unused, unreachable_pub)]
64mod impl_secp256k1 {
65    use super::*;
66    pub(crate) use ::secp256k1::Error;
67    use ::secp256k1::{
68        ecdsa::{RecoverableSignature, RecoveryId},
69        Message, PublicKey, SecretKey, SECP256K1,
70    };
71    use alloy_primitives::{keccak256, Address, B256, U256};
72
73    /// Recovers the address of the sender using secp256k1 pubkey recovery.
74    ///
75    /// Converts the public key into an ethereum address by hashing the public key with keccak256.
76    ///
77    /// This does not ensure that the `s` value in the signature is low, and _just_ wraps the
78    /// underlying secp256k1 library.
79    pub(crate) fn recover_signer_unchecked(
80        sig: &[u8; 65],
81        msg: &[u8; 32],
82    ) -> Result<Address, Error> {
83        let sig =
84            RecoverableSignature::from_compact(&sig[0..64], RecoveryId::try_from(sig[64] as i32)?)?;
85
86        let public = SECP256K1.recover_ecdsa(&Message::from_digest(*msg), &sig)?;
87        Ok(public_key_to_address(public))
88    }
89
90    /// Signs message with the given secret key.
91    /// Returns the corresponding signature.
92    pub fn sign_message(secret: B256, message: B256) -> Result<Signature, Error> {
93        let sec = SecretKey::from_slice(secret.as_ref())?;
94        let s = SECP256K1.sign_ecdsa_recoverable(&Message::from_digest(message.0), &sec);
95        let (rec_id, data) = s.serialize_compact();
96
97        let signature = Signature::new(
98            U256::try_from_be_slice(&data[..32]).expect("The slice has at most 32 bytes"),
99            U256::try_from_be_slice(&data[32..64]).expect("The slice has at most 32 bytes"),
100            i32::from(rec_id) != 0,
101        );
102        Ok(signature)
103    }
104
105    /// Converts a public key into an ethereum address by hashing the encoded public key with
106    /// keccak256.
107    pub fn public_key_to_address(public: PublicKey) -> Address {
108        // strip out the first byte because that should be the SECP256K1_TAG_PUBKEY_UNCOMPRESSED
109        // tag returned by libsecp's uncompressed pubkey serialization
110        let hash = keccak256(&public.serialize_uncompressed()[1..]);
111        Address::from_slice(&hash[12..])
112    }
113}
114
115#[cfg_attr(feature = "secp256k1", allow(unused, unreachable_pub))]
116mod impl_k256 {
117    use super::*;
118    use alloy_primitives::{keccak256, Address, B256};
119    pub(crate) use k256::ecdsa::Error;
120    use k256::ecdsa::{RecoveryId, SigningKey, VerifyingKey};
121
122    /// Recovers the address of the sender using secp256k1 pubkey recovery.
123    ///
124    /// Converts the public key into an ethereum address by hashing the public key with keccak256.
125    ///
126    /// This does not ensure that the `s` value in the signature is low, and _just_ wraps the
127    /// underlying secp256k1 library.
128    pub(crate) fn recover_signer_unchecked(
129        sig: &[u8; 65],
130        msg: &[u8; 32],
131    ) -> Result<Address, Error> {
132        let mut signature = k256::ecdsa::Signature::from_slice(&sig[0..64])?;
133        let mut recid = sig[64];
134
135        // normalize signature and flip recovery id if needed.
136        if let Some(sig_normalized) = signature.normalize_s() {
137            signature = sig_normalized;
138            recid ^= 1;
139        }
140        let recid = RecoveryId::from_byte(recid).expect("recovery ID is valid");
141
142        // recover key
143        let recovered_key = VerifyingKey::recover_from_prehash(&msg[..], &signature, recid)?;
144        Ok(public_key_to_address(recovered_key))
145    }
146
147    /// Signs message with the given secret key.
148    /// Returns the corresponding signature.
149    pub fn sign_message(secret: B256, message: B256) -> Result<Signature, Error> {
150        let sec = SigningKey::from_slice(secret.as_ref())?;
151        sec.sign_prehash_recoverable(&message.0).map(Into::into)
152    }
153
154    /// Converts a public key into an ethereum address by hashing the encoded public key with
155    /// keccak256.
156    pub fn public_key_to_address(public: VerifyingKey) -> Address {
157        let hash = keccak256(&public.to_encoded_point(/* compress = */ false).as_bytes()[1..]);
158        Address::from_slice(&hash[12..])
159    }
160}
161
162#[cfg(test)]
163mod tests {
164    use alloy_primitives::{keccak256, B256};
165
166    #[cfg(feature = "secp256k1")]
167    #[test]
168    fn sanity_ecrecover_call_secp256k1() {
169        use super::impl_secp256k1::*;
170
171        let (secret, public) = secp256k1::generate_keypair(&mut rand::thread_rng());
172        let signer = public_key_to_address(public);
173
174        let message = b"hello world";
175        let hash = keccak256(message);
176        let signature =
177            sign_message(B256::from_slice(&secret.secret_bytes()[..]), hash).expect("sign message");
178
179        let mut sig: [u8; 65] = [0; 65];
180        sig[0..32].copy_from_slice(&signature.r().to_be_bytes::<32>());
181        sig[32..64].copy_from_slice(&signature.s().to_be_bytes::<32>());
182        sig[64] = signature.v() as u8;
183
184        assert_eq!(recover_signer_unchecked(&sig, &hash), Ok(signer));
185    }
186
187    #[cfg(not(feature = "secp256k1"))]
188    #[test]
189    fn sanity_ecrecover_call_k256() {
190        use super::impl_k256::*;
191
192        let secret = k256::ecdsa::SigningKey::random(&mut rand::thread_rng());
193        let public = *secret.verifying_key();
194        let signer = public_key_to_address(public);
195
196        let message = b"hello world";
197        let hash = keccak256(message);
198        let signature =
199            sign_message(B256::from_slice(&secret.to_bytes()[..]), hash).expect("sign message");
200
201        let mut sig: [u8; 65] = [0; 65];
202        sig[0..32].copy_from_slice(&signature.r().to_be_bytes::<32>());
203        sig[32..64].copy_from_slice(&signature.s().to_be_bytes::<32>());
204        sig[64] = signature.v() as u8;
205
206        assert_eq!(recover_signer_unchecked(&sig, &hash).ok(), Some(signer));
207    }
208
209    #[test]
210    fn sanity_secp256k1_k256_compat() {
211        use super::{impl_k256, impl_secp256k1};
212
213        let (secp256k1_secret, secp256k1_public) =
214            secp256k1::generate_keypair(&mut rand::thread_rng());
215        let k256_secret = k256::ecdsa::SigningKey::from_slice(&secp256k1_secret.secret_bytes())
216            .expect("k256 secret");
217        let k256_public = *k256_secret.verifying_key();
218
219        let secp256k1_signer = impl_secp256k1::public_key_to_address(secp256k1_public);
220        let k256_signer = impl_k256::public_key_to_address(k256_public);
221        assert_eq!(secp256k1_signer, k256_signer);
222
223        let message = b"hello world";
224        let hash = keccak256(message);
225
226        let secp256k1_signature = impl_secp256k1::sign_message(
227            B256::from_slice(&secp256k1_secret.secret_bytes()[..]),
228            hash,
229        )
230        .expect("secp256k1 sign");
231        let k256_signature =
232            impl_k256::sign_message(B256::from_slice(&k256_secret.to_bytes()[..]), hash)
233                .expect("k256 sign");
234        assert_eq!(secp256k1_signature, k256_signature);
235
236        let mut sig: [u8; 65] = [0; 65];
237
238        sig[0..32].copy_from_slice(&secp256k1_signature.r().to_be_bytes::<32>());
239        sig[32..64].copy_from_slice(&secp256k1_signature.s().to_be_bytes::<32>());
240        sig[64] = secp256k1_signature.v() as u8;
241        let secp256k1_recovered =
242            impl_secp256k1::recover_signer_unchecked(&sig, &hash).expect("secp256k1 recover");
243        assert_eq!(secp256k1_recovered, secp256k1_signer);
244
245        sig[0..32].copy_from_slice(&k256_signature.r().to_be_bytes::<32>());
246        sig[32..64].copy_from_slice(&k256_signature.s().to_be_bytes::<32>());
247        sig[64] = k256_signature.v() as u8;
248        let k256_recovered =
249            impl_k256::recover_signer_unchecked(&sig, &hash).expect("k256 recover");
250        assert_eq!(k256_recovered, k256_signer);
251
252        assert_eq!(secp256k1_recovered, k256_recovered);
253    }
254}