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"))]
63mod impl_secp256k1 {
64    use super::*;
65    pub(crate) use ::secp256k1::Error;
66    use ::secp256k1::{
67        ecdsa::{RecoverableSignature, RecoveryId},
68        Message, PublicKey, SecretKey, SECP256K1,
69    };
70    use alloy_primitives::{keccak256, Address, B256, U256};
71
72    /// Recovers the address of the sender using secp256k1 pubkey recovery.
73    ///
74    /// Converts the public key into an ethereum address by hashing the public key with keccak256.
75    ///
76    /// This does not ensure that the `s` value in the signature is low, and _just_ wraps the
77    /// underlying secp256k1 library.
78    pub(crate) fn recover_signer_unchecked(
79        sig: &[u8; 65],
80        msg: &[u8; 32],
81    ) -> Result<Address, Error> {
82        let sig =
83            RecoverableSignature::from_compact(&sig[0..64], RecoveryId::try_from(sig[64] as i32)?)?;
84
85        let public = SECP256K1.recover_ecdsa(&Message::from_digest(*msg), &sig)?;
86        Ok(public_key_to_address(public))
87    }
88
89    /// Signs message with the given secret key.
90    /// Returns the corresponding signature.
91    pub fn sign_message(secret: B256, message: B256) -> Result<Signature, Error> {
92        let sec = SecretKey::from_slice(secret.as_ref())?;
93        let s = SECP256K1.sign_ecdsa_recoverable(&Message::from_digest(message.0), &sec);
94        let (rec_id, data) = s.serialize_compact();
95
96        let signature = Signature::new(
97            U256::try_from_be_slice(&data[..32]).expect("The slice has at most 32 bytes"),
98            U256::try_from_be_slice(&data[32..64]).expect("The slice has at most 32 bytes"),
99            i32::from(rec_id) != 0,
100        );
101        Ok(signature)
102    }
103
104    /// Converts a public key into an ethereum address by hashing the encoded public key with
105    /// keccak256.
106    pub fn public_key_to_address(public: PublicKey) -> Address {
107        // strip out the first byte because that should be the SECP256K1_TAG_PUBKEY_UNCOMPRESSED
108        // tag returned by libsecp's uncompressed pubkey serialization
109        let hash = keccak256(&public.serialize_uncompressed()[1..]);
110        Address::from_slice(&hash[12..])
111    }
112}
113
114#[cfg_attr(feature = "secp256k1", allow(unused, unreachable_pub))]
115mod impl_k256 {
116    use super::*;
117    use alloy_primitives::{keccak256, Address, B256};
118    pub(crate) use k256::ecdsa::Error;
119    use k256::ecdsa::{RecoveryId, SigningKey, VerifyingKey};
120
121    /// Recovers the address of the sender using secp256k1 pubkey recovery.
122    ///
123    /// Converts the public key into an ethereum address by hashing the public key with keccak256.
124    ///
125    /// This does not ensure that the `s` value in the signature is low, and _just_ wraps the
126    /// underlying secp256k1 library.
127    pub(crate) fn recover_signer_unchecked(
128        sig: &[u8; 65],
129        msg: &[u8; 32],
130    ) -> Result<Address, Error> {
131        let mut signature = k256::ecdsa::Signature::from_slice(&sig[0..64])?;
132        let mut recid = sig[64];
133
134        // normalize signature and flip recovery id if needed.
135        if let Some(sig_normalized) = signature.normalize_s() {
136            signature = sig_normalized;
137            recid ^= 1;
138        }
139        let recid = RecoveryId::from_byte(recid).expect("recovery ID is valid");
140
141        // recover key
142        let recovered_key = VerifyingKey::recover_from_prehash(&msg[..], &signature, recid)?;
143        Ok(public_key_to_address(recovered_key))
144    }
145
146    /// Signs message with the given secret key.
147    /// Returns the corresponding signature.
148    pub fn sign_message(secret: B256, message: B256) -> Result<Signature, Error> {
149        let sec = SigningKey::from_slice(secret.as_ref())?;
150        sec.sign_prehash_recoverable(&message.0).map(Into::into)
151    }
152
153    /// Converts a public key into an ethereum address by hashing the encoded public key with
154    /// keccak256.
155    pub fn public_key_to_address(public: VerifyingKey) -> Address {
156        let hash = keccak256(&public.to_encoded_point(/* compress = */ false).as_bytes()[1..]);
157        Address::from_slice(&hash[12..])
158    }
159}
160
161#[cfg(test)]
162mod tests {
163    use alloy_primitives::{keccak256, B256};
164
165    #[cfg(feature = "secp256k1")]
166    #[test]
167    fn sanity_ecrecover_call_secp256k1() {
168        use super::impl_secp256k1::*;
169
170        let (secret, public) = secp256k1::generate_keypair(&mut rand_08::thread_rng());
171        let signer = public_key_to_address(public);
172
173        let message = b"hello world";
174        let hash = keccak256(message);
175        let signature =
176            sign_message(B256::from_slice(&secret.secret_bytes()[..]), hash).expect("sign message");
177
178        let mut sig: [u8; 65] = [0; 65];
179        sig[0..32].copy_from_slice(&signature.r().to_be_bytes::<32>());
180        sig[32..64].copy_from_slice(&signature.s().to_be_bytes::<32>());
181        sig[64] = signature.v() as u8;
182
183        assert_eq!(recover_signer_unchecked(&sig, &hash), Ok(signer));
184    }
185
186    #[cfg(not(feature = "secp256k1"))]
187    #[test]
188    fn sanity_ecrecover_call_k256() {
189        use super::impl_k256::*;
190
191        let secret = k256::ecdsa::SigningKey::random(&mut rand::thread_rng());
192        let public = *secret.verifying_key();
193        let signer = public_key_to_address(public);
194
195        let message = b"hello world";
196        let hash = keccak256(message);
197        let signature =
198            sign_message(B256::from_slice(&secret.to_bytes()[..]), hash).expect("sign message");
199
200        let mut sig: [u8; 65] = [0; 65];
201        sig[0..32].copy_from_slice(&signature.r().to_be_bytes::<32>());
202        sig[32..64].copy_from_slice(&signature.s().to_be_bytes::<32>());
203        sig[64] = signature.v() as u8;
204
205        assert_eq!(recover_signer_unchecked(&sig, &hash).ok(), Some(signer));
206    }
207
208    #[test]
209    fn sanity_secp256k1_k256_compat() {
210        use super::{impl_k256, impl_secp256k1};
211
212        let (secp256k1_secret, secp256k1_public) =
213            secp256k1::generate_keypair(&mut rand_08::thread_rng());
214        let k256_secret = k256::ecdsa::SigningKey::from_slice(&secp256k1_secret.secret_bytes())
215            .expect("k256 secret");
216        let k256_public = *k256_secret.verifying_key();
217
218        let secp256k1_signer = impl_secp256k1::public_key_to_address(secp256k1_public);
219        let k256_signer = impl_k256::public_key_to_address(k256_public);
220        assert_eq!(secp256k1_signer, k256_signer);
221
222        let message = b"hello world";
223        let hash = keccak256(message);
224
225        let secp256k1_signature = impl_secp256k1::sign_message(
226            B256::from_slice(&secp256k1_secret.secret_bytes()[..]),
227            hash,
228        )
229        .expect("secp256k1 sign");
230        let k256_signature =
231            impl_k256::sign_message(B256::from_slice(&k256_secret.to_bytes()[..]), hash)
232                .expect("k256 sign");
233        assert_eq!(secp256k1_signature, k256_signature);
234
235        let mut sig: [u8; 65] = [0; 65];
236
237        sig[0..32].copy_from_slice(&secp256k1_signature.r().to_be_bytes::<32>());
238        sig[32..64].copy_from_slice(&secp256k1_signature.s().to_be_bytes::<32>());
239        sig[64] = secp256k1_signature.v() as u8;
240        let secp256k1_recovered =
241            impl_secp256k1::recover_signer_unchecked(&sig, &hash).expect("secp256k1 recover");
242        assert_eq!(secp256k1_recovered, secp256k1_signer);
243
244        sig[0..32].copy_from_slice(&k256_signature.r().to_be_bytes::<32>());
245        sig[32..64].copy_from_slice(&k256_signature.s().to_be_bytes::<32>());
246        sig[64] = k256_signature.v() as u8;
247        let k256_recovered =
248            impl_k256::recover_signer_unchecked(&sig, &hash).expect("k256 recover");
249        assert_eq!(k256_recovered, k256_signer);
250
251        assert_eq!(secp256k1_recovered, k256_recovered);
252    }
253}