secp256k1/ecdsa/
recovery.rs

1// SPDX-License-Identifier: CC0-1.0
2
3//! Provides a signing function that allows recovering the public key from the
4//! signature.
5//!
6
7use core::ptr;
8
9use self::super_ffi::CPtr;
10use super::ffi as super_ffi;
11use crate::ecdsa::Signature;
12use crate::ffi::recovery as ffi;
13use crate::{key, Error, Message, Secp256k1, Signing, Verification};
14
15/// A tag used for recovering the public key from a compact signature.
16#[derive(Copy, Clone, PartialEq, Eq, Debug, Ord, PartialOrd)]
17pub struct RecoveryId(i32);
18
19/// An ECDSA signature with a recovery ID for pubkey recovery.
20#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash, Ord, PartialOrd)]
21pub struct RecoverableSignature(ffi::RecoverableSignature);
22
23impl RecoveryId {
24    #[inline]
25    /// Allows library users to create valid recovery IDs from i32.
26    pub fn from_i32(id: i32) -> Result<RecoveryId, Error> {
27        match id {
28            0..=3 => Ok(RecoveryId(id)),
29            _ => Err(Error::InvalidRecoveryId),
30        }
31    }
32
33    #[inline]
34    /// Allows library users to convert recovery IDs to i32.
35    pub fn to_i32(self) -> i32 { self.0 }
36}
37
38impl RecoverableSignature {
39    #[inline]
40    /// Converts a compact-encoded byte slice to a signature. This
41    /// representation is nonstandard and defined by the libsecp256k1 library.
42    pub fn from_compact(data: &[u8], recid: RecoveryId) -> Result<RecoverableSignature, Error> {
43        if data.is_empty() {
44            return Err(Error::InvalidSignature);
45        }
46
47        let mut ret = ffi::RecoverableSignature::new();
48
49        unsafe {
50            if data.len() != 64 {
51                Err(Error::InvalidSignature)
52            } else if ffi::secp256k1_ecdsa_recoverable_signature_parse_compact(
53                super_ffi::secp256k1_context_no_precomp,
54                &mut ret,
55                data.as_c_ptr(),
56                recid.0,
57            ) == 1
58            {
59                Ok(RecoverableSignature(ret))
60            } else {
61                Err(Error::InvalidSignature)
62            }
63        }
64    }
65
66    /// Obtains a raw pointer suitable for use with FFI functions.
67    #[inline]
68    #[deprecated(since = "0.25.0", note = "Use Self::as_c_ptr if you need to access the FFI layer")]
69    pub fn as_ptr(&self) -> *const ffi::RecoverableSignature { self.as_c_ptr() }
70
71    /// Obtains a raw mutable pointer suitable for use with FFI functions.
72    #[inline]
73    #[deprecated(
74        since = "0.25.0",
75        note = "Use Self::as_mut_c_ptr if you need to access the FFI layer"
76    )]
77    pub fn as_mut_ptr(&mut self) -> *mut ffi::RecoverableSignature { self.as_mut_c_ptr() }
78
79    #[inline]
80    /// Serializes the recoverable signature in compact format.
81    pub fn serialize_compact(&self) -> (RecoveryId, [u8; 64]) {
82        let mut ret = [0u8; 64];
83        let mut recid = 0i32;
84        unsafe {
85            let err = ffi::secp256k1_ecdsa_recoverable_signature_serialize_compact(
86                super_ffi::secp256k1_context_no_precomp,
87                ret.as_mut_c_ptr(),
88                &mut recid,
89                self.as_c_ptr(),
90            );
91            assert!(err == 1);
92        }
93        (RecoveryId(recid), ret)
94    }
95
96    /// Converts a recoverable signature to a non-recoverable one (this is needed
97    /// for verification).
98    #[inline]
99    pub fn to_standard(&self) -> Signature {
100        unsafe {
101            let mut ret = super_ffi::Signature::new();
102            let err = ffi::secp256k1_ecdsa_recoverable_signature_convert(
103                super_ffi::secp256k1_context_no_precomp,
104                &mut ret,
105                self.as_c_ptr(),
106            );
107            assert!(err == 1);
108            Signature(ret)
109        }
110    }
111
112    /// Determines the public key for which this [`Signature`] is valid for `msg`. Requires a
113    /// verify-capable context.
114    #[inline]
115    #[cfg(feature = "global-context")]
116    pub fn recover(&self, msg: &Message) -> Result<key::PublicKey, Error> {
117        crate::SECP256K1.recover_ecdsa(msg, self)
118    }
119}
120
121impl CPtr for RecoverableSignature {
122    type Target = ffi::RecoverableSignature;
123    fn as_c_ptr(&self) -> *const Self::Target { &self.0 }
124
125    fn as_mut_c_ptr(&mut self) -> *mut Self::Target { &mut self.0 }
126}
127
128/// Creates a new recoverable signature from a FFI one.
129impl From<ffi::RecoverableSignature> for RecoverableSignature {
130    #[inline]
131    fn from(sig: ffi::RecoverableSignature) -> RecoverableSignature { RecoverableSignature(sig) }
132}
133
134impl<C: Signing> Secp256k1<C> {
135    fn sign_ecdsa_recoverable_with_noncedata_pointer(
136        &self,
137        msg: &Message,
138        sk: &key::SecretKey,
139        noncedata_ptr: *const super_ffi::types::c_void,
140    ) -> RecoverableSignature {
141        let mut ret = ffi::RecoverableSignature::new();
142        unsafe {
143            // We can assume the return value because it's not possible to construct
144            // an invalid signature from a valid `Message` and `SecretKey`
145            assert_eq!(
146                ffi::secp256k1_ecdsa_sign_recoverable(
147                    self.ctx.as_ptr(),
148                    &mut ret,
149                    msg.as_c_ptr(),
150                    sk.as_c_ptr(),
151                    super_ffi::secp256k1_nonce_function_rfc6979,
152                    noncedata_ptr
153                ),
154                1
155            );
156        }
157
158        RecoverableSignature::from(ret)
159    }
160
161    /// Constructs a signature for `msg` using the secret key `sk` and RFC6979 nonce
162    /// Requires a signing-capable context.
163    pub fn sign_ecdsa_recoverable(
164        &self,
165        msg: &Message,
166        sk: &key::SecretKey,
167    ) -> RecoverableSignature {
168        self.sign_ecdsa_recoverable_with_noncedata_pointer(msg, sk, ptr::null())
169    }
170
171    /// Constructs a signature for `msg` using the secret key `sk` and RFC6979 nonce
172    /// and includes 32 bytes of noncedata in the nonce generation via inclusion in
173    /// one of the hash operations during nonce generation. This is useful when multiple
174    /// signatures are needed for the same Message and SecretKey while still using RFC6979.
175    /// Requires a signing-capable context.
176    pub fn sign_ecdsa_recoverable_with_noncedata(
177        &self,
178        msg: &Message,
179        sk: &key::SecretKey,
180        noncedata: &[u8; 32],
181    ) -> RecoverableSignature {
182        let noncedata_ptr = noncedata.as_ptr() as *const super_ffi::types::c_void;
183        self.sign_ecdsa_recoverable_with_noncedata_pointer(msg, sk, noncedata_ptr)
184    }
185}
186
187impl<C: Verification> Secp256k1<C> {
188    /// Determines the public key for which `sig` is a valid signature for
189    /// `msg`. Requires a verify-capable context.
190    pub fn recover_ecdsa(
191        &self,
192        msg: &Message,
193        sig: &RecoverableSignature,
194    ) -> Result<key::PublicKey, Error> {
195        unsafe {
196            let mut pk = super_ffi::PublicKey::new();
197            if ffi::secp256k1_ecdsa_recover(
198                self.ctx.as_ptr(),
199                &mut pk,
200                sig.as_c_ptr(),
201                msg.as_c_ptr(),
202            ) != 1
203            {
204                return Err(Error::InvalidSignature);
205            }
206            Ok(key::PublicKey::from(pk))
207        }
208    }
209}
210
211#[cfg(test)]
212#[allow(unused_imports)]
213mod tests {
214    #[cfg(target_arch = "wasm32")]
215    use wasm_bindgen_test::wasm_bindgen_test as test;
216
217    use super::{RecoverableSignature, RecoveryId};
218    use crate::constants::ONE;
219    use crate::{Error, Message, Secp256k1, SecretKey};
220
221    #[test]
222    #[cfg(feature = "rand-std")]
223    fn capabilities() {
224        let sign = Secp256k1::signing_only();
225        let vrfy = Secp256k1::verification_only();
226        let full = Secp256k1::new();
227
228        let msg = crate::random_32_bytes(&mut rand::thread_rng());
229        let msg = Message::from_digest_slice(&msg).unwrap();
230
231        // Try key generation
232        let (sk, pk) = full.generate_keypair(&mut rand::thread_rng());
233
234        // Try signing
235        assert_eq!(sign.sign_ecdsa_recoverable(&msg, &sk), full.sign_ecdsa_recoverable(&msg, &sk));
236        let sigr = full.sign_ecdsa_recoverable(&msg, &sk);
237
238        // Try pk recovery
239        assert!(vrfy.recover_ecdsa(&msg, &sigr).is_ok());
240        assert!(full.recover_ecdsa(&msg, &sigr).is_ok());
241
242        assert_eq!(vrfy.recover_ecdsa(&msg, &sigr), full.recover_ecdsa(&msg, &sigr));
243        assert_eq!(full.recover_ecdsa(&msg, &sigr), Ok(pk));
244    }
245
246    #[test]
247    fn recid_sanity_check() {
248        let one = RecoveryId(1);
249        assert_eq!(one, one.clone());
250    }
251
252    #[test]
253    #[cfg(not(secp256k1_fuzz))]  // fixed sig vectors can't work with fuzz-sigs
254    #[cfg(feature = "rand-std")]
255    #[rustfmt::skip]
256    fn sign() {
257        let mut s = Secp256k1::new();
258        s.randomize(&mut rand::thread_rng());
259
260        let sk = SecretKey::from_slice(&ONE).unwrap();
261        let msg = Message::from_digest_slice(&ONE).unwrap();
262
263        let sig = s.sign_ecdsa_recoverable(&msg, &sk);
264
265        assert_eq!(Ok(sig), RecoverableSignature::from_compact(&[
266            0x66, 0x73, 0xff, 0xad, 0x21, 0x47, 0x74, 0x1f,
267            0x04, 0x77, 0x2b, 0x6f, 0x92, 0x1f, 0x0b, 0xa6,
268            0xaf, 0x0c, 0x1e, 0x77, 0xfc, 0x43, 0x9e, 0x65,
269            0xc3, 0x6d, 0xed, 0xf4, 0x09, 0x2e, 0x88, 0x98,
270            0x4c, 0x1a, 0x97, 0x16, 0x52, 0xe0, 0xad, 0xa8,
271            0x80, 0x12, 0x0e, 0xf8, 0x02, 0x5e, 0x70, 0x9f,
272            0xff, 0x20, 0x80, 0xc4, 0xa3, 0x9a, 0xae, 0x06,
273            0x8d, 0x12, 0xee, 0xd0, 0x09, 0xb6, 0x8c, 0x89],
274            RecoveryId(1)))
275    }
276
277    #[test]
278    #[cfg(not(secp256k1_fuzz))]  // fixed sig vectors can't work with fuzz-sigs
279    #[cfg(feature = "rand-std")]
280    #[rustfmt::skip]
281    fn sign_with_noncedata() {
282        let mut s = Secp256k1::new();
283        s.randomize(&mut rand::thread_rng());
284
285        let sk = SecretKey::from_slice(&ONE).unwrap();
286        let msg = Message::from_digest_slice(&ONE).unwrap();
287        let noncedata = [42u8; 32];
288
289        let sig = s.sign_ecdsa_recoverable_with_noncedata(&msg, &sk, &noncedata);
290
291        assert_eq!(Ok(sig), RecoverableSignature::from_compact(&[
292            0xb5, 0x0b, 0xb6, 0x79, 0x5f, 0x31, 0x74, 0x8a,
293            0x4d, 0x37, 0xc3, 0xa9, 0x7e, 0xbd, 0x06, 0xa2,
294            0x2e, 0xa3, 0x37, 0x71, 0x04, 0x0f, 0x5c, 0x05,
295            0xd6, 0xe2, 0xbb, 0x2d, 0x38, 0xc6, 0x22, 0x7c,
296            0x34, 0x3b, 0x66, 0x59, 0xdb, 0x96, 0x99, 0x59,
297            0xd9, 0xfd, 0xdb, 0x44, 0xbd, 0x0d, 0xd9, 0xb9,
298            0xdd, 0x47, 0x66, 0x6a, 0xb5, 0x28, 0x71, 0x90,
299            0x1d, 0x17, 0x61, 0xeb, 0x82, 0xec, 0x87, 0x22],
300            RecoveryId(0)))
301    }
302
303    #[test]
304    #[cfg(feature = "rand-std")]
305    fn sign_and_verify_fail() {
306        let mut s = Secp256k1::new();
307        s.randomize(&mut rand::thread_rng());
308
309        let msg = crate::random_32_bytes(&mut rand::thread_rng());
310        let msg = Message::from_digest_slice(&msg).unwrap();
311
312        let (sk, pk) = s.generate_keypair(&mut rand::thread_rng());
313
314        let sigr = s.sign_ecdsa_recoverable(&msg, &sk);
315        let sig = sigr.to_standard();
316
317        let msg = crate::random_32_bytes(&mut rand::thread_rng());
318        let msg = Message::from_digest_slice(&msg).unwrap();
319        assert_eq!(s.verify_ecdsa(&msg, &sig, &pk), Err(Error::IncorrectSignature));
320
321        let recovered_key = s.recover_ecdsa(&msg, &sigr).unwrap();
322        assert!(recovered_key != pk);
323    }
324
325    #[test]
326    #[cfg(feature = "rand-std")]
327    fn sign_with_recovery() {
328        let mut s = Secp256k1::new();
329        s.randomize(&mut rand::thread_rng());
330
331        let msg = crate::random_32_bytes(&mut rand::thread_rng());
332        let msg = Message::from_digest_slice(&msg).unwrap();
333
334        let (sk, pk) = s.generate_keypair(&mut rand::thread_rng());
335
336        let sig = s.sign_ecdsa_recoverable(&msg, &sk);
337
338        assert_eq!(s.recover_ecdsa(&msg, &sig), Ok(pk));
339    }
340
341    #[test]
342    #[cfg(feature = "rand-std")]
343    fn sign_with_recovery_and_noncedata() {
344        let mut s = Secp256k1::new();
345        s.randomize(&mut rand::thread_rng());
346
347        let msg = crate::random_32_bytes(&mut rand::thread_rng());
348        let msg = Message::from_digest_slice(&msg).unwrap();
349
350        let noncedata = [42u8; 32];
351
352        let (sk, pk) = s.generate_keypair(&mut rand::thread_rng());
353
354        let sig = s.sign_ecdsa_recoverable_with_noncedata(&msg, &sk, &noncedata);
355
356        assert_eq!(s.recover_ecdsa(&msg, &sig), Ok(pk));
357    }
358
359    #[test]
360    #[cfg(feature = "rand-std")]
361    fn bad_recovery() {
362        let mut s = Secp256k1::new();
363        s.randomize(&mut rand::thread_rng());
364
365        let msg = Message::from_digest_slice(&[0x55; 32]).unwrap();
366
367        // Zero is not a valid sig
368        let sig = RecoverableSignature::from_compact(&[0; 64], RecoveryId(0)).unwrap();
369        assert_eq!(s.recover_ecdsa(&msg, &sig), Err(Error::InvalidSignature));
370        // ...but 111..111 is
371        let sig = RecoverableSignature::from_compact(&[1; 64], RecoveryId(0)).unwrap();
372        assert!(s.recover_ecdsa(&msg, &sig).is_ok());
373    }
374
375    #[test]
376    fn test_debug_output() {
377        #[rustfmt::skip]
378        let sig = RecoverableSignature::from_compact(&[
379            0x66, 0x73, 0xff, 0xad, 0x21, 0x47, 0x74, 0x1f,
380            0x04, 0x77, 0x2b, 0x6f, 0x92, 0x1f, 0x0b, 0xa6,
381            0xaf, 0x0c, 0x1e, 0x77, 0xfc, 0x43, 0x9e, 0x65,
382            0xc3, 0x6d, 0xed, 0xf4, 0x09, 0x2e, 0x88, 0x98,
383            0x4c, 0x1a, 0x97, 0x16, 0x52, 0xe0, 0xad, 0xa8,
384            0x80, 0x12, 0x0e, 0xf8, 0x02, 0x5e, 0x70, 0x9f,
385            0xff, 0x20, 0x80, 0xc4, 0xa3, 0x9a, 0xae, 0x06,
386            0x8d, 0x12, 0xee, 0xd0, 0x09, 0xb6, 0x8c, 0x89],
387            RecoveryId(1)).unwrap();
388        assert_eq!(&format!("{:?}", sig), "RecoverableSignature(6673ffad2147741f04772b6f921f0ba6af0c1e77fc439e65c36dedf4092e88984c1a971652e0ada880120ef8025e709fff2080c4a39aae068d12eed009b68c8901)");
389    }
390
391    #[test]
392    fn test_recov_sig_serialize_compact() {
393        let recid_in = RecoveryId(1);
394        #[rustfmt::skip]
395        let bytes_in = &[
396            0x66, 0x73, 0xff, 0xad, 0x21, 0x47, 0x74, 0x1f,
397            0x04, 0x77, 0x2b, 0x6f, 0x92, 0x1f, 0x0b, 0xa6,
398            0xaf, 0x0c, 0x1e, 0x77, 0xfc, 0x43, 0x9e, 0x65,
399            0xc3, 0x6d, 0xed, 0xf4, 0x09, 0x2e, 0x88, 0x98,
400            0x4c, 0x1a, 0x97, 0x16, 0x52, 0xe0, 0xad, 0xa8,
401            0x80, 0x12, 0x0e, 0xf8, 0x02, 0x5e, 0x70, 0x9f,
402            0xff, 0x20, 0x80, 0xc4, 0xa3, 0x9a, 0xae, 0x06,
403            0x8d, 0x12, 0xee, 0xd0, 0x09, 0xb6, 0x8c, 0x89];
404        let sig = RecoverableSignature::from_compact(bytes_in, recid_in).unwrap();
405        let (recid_out, bytes_out) = sig.serialize_compact();
406        assert_eq!(recid_in, recid_out);
407        assert_eq!(&bytes_in[..], &bytes_out[..]);
408    }
409
410    #[test]
411    fn test_recov_id_conversion_between_i32() {
412        assert!(RecoveryId::from_i32(-1).is_err());
413        assert!(RecoveryId::from_i32(0).is_ok());
414        assert!(RecoveryId::from_i32(1).is_ok());
415        assert!(RecoveryId::from_i32(2).is_ok());
416        assert!(RecoveryId::from_i32(3).is_ok());
417        assert!(RecoveryId::from_i32(4).is_err());
418        let id0 = RecoveryId::from_i32(0).unwrap();
419        assert_eq!(id0.to_i32(), 0);
420        let id1 = RecoveryId(1);
421        assert_eq!(id1.to_i32(), 1);
422    }
423}
424
425#[cfg(bench)]
426#[cfg(feature = "rand-std")] // Currently only a single bench that requires "rand-std".
427mod benches {
428    use test::{black_box, Bencher};
429
430    use super::{Message, Secp256k1};
431
432    #[bench]
433    pub fn bench_recover(bh: &mut Bencher) {
434        let s = Secp256k1::new();
435        let msg = crate::random_32_bytes(&mut rand::thread_rng());
436        let msg = Message::from_digest_slice(&msg).unwrap();
437        let (sk, _) = s.generate_keypair(&mut rand::thread_rng());
438        let sig = s.sign_ecdsa_recoverable(&msg, &sk);
439
440        bh.iter(|| {
441            let res = s.recover_ecdsa(&msg, &sig).unwrap();
442            black_box(res);
443        });
444    }
445}