1use alloc::boxed::Box;
4use alloy_primitives::U256;
5
6#[cfg(any(feature = "secp256k1", feature = "k256"))]
7use alloy_primitives::Signature;
8
9#[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 pub fn new() -> Self {
20 Self::default()
21 }
22
23 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
38pub 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#[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 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 imp::recover_signer_unchecked(&sig, &hash.0).map_err(|_| RecoveryError::new())
79 }
80
81 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 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 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 pub fn public_key_to_address(public: PublicKey) -> Address {
138 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 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 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 let recovered_key = VerifyingKey::recover_from_prehash(&msg[..], &signature, recid)?;
176 Ok(public_key_to_address(recovered_key))
177 }
178
179 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 pub fn public_key_to_address(public: VerifyingKey) -> Address {
189 let hash = keccak256(&public.to_encoded_point(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}