alloy_consensus/
crypto.rs

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