From 264b368ee0e8f45b1413062cb0a5bcb1aa49d1e0 Mon Sep 17 00:00:00 2001 From: Jonas Nick Date: Mon, 20 May 2019 19:11:59 +0000 Subject: [PATCH 1/3] Move recovery to its own module --- src/lib.rs | 290 +------------------------------------- src/recovery.rs | 359 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 362 insertions(+), 287 deletions(-) create mode 100644 src/recovery.rs diff --git a/src/lib.rs b/src/lib.rs index 69dee83..6277cb1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -152,16 +152,13 @@ pub mod constants; pub mod ecdh; pub mod ffi; pub mod key; +pub mod recovery; pub use key::SecretKey; pub use key::PublicKey; use core::marker::PhantomData; use core::ops::Deref; -/// A tag used for recovering the public key from a compact signature -#[derive(Copy, Clone, PartialEq, Eq, Debug)] -pub struct RecoveryId(i32); - /// An ECDSA signature #[derive(Copy, Clone, PartialEq, Eq)] pub struct Signature(ffi::Signature); @@ -210,10 +207,6 @@ fn from_str(s: &str) -> Result { } } -/// An ECDSA signature with a recovery ID for pubkey recovery -#[derive(Copy, Clone, PartialEq, Eq, Debug)] -pub struct RecoverableSignature(ffi::RecoverableSignature); - /// Trait describing something that promises to be a 32-byte random number; in particular, /// it has negligible probability of being zero or overflowing the group order. Such objects /// may be converted to `Message`s without any error paths. @@ -222,23 +215,6 @@ pub trait ThirtyTwoByteHash { fn into_32(self) -> [u8; 32]; } -impl RecoveryId { -#[inline] -/// Allows library users to create valid recovery IDs from i32. -pub fn from_i32(id: i32) -> Result { - match id { - 0 | 1 | 2 | 3 => Ok(RecoveryId(id)), - _ => Err(Error::InvalidRecoveryId) - } -} - -#[inline] -/// Allows library users to convert recovery IDs to i32. -pub fn to_i32(self) -> i32 { - self.0 -} -} - impl SerializedSignature { /// Get a pointer to the underlying data with the specified capacity. pub(crate) fn get_data_mut_ptr(&mut self) -> *mut u8 { @@ -420,79 +396,6 @@ impl From for Signature { } -impl RecoverableSignature { - #[inline] - /// Converts a compact-encoded byte slice to a signature. This - /// representation is nonstandard and defined by the libsecp256k1 - /// library. - pub fn from_compact(data: &[u8], recid: RecoveryId) -> Result { - let mut ret = unsafe { ffi::RecoverableSignature::blank() }; - - unsafe { - if data.len() != 64 { - Err(Error::InvalidSignature) - } else if ffi::secp256k1_ecdsa_recoverable_signature_parse_compact( - ffi::secp256k1_context_no_precomp, - &mut ret, - data.as_ptr(), - recid.0, - ) == 1 - { - Ok(RecoverableSignature(ret)) - } else { - Err(Error::InvalidSignature) - } - } - } - - /// Obtains a raw pointer suitable for use with FFI functions - #[inline] - pub fn as_ptr(&self) -> *const ffi::RecoverableSignature { - &self.0 as *const _ - } - - #[inline] - /// Serializes the recoverable signature in compact format - pub fn serialize_compact(&self) -> (RecoveryId, [u8; 64]) { - let mut ret = [0u8; 64]; - let mut recid = 0i32; - unsafe { - let err = ffi::secp256k1_ecdsa_recoverable_signature_serialize_compact( - ffi::secp256k1_context_no_precomp, - ret.as_mut_ptr(), - &mut recid, - self.as_ptr(), - ); - assert!(err == 1); - } - (RecoveryId(recid), ret) - } - - /// Converts a recoverable signature to a non-recoverable one (this is needed - /// for verification - #[inline] - pub fn to_standard(&self) -> Signature { - let mut ret = unsafe { ffi::Signature::blank() }; - unsafe { - let err = ffi::secp256k1_ecdsa_recoverable_signature_convert( - ffi::secp256k1_context_no_precomp, - &mut ret, - self.as_ptr(), - ); - assert!(err == 1); - } - Signature(ret) - } -} - -/// Creates a new recoverable signature from a FFI one -impl From for RecoverableSignature { - #[inline] - fn from(sig: ffi::RecoverableSignature) -> RecoverableSignature { - RecoverableSignature(sig) - } -} - #[cfg(feature = "serde")] impl ::serde::Serialize for Signature { fn serialize(&self, s: S) -> Result { @@ -767,31 +670,6 @@ impl Secp256k1 { Signature::from(ret) } - /// Constructs a signature for `msg` using the secret key `sk` and RFC6979 nonce - /// Requires a signing-capable context. - pub fn sign_recoverable(&self, msg: &Message, sk: &key::SecretKey) - -> RecoverableSignature { - - let mut ret = unsafe { ffi::RecoverableSignature::blank() }; - unsafe { - // We can assume the return value because it's not possible to construct - // an invalid signature from a valid `Message` and `SecretKey` - assert_eq!( - ffi::secp256k1_ecdsa_sign_recoverable( - self.ctx, - &mut ret, - msg.as_ptr(), - sk.as_ptr(), - ffi::secp256k1_nonce_function_rfc6979, - ptr::null() - ), - 1 - ); - } - - RecoverableSignature::from(ret) - } - /// Generates a random keypair. Convenience function for `key::SecretKey::new` /// and `key::PublicKey::from_secret_key`; call those functions directly for /// batch key generation. Requires a signing-capable context. Requires compilation @@ -807,23 +685,6 @@ impl Secp256k1 { } impl Secp256k1 { - - /// Determines the public key for which `sig` is a valid signature for - /// `msg`. Requires a verify-capable context. - pub fn recover(&self, msg: &Message, sig: &RecoverableSignature) - -> Result { - - let mut pk = unsafe { ffi::PublicKey::blank() }; - - unsafe { - if ffi::secp256k1_ecdsa_recover(self.ctx, &mut pk, - sig.as_ptr(), msg.as_ptr()) != 1 { - return Err(Error::InvalidSignature); - } - }; - Ok(key::PublicKey::from(pk)) - } - /// Checks that `sig` is a valid ECDSA signature for `msg` using the public /// key `pubkey`. Returns `Ok(true)` on success. Note that this function cannot /// be used for Bitcoin consensus checking since there may exist signatures @@ -877,7 +738,7 @@ mod tests { use key::{SecretKey, PublicKey}; use super::from_hex; use super::constants; - use super::{Secp256k1, Signature, RecoverableSignature, Message, RecoveryId}; + use super::{Secp256k1, Signature, Message}; use super::Error::{InvalidMessage, IncorrectSignature, InvalidSignature}; macro_rules! hex { @@ -903,22 +764,12 @@ mod tests { // Try signing assert_eq!(sign.sign(&msg, &sk), full.sign(&msg, &sk)); - assert_eq!(sign.sign_recoverable(&msg, &sk), full.sign_recoverable(&msg, &sk)); let sig = full.sign(&msg, &sk); - let sigr = full.sign_recoverable(&msg, &sk); // Try verifying assert!(vrfy.verify(&msg, &sig, &pk).is_ok()); assert!(full.verify(&msg, &sig, &pk).is_ok()); - // Try pk recovery - assert!(vrfy.recover(&msg, &sigr).is_ok()); - assert!(full.recover(&msg, &sigr).is_ok()); - - assert_eq!(vrfy.recover(&msg, &sigr), - full.recover(&msg, &sigr)); - assert_eq!(full.recover(&msg, &sigr), Ok(pk)); - // Check that we can produce keys from slices with no precomputation let (pk_slice, sk_slice) = (&pk.serialize(), &sk[..]); let new_pk = PublicKey::from_slice(pk_slice).unwrap(); @@ -927,35 +778,6 @@ mod tests { assert_eq!(pk, new_pk); } - #[test] - fn recid_sanity_check() { - let one = RecoveryId(1); - assert_eq!(one, one.clone()); - } - - #[test] - fn sign() { - let mut s = Secp256k1::new(); - s.randomize(&mut thread_rng()); - let one = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]; - - let sk = SecretKey::from_slice(&one).unwrap(); - let msg = Message::from_slice(&one).unwrap(); - - let sig = s.sign_recoverable(&msg, &sk); - assert_eq!(Ok(sig), RecoverableSignature::from_compact(&[ - 0x66, 0x73, 0xff, 0xad, 0x21, 0x47, 0x74, 0x1f, - 0x04, 0x77, 0x2b, 0x6f, 0x92, 0x1f, 0x0b, 0xa6, - 0xaf, 0x0c, 0x1e, 0x77, 0xfc, 0x43, 0x9e, 0x65, - 0xc3, 0x6d, 0xed, 0xf4, 0x09, 0x2e, 0x88, 0x98, - 0x4c, 0x1a, 0x97, 0x16, 0x52, 0xe0, 0xad, 0xa8, - 0x80, 0x12, 0x0e, 0xf8, 0x02, 0x5e, 0x70, 0x9f, - 0xff, 0x20, 0x80, 0xc4, 0xa3, 0x9a, 0xae, 0x06, - 0x8d, 0x12, 0xee, 0xd0, 0x09, 0xb6, 0x8c, 0x89], - RecoveryId(1))) - } - #[test] fn signature_serialize_roundtrip() { let mut s = Secp256k1::new(); @@ -1093,47 +915,12 @@ mod tests { let (sk, pk) = s.generate_keypair(&mut thread_rng()); - let sigr = s.sign_recoverable(&msg, &sk); - let sig = sigr.to_standard(); + let sig = s.sign(&msg, &sk); let mut msg = [0u8; 32]; thread_rng().fill_bytes(&mut msg); let msg = Message::from_slice(&msg).unwrap(); assert_eq!(s.verify(&msg, &sig, &pk), Err(IncorrectSignature)); - - let recovered_key = s.recover(&msg, &sigr).unwrap(); - assert!(recovered_key != pk); - } - - #[test] - fn sign_with_recovery() { - let mut s = Secp256k1::new(); - s.randomize(&mut thread_rng()); - - let mut msg = [0u8; 32]; - thread_rng().fill_bytes(&mut msg); - let msg = Message::from_slice(&msg).unwrap(); - - let (sk, pk) = s.generate_keypair(&mut thread_rng()); - - let sig = s.sign_recoverable(&msg, &sk); - - assert_eq!(s.recover(&msg, &sig), Ok(pk)); - } - - #[test] - fn bad_recovery() { - let mut s = Secp256k1::new(); - s.randomize(&mut thread_rng()); - - let msg = Message::from_slice(&[0x55; 32]).unwrap(); - - // Zero is not a valid sig - let sig = RecoverableSignature::from_compact(&[0; 64], RecoveryId(0)).unwrap(); - assert_eq!(s.recover(&msg, &sig), Err(InvalidSignature)); - // ...but 111..111 is - let sig = RecoverableSignature::from_compact(&[1; 64], RecoveryId(0)).unwrap(); - assert!(s.recover(&msg, &sig).is_ok()); } #[test] @@ -1154,62 +941,6 @@ mod tests { assert!(Message::from_slice(&[1; constants::MESSAGE_SIZE]).is_ok()); } - #[test] - fn test_debug_output() { - let sig = RecoverableSignature::from_compact(&[ - 0x66, 0x73, 0xff, 0xad, 0x21, 0x47, 0x74, 0x1f, - 0x04, 0x77, 0x2b, 0x6f, 0x92, 0x1f, 0x0b, 0xa6, - 0xaf, 0x0c, 0x1e, 0x77, 0xfc, 0x43, 0x9e, 0x65, - 0xc3, 0x6d, 0xed, 0xf4, 0x09, 0x2e, 0x88, 0x98, - 0x4c, 0x1a, 0x97, 0x16, 0x52, 0xe0, 0xad, 0xa8, - 0x80, 0x12, 0x0e, 0xf8, 0x02, 0x5e, 0x70, 0x9f, - 0xff, 0x20, 0x80, 0xc4, 0xa3, 0x9a, 0xae, 0x06, - 0x8d, 0x12, 0xee, 0xd0, 0x09, 0xb6, 0x8c, 0x89], - RecoveryId(1)).unwrap(); - assert_eq!(&format!("{:?}", sig), "RecoverableSignature(98882e09f4ed6dc3659e43fc771e0cafa60b1f926f2b77041f744721adff7366898cb609d0ee128d06ae9aa3c48020ff9f705e02f80e1280a8ade05216971a4c01)"); - - let msg = Message([1, 2, 3, 4, 5, 6, 7, 8, - 9, 10, 11, 12, 13, 14, 15, 16, - 17, 18, 19, 20, 21, 22, 23, 24, - 25, 26, 27, 28, 29, 30, 31, 255]); - assert_eq!(&format!("{:?}", msg), "Message(0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1fff)"); - } - - #[test] - fn test_recov_sig_serialize_compact() { - let recid_in = RecoveryId(1); - let bytes_in = &[ - 0x66, 0x73, 0xff, 0xad, 0x21, 0x47, 0x74, 0x1f, - 0x04, 0x77, 0x2b, 0x6f, 0x92, 0x1f, 0x0b, 0xa6, - 0xaf, 0x0c, 0x1e, 0x77, 0xfc, 0x43, 0x9e, 0x65, - 0xc3, 0x6d, 0xed, 0xf4, 0x09, 0x2e, 0x88, 0x98, - 0x4c, 0x1a, 0x97, 0x16, 0x52, 0xe0, 0xad, 0xa8, - 0x80, 0x12, 0x0e, 0xf8, 0x02, 0x5e, 0x70, 0x9f, - 0xff, 0x20, 0x80, 0xc4, 0xa3, 0x9a, 0xae, 0x06, - 0x8d, 0x12, 0xee, 0xd0, 0x09, 0xb6, 0x8c, 0x89]; - let sig = RecoverableSignature::from_compact( - bytes_in, - recid_in, - ).unwrap(); - let (recid_out, bytes_out) = sig.serialize_compact(); - assert_eq!(recid_in, recid_out); - assert_eq!(&bytes_in[..], &bytes_out[..]); - } - - #[test] - fn test_recov_id_conversion_between_i32() { - assert!(RecoveryId::from_i32(-1).is_err()); - assert!(RecoveryId::from_i32(0).is_ok()); - assert!(RecoveryId::from_i32(1).is_ok()); - assert!(RecoveryId::from_i32(2).is_ok()); - assert!(RecoveryId::from_i32(3).is_ok()); - assert!(RecoveryId::from_i32(4).is_err()); - let id0 = RecoveryId::from_i32(0).unwrap(); - assert_eq!(id0.to_i32(), 0); - let id1 = RecoveryId(1); - assert_eq!(id1.to_i32(), 1); - } - #[test] fn test_low_s() { // nb this is a transaction on testnet @@ -1304,19 +1035,4 @@ mod benches { black_box(res); }); } - - #[bench] - pub fn bench_recover(bh: &mut Bencher) { - let s = Secp256k1::new(); - let mut msg = [0u8; 32]; - thread_rng().fill_bytes(&mut msg); - let msg = Message::from_slice(&msg).unwrap(); - let (sk, _) = s.generate_keypair(&mut thread_rng()); - let sig = s.sign_recoverable(&msg, &sk); - - bh.iter(|| { - let res = s.recover(&msg, &sig).unwrap(); - black_box(res); - }); - } } diff --git a/src/recovery.rs b/src/recovery.rs new file mode 100644 index 0000000..c04005d --- /dev/null +++ b/src/recovery.rs @@ -0,0 +1,359 @@ +// TODO header +// Bitcoin secp256k1 bindings +// Written in 2014 by +// Dawid Ciężarkiewicz +// Andrew Poelstra +// +// To the extent possible under law, the author(s) have dedicated all +// copyright and related and neighboring rights to this software to +// the public domain worldwide. This software is distributed without +// any warranty. +// +// You should have received a copy of the CC0 Public Domain Dedication +// along with this software. +// If not, see . +// + +//! # Recovery module +//! Provides a signing function that allows recovering the public key from the +//! signature. + +use core::ptr; +use ffi; +use key; +use super::{Secp256k1, Message, Error, Signature, Verification, Signing}; +pub use key::SecretKey; +pub use key::PublicKey; + +/// A tag used for recovering the public key from a compact signature +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +pub struct RecoveryId(i32); + +/// An ECDSA signature with a recovery ID for pubkey recovery +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +pub struct RecoverableSignature(ffi::RecoverableSignature); + +impl RecoveryId { +#[inline] +/// Allows library users to create valid recovery IDs from i32. +/// TODO +pub fn from_i32(id: i32) -> Result { + match id { + 0 | 1 | 2 | 3 => Ok(RecoveryId(id)), + _ => Err(Error::InvalidRecoveryId) + } +} + +#[inline] +/// Allows library users to convert recovery IDs to i32. +pub fn to_i32(self) -> i32 { + self.0 +} +} + +impl RecoverableSignature { + #[inline] + /// Converts a compact-encoded byte slice to a signature. This + /// representation is nonstandard and defined by the libsecp256k1 + /// library. + pub fn from_compact(data: &[u8], recid: RecoveryId) -> Result { + let mut ret = unsafe { ffi::RecoverableSignature::blank() }; + + unsafe { + if data.len() != 64 { + Err(Error::InvalidSignature) + } else if ffi::secp256k1_ecdsa_recoverable_signature_parse_compact( + ffi::secp256k1_context_no_precomp, + &mut ret, + data.as_ptr(), + recid.0, + ) == 1 + { + Ok(RecoverableSignature(ret)) + } else { + Err(Error::InvalidSignature) + } + } + } + + /// Obtains a raw pointer suitable for use with FFI functions + #[inline] + pub fn as_ptr(&self) -> *const ffi::RecoverableSignature { + &self.0 as *const _ + } + + #[inline] + /// Serializes the recoverable signature in compact format + pub fn serialize_compact(&self) -> (RecoveryId, [u8; 64]) { + let mut ret = [0u8; 64]; + let mut recid = 0i32; + unsafe { + let err = ffi::secp256k1_ecdsa_recoverable_signature_serialize_compact( + ffi::secp256k1_context_no_precomp, + ret.as_mut_ptr(), + &mut recid, + self.as_ptr(), + ); + assert!(err == 1); + } + (RecoveryId(recid), ret) + } + + /// Converts a recoverable signature to a non-recoverable one (this is needed + /// for verification + #[inline] + pub fn to_standard(&self) -> Signature { + let mut ret = unsafe { ffi::Signature::blank() }; + unsafe { + let err = ffi::secp256k1_ecdsa_recoverable_signature_convert( + ffi::secp256k1_context_no_precomp, + &mut ret, + self.as_ptr(), + ); + assert!(err == 1); + } + Signature(ret) + } +} + +/// Creates a new recoverable signature from a FFI one +impl From for RecoverableSignature { + #[inline] + fn from(sig: ffi::RecoverableSignature) -> RecoverableSignature { + RecoverableSignature(sig) + } +} + +impl Secp256k1 { + /// Constructs a signature for `msg` using the secret key `sk` and RFC6979 nonce + /// Requires a signing-capable context. + pub fn sign_recoverable(&self, msg: &Message, sk: &key::SecretKey) + -> RecoverableSignature { + + let mut ret = unsafe { ffi::RecoverableSignature::blank() }; + unsafe { + // We can assume the return value because it's not possible to construct + // an invalid signature from a valid `Message` and `SecretKey` + assert_eq!( + ffi::secp256k1_ecdsa_sign_recoverable( + self.ctx, + &mut ret, + msg.as_ptr(), + sk.as_ptr(), + ffi::secp256k1_nonce_function_rfc6979, + ptr::null() + ), + 1 + ); + } + + RecoverableSignature::from(ret) + } +} + +impl Secp256k1 { + /// Determines the public key for which `sig` is a valid signature for + /// `msg`. Requires a verify-capable context. + pub fn recover(&self, msg: &Message, sig: &RecoverableSignature) + -> Result { + + let mut pk = unsafe { ffi::PublicKey::blank() }; + + unsafe { + if ffi::secp256k1_ecdsa_recover(self.ctx, &mut pk, + sig.as_ptr(), msg.as_ptr()) != 1 { + return Err(Error::InvalidSignature); + } + }; + Ok(key::PublicKey::from(pk)) + } +} + + +#[cfg(test)] +mod tests { + use rand::{RngCore, thread_rng}; + + use key::{SecretKey, PublicKey}; + use super::{RecoveryId, RecoverableSignature}; + use super::super::{Secp256k1, Message}; + use super::super::Error::{IncorrectSignature, InvalidSignature}; + + #[test] + fn capabilities() { + let sign = Secp256k1::signing_only(); + let vrfy = Secp256k1::verification_only(); + let full = Secp256k1::new(); + + let mut msg = [0u8; 32]; + thread_rng().fill_bytes(&mut msg); + let msg = Message::from_slice(&msg).unwrap(); + + // Try key generation + let (sk, pk) = full.generate_keypair(&mut thread_rng()); + + // Try signing + assert_eq!(sign.sign_recoverable(&msg, &sk), full.sign_recoverable(&msg, &sk)); + let sigr = full.sign_recoverable(&msg, &sk); + + // Try pk recovery + assert!(vrfy.recover(&msg, &sigr).is_ok()); + assert!(full.recover(&msg, &sigr).is_ok()); + + assert_eq!(vrfy.recover(&msg, &sigr), + full.recover(&msg, &sigr)); + assert_eq!(full.recover(&msg, &sigr), Ok(pk)); + } + + #[test] + fn recid_sanity_check() { + let one = RecoveryId(1); + assert_eq!(one, one.clone()); + } + + #[test] + fn sign() { + let mut s = Secp256k1::new(); + s.randomize(&mut thread_rng()); + let one = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]; + + let sk = SecretKey::from_slice(&one).unwrap(); + let msg = Message::from_slice(&one).unwrap(); + + let sig = s.sign_recoverable(&msg, &sk); + assert_eq!(Ok(sig), RecoverableSignature::from_compact(&[ + 0x66, 0x73, 0xff, 0xad, 0x21, 0x47, 0x74, 0x1f, + 0x04, 0x77, 0x2b, 0x6f, 0x92, 0x1f, 0x0b, 0xa6, + 0xaf, 0x0c, 0x1e, 0x77, 0xfc, 0x43, 0x9e, 0x65, + 0xc3, 0x6d, 0xed, 0xf4, 0x09, 0x2e, 0x88, 0x98, + 0x4c, 0x1a, 0x97, 0x16, 0x52, 0xe0, 0xad, 0xa8, + 0x80, 0x12, 0x0e, 0xf8, 0x02, 0x5e, 0x70, 0x9f, + 0xff, 0x20, 0x80, 0xc4, 0xa3, 0x9a, 0xae, 0x06, + 0x8d, 0x12, 0xee, 0xd0, 0x09, 0xb6, 0x8c, 0x89], + RecoveryId(1))) + } + + #[test] + fn sign_and_verify_fail() { + let mut s = Secp256k1::new(); + s.randomize(&mut thread_rng()); + + let mut msg = [0u8; 32]; + thread_rng().fill_bytes(&mut msg); + let msg = Message::from_slice(&msg).unwrap(); + + let (sk, pk) = s.generate_keypair(&mut thread_rng()); + + let sigr = s.sign_recoverable(&msg, &sk); + let sig = sigr.to_standard(); + + let mut msg = [0u8; 32]; + thread_rng().fill_bytes(&mut msg); + let msg = Message::from_slice(&msg).unwrap(); + assert_eq!(s.verify(&msg, &sig, &pk), Err(IncorrectSignature)); + + let recovered_key = s.recover(&msg, &sigr).unwrap(); + assert!(recovered_key != pk); + } + + #[test] + fn sign_with_recovery() { + let mut s = Secp256k1::new(); + s.randomize(&mut thread_rng()); + + let mut msg = [0u8; 32]; + thread_rng().fill_bytes(&mut msg); + let msg = Message::from_slice(&msg).unwrap(); + + let (sk, pk) = s.generate_keypair(&mut thread_rng()); + + let sig = s.sign_recoverable(&msg, &sk); + + assert_eq!(s.recover(&msg, &sig), Ok(pk)); + } + + #[test] + fn bad_recovery() { + let mut s = Secp256k1::new(); + s.randomize(&mut thread_rng()); + + let msg = Message::from_slice(&[0x55; 32]).unwrap(); + + // Zero is not a valid sig + let sig = RecoverableSignature::from_compact(&[0; 64], RecoveryId(0)).unwrap(); + assert_eq!(s.recover(&msg, &sig), Err(InvalidSignature)); + // ...but 111..111 is + let sig = RecoverableSignature::from_compact(&[1; 64], RecoveryId(0)).unwrap(); + assert!(s.recover(&msg, &sig).is_ok()); + } + + #[test] + fn test_debug_output() { + let sig = RecoverableSignature::from_compact(&[ + 0x66, 0x73, 0xff, 0xad, 0x21, 0x47, 0x74, 0x1f, + 0x04, 0x77, 0x2b, 0x6f, 0x92, 0x1f, 0x0b, 0xa6, + 0xaf, 0x0c, 0x1e, 0x77, 0xfc, 0x43, 0x9e, 0x65, + 0xc3, 0x6d, 0xed, 0xf4, 0x09, 0x2e, 0x88, 0x98, + 0x4c, 0x1a, 0x97, 0x16, 0x52, 0xe0, 0xad, 0xa8, + 0x80, 0x12, 0x0e, 0xf8, 0x02, 0x5e, 0x70, 0x9f, + 0xff, 0x20, 0x80, 0xc4, 0xa3, 0x9a, 0xae, 0x06, + 0x8d, 0x12, 0xee, 0xd0, 0x09, 0xb6, 0x8c, 0x89], + RecoveryId(1)).unwrap(); + assert_eq!(&format!("{:?}", sig), "RecoverableSignature(98882e09f4ed6dc3659e43fc771e0cafa60b1f926f2b77041f744721adff7366898cb609d0ee128d06ae9aa3c48020ff9f705e02f80e1280a8ade05216971a4c01)"); + } + + #[test] + fn test_recov_sig_serialize_compact() { + let recid_in = RecoveryId(1); + let bytes_in = &[ + 0x66, 0x73, 0xff, 0xad, 0x21, 0x47, 0x74, 0x1f, + 0x04, 0x77, 0x2b, 0x6f, 0x92, 0x1f, 0x0b, 0xa6, + 0xaf, 0x0c, 0x1e, 0x77, 0xfc, 0x43, 0x9e, 0x65, + 0xc3, 0x6d, 0xed, 0xf4, 0x09, 0x2e, 0x88, 0x98, + 0x4c, 0x1a, 0x97, 0x16, 0x52, 0xe0, 0xad, 0xa8, + 0x80, 0x12, 0x0e, 0xf8, 0x02, 0x5e, 0x70, 0x9f, + 0xff, 0x20, 0x80, 0xc4, 0xa3, 0x9a, 0xae, 0x06, + 0x8d, 0x12, 0xee, 0xd0, 0x09, 0xb6, 0x8c, 0x89]; + let sig = RecoverableSignature::from_compact( + bytes_in, + recid_in, + ).unwrap(); + let (recid_out, bytes_out) = sig.serialize_compact(); + assert_eq!(recid_in, recid_out); + assert_eq!(&bytes_in[..], &bytes_out[..]); + } + + #[test] + fn test_recov_id_conversion_between_i32() { + assert!(RecoveryId::from_i32(-1).is_err()); + assert!(RecoveryId::from_i32(0).is_ok()); + assert!(RecoveryId::from_i32(1).is_ok()); + assert!(RecoveryId::from_i32(2).is_ok()); + assert!(RecoveryId::from_i32(3).is_ok()); + assert!(RecoveryId::from_i32(4).is_err()); + let id0 = RecoveryId::from_i32(0).unwrap(); + assert_eq!(id0.to_i32(), 0); + let id1 = RecoveryId(1); + assert_eq!(id1.to_i32(), 1); + } +} + + +#[cfg(all(test, feature = "unstable"))] +mod benches { + #[bench] + pub fn bench_recover(bh: &mut Bencher) { + let s = Secp256k1::new(); + let mut msg = [0u8; 32]; + thread_rng().fill_bytes(&mut msg); + let msg = Message::from_slice(&msg).unwrap(); + let (sk, _) = s.generate_keypair(&mut thread_rng()); + let sig = s.sign_recoverable(&msg, &sk); + + bh.iter(|| { + let res = s.recover(&msg, &sig).unwrap(); + black_box(res); + }); + } +} From c7eecd159eb2d5ffc7bc6e4c5cf37d15369223c8 Mon Sep 17 00:00:00 2001 From: Jonas Nick Date: Mon, 20 May 2019 19:41:10 +0000 Subject: [PATCH 2/3] Feature gate recovery module --- .travis.yml | 3 ++- Cargo.toml | 3 ++- src/lib.rs | 1 + src/recovery.rs | 2 +- 4 files changed, 6 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 3cc8800..a457eb5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -24,10 +24,11 @@ script: - cargo build --verbose --features=rand - cargo test --verbose --features=rand - cargo test --verbose --features="rand serde" + - cargo test --verbose --features="rand serde recovery" - cargo build --verbose --no-default-features - cargo build --verbose --no-default-features --features="serde" - cargo build --verbose --no-default-features --features="rand" - - cargo build --verbose --no-default-features --features="rand serde" + - cargo build --verbose --no-default-features --features="rand serde recovery" - cargo build --verbose - cargo test --verbose - cargo build --release diff --git a/Cargo.toml b/Cargo.toml index 31a8c6e..c7c876e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,7 @@ build = "build.rs" # Should make docs.rs show all functions, even those behind non-default features [package.metadata.docs.rs] -features = [ "rand", "serde" ] +features = [ "rand", "serde", "recovery" ] all-features = true [build-dependencies] @@ -30,6 +30,7 @@ unstable = [] default = ["std"] fuzztarget = [] std = [] +recovery = [] [dev-dependencies] rand = "0.6" diff --git a/src/lib.rs b/src/lib.rs index 6277cb1..e1bf172 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -152,6 +152,7 @@ pub mod constants; pub mod ecdh; pub mod ffi; pub mod key; +#[cfg(feature = "recovery")] pub mod recovery; pub use key::SecretKey; diff --git a/src/recovery.rs b/src/recovery.rs index c04005d..73e5367 100644 --- a/src/recovery.rs +++ b/src/recovery.rs @@ -174,7 +174,7 @@ impl Secp256k1 { mod tests { use rand::{RngCore, thread_rng}; - use key::{SecretKey, PublicKey}; + use key::SecretKey; use super::{RecoveryId, RecoverableSignature}; use super::super::{Secp256k1, Message}; use super::super::Error::{IncorrectSignature, InvalidSignature}; From 16da1a854c64ff945f443b3f4dc4621eb8d75963 Mon Sep 17 00:00:00 2001 From: Jonas Nick Date: Tue, 21 May 2019 07:37:15 +0000 Subject: [PATCH 3/3] Move recovery ffi into recovery module and feature gate C-secp recovery module --- build.rs | 6 +- src/ffi.rs | 94 --------------------- src/recovery/ffi.rs | 118 +++++++++++++++++++++++++++ src/{recovery.rs => recovery/mod.rs} | 18 ++-- 4 files changed, 131 insertions(+), 105 deletions(-) create mode 100644 src/recovery/ffi.rs rename src/{recovery.rs => recovery/mod.rs} (96%) diff --git a/build.rs b/build.rs index ebc50d0..d104124 100644 --- a/build.rs +++ b/build.rs @@ -53,8 +53,10 @@ fn main() { .define("USE_FIELD_INV_BUILTIN", Some("1")) .define("USE_SCALAR_INV_BUILTIN", Some("1")) .define("USE_ENDOMORPHISM", Some("1")) - .define("ENABLE_MODULE_ECDH", Some("1")) - .define("ENABLE_MODULE_RECOVERY", Some("1")); + .define("ENABLE_MODULE_ECDH", Some("1")); + + #[cfg(feature = "recovery")] + base_config.define("ENABLE_MODULE_RECOVERY", Some("1")); if let Ok(target_endian) = env::var("CARGO_CFG_TARGET_ENDIAN") { if target_endian == "big" { diff --git a/src/ffi.rs b/src/ffi.rs index f173300..884a223 100644 --- a/src/ffi.rs +++ b/src/ffi.rs @@ -18,8 +18,6 @@ //! not be needed for most users. use core::{mem, hash}; use types::*; -// use std::os::raw::{c_int, c_uchar, c_uint, c_void}; - /// Flag for context to enable no precomputation pub const SECP256K1_START_NONE: c_uint = 1; @@ -93,12 +91,6 @@ pub struct Signature([c_uchar; 64]); impl_array_newtype!(Signature, c_uchar, 64); impl_raw_debug!(Signature); -/// Library-internal representation of a Secp256k1 signature + recovery ID -#[repr(C)] -pub struct RecoverableSignature([c_uchar; 65]); -impl_array_newtype!(RecoverableSignature, c_uchar, 65); -impl_raw_debug!(RecoverableSignature); - impl Signature { /// Create a new (zeroed) signature usable for the FFI interface pub fn new() -> Signature { Signature([0; 64]) } @@ -112,19 +104,6 @@ impl Default for Signature { } } -impl RecoverableSignature { - /// Create a new (zeroed) signature usable for the FFI interface - pub fn new() -> RecoverableSignature { RecoverableSignature([0; 65]) } - /// Create a new (uninitialized) signature usable for the FFI interface - pub unsafe fn blank() -> RecoverableSignature { mem::uninitialized() } -} - -impl Default for RecoverableSignature { - fn default() -> Self { - RecoverableSignature::new() - } -} - /// Library-internal representation of an ECDH shared secret #[repr(C)] pub struct SharedSecret([c_uchar; 32]); @@ -204,18 +183,6 @@ extern "C" { sig: *const Signature) -> c_int; - pub fn secp256k1_ecdsa_recoverable_signature_parse_compact(cx: *const Context, sig: *mut RecoverableSignature, - input64: *const c_uchar, recid: c_int) - -> c_int; - - pub fn secp256k1_ecdsa_recoverable_signature_serialize_compact(cx: *const Context, output64: *const c_uchar, - recid: *mut c_int, sig: *const RecoverableSignature) - -> c_int; - - pub fn secp256k1_ecdsa_recoverable_signature_convert(cx: *const Context, sig: *mut Signature, - input: *const RecoverableSignature) - -> c_int; - pub fn secp256k1_ecdsa_signature_normalize(cx: *const Context, out_sig: *mut Signature, in_sig: *const Signature) -> c_int; @@ -235,20 +202,6 @@ extern "C" { noncedata: *const c_void) -> c_int; - pub fn secp256k1_ecdsa_sign_recoverable(cx: *const Context, - sig: *mut RecoverableSignature, - msg32: *const c_uchar, - sk: *const c_uchar, - noncefn: NonceFn, - noncedata: *const c_void) - -> c_int; - - pub fn secp256k1_ecdsa_recover(cx: *const Context, - pk: *mut PublicKey, - sig: *const RecoverableSignature, - msg32: *const c_uchar) - -> c_int; - // EC pub fn secp256k1_ec_seckey_verify(cx: *const Context, sk: *const c_uchar) -> c_int; @@ -463,24 +416,6 @@ mod fuzz_dummy { 1 } - pub unsafe fn secp256k1_ecdsa_recoverable_signature_parse_compact(_cx: *const Context, _sig: *mut RecoverableSignature, - _input64: *const c_uchar, _recid: c_int) - -> c_int { - unimplemented!(); - } - - pub unsafe fn secp256k1_ecdsa_recoverable_signature_serialize_compact(_cx: *const Context, _output64: *const c_uchar, - _recid: *mut c_int, _sig: *const RecoverableSignature) - -> c_int { - unimplemented!(); - } - - pub unsafe fn secp256k1_ecdsa_recoverable_signature_convert(_cx: *const Context, _sig: *mut Signature, - _input: *const RecoverableSignature) - -> c_int { - unimplemented!(); - } - pub unsafe fn secp256k1_ecdsa_signature_normalize(_cx: *const Context, _out_sig: *mut Signature, _in_sig: *const Signature) -> c_int { @@ -525,35 +460,6 @@ mod fuzz_dummy { 1 } - /// Sets sig to (2|3)||msg32||sk - pub unsafe fn secp256k1_ecdsa_sign_recoverable(cx: *const Context, - sig: *mut RecoverableSignature, - msg32: *const c_uchar, - sk: *const c_uchar, - _noncefn: NonceFn, - _noncedata: *const c_void) - -> c_int { - assert!(!cx.is_null() && (*cx).0 as u32 & !(SECP256K1_START_NONE | SECP256K1_START_VERIFY | SECP256K1_START_SIGN) == 0); - assert!((*cx).0 as u32 & SECP256K1_START_SIGN == SECP256K1_START_SIGN); - if secp256k1_ec_seckey_verify(cx, sk) != 1 { return 0; } - if *sk.offset(0) > 0x7f { - (*sig).0[0] = 2; - } else { - (*sig).0[0] = 3; - } - ptr::copy(msg32, (*sig).0[1..33].as_mut_ptr(), 32); - ptr::copy(sk, (*sig).0[33..65].as_mut_ptr(), 32); - 1 - } - - pub unsafe fn secp256k1_ecdsa_recover(_cx: *const Context, - _pk: *mut PublicKey, - _sig: *const RecoverableSignature, - _msg32: *const c_uchar) - -> c_int { - unimplemented!(); - } - // EC /// Checks that pk != 0xffff...ffff and pk[0..32] == pk[32..64] pub unsafe fn test_pk_validate(cx: *const Context, diff --git a/src/recovery/ffi.rs b/src/recovery/ffi.rs new file mode 100644 index 0000000..b9842c9 --- /dev/null +++ b/src/recovery/ffi.rs @@ -0,0 +1,118 @@ +// Bitcoin secp256k1 bindings +// Written in 2014 by +// Dawid Ciężarkiewicz +// Andrew Poelstra +// +// To the extent possible under law, the author(s) have dedicated all +// copyright and related and neighboring rights to this software to +// the public domain worldwide. This software is distributed without +// any warranty. +// +// You should have received a copy of the CC0 Public Domain Dedication +// along with this software. +// If not, see . +// + +//! # FFI of the recovery module + +use core::mem; +use types::*; +use ffi::{Context, NonceFn, PublicKey, Signature}; + +/// Library-internal representation of a Secp256k1 signature + recovery ID +#[repr(C)] +pub struct RecoverableSignature([c_uchar; 65]); +impl_array_newtype!(RecoverableSignature, c_uchar, 65); +impl_raw_debug!(RecoverableSignature); + +impl RecoverableSignature { + /// Create a new (zeroed) signature usable for the FFI interface + pub fn new() -> RecoverableSignature { RecoverableSignature([0; 65]) } + /// Create a new (uninitialized) signature usable for the FFI interface + pub unsafe fn blank() -> RecoverableSignature { mem::uninitialized() } +} + +impl Default for RecoverableSignature { + fn default() -> Self { + RecoverableSignature::new() + } +} + +#[cfg(not(feature = "fuzztarget"))] +extern "C" { + pub fn secp256k1_ecdsa_recoverable_signature_parse_compact(cx: *const Context, sig: *mut RecoverableSignature, + input64: *const c_uchar, recid: c_int) + -> c_int; + + pub fn secp256k1_ecdsa_recoverable_signature_serialize_compact(cx: *const Context, output64: *const c_uchar, + recid: *mut c_int, sig: *const RecoverableSignature) + -> c_int; + + pub fn secp256k1_ecdsa_recoverable_signature_convert(cx: *const Context, sig: *mut Signature, + input: *const RecoverableSignature) + -> c_int; + pub fn secp256k1_ecdsa_sign_recoverable(cx: *const Context, + sig: *mut RecoverableSignature, + msg32: *const c_uchar, + sk: *const c_uchar, + noncefn: NonceFn, + noncedata: *const c_void) + -> c_int; + + pub fn secp256k1_ecdsa_recover(cx: *const Context, + pk: *mut PublicKey, + sig: *const RecoverableSignature, + msg32: *const c_uchar) + -> c_int; +} + + +#[cfg(feature = "fuzztarget")] +mod fuzz_dummy { + pub unsafe fn secp256k1_ecdsa_recoverable_signature_parse_compact(_cx: *const Context, _sig: *mut RecoverableSignature, + _input64: *const c_uchar, _recid: c_int) + -> c_int { + unimplemented!(); + } + + pub unsafe fn secp256k1_ecdsa_recoverable_signature_serialize_compact(_cx: *const Context, _output64: *const c_uchar, + _recid: *mut c_int, _sig: *const RecoverableSignature) + -> c_int { + unimplemented!(); + } + + pub unsafe fn secp256k1_ecdsa_recoverable_signature_convert(_cx: *const Context, _sig: *mut Signature, + _input: *const RecoverableSignature) + -> c_int { + unimplemented!(); + } + + /// Sets sig to (2|3)||msg32||sk + pub unsafe fn secp256k1_ecdsa_sign_recoverable(cx: *const Context, + sig: *mut RecoverableSignature, + msg32: *const c_uchar, + sk: *const c_uchar, + _noncefn: NonceFn, + _noncedata: *const c_void) + -> c_int { + assert!(!cx.is_null() && (*cx).0 as u32 & !(SECP256K1_START_NONE | SECP256K1_START_VERIFY | SECP256K1_START_SIGN) == 0); + assert!((*cx).0 as u32 & SECP256K1_START_SIGN == SECP256K1_START_SIGN); + if secp256k1_ec_seckey_verify(cx, sk) != 1 { return 0; } + if *sk.offset(0) > 0x7f { + (*sig).0[0] = 2; + } else { + (*sig).0[0] = 3; + } + ptr::copy(msg32, (*sig).0[1..33].as_mut_ptr(), 32); + ptr::copy(sk, (*sig).0[33..65].as_mut_ptr(), 32); + 1 + } + + pub unsafe fn secp256k1_ecdsa_recover(_cx: *const Context, + _pk: *mut PublicKey, + _sig: *const RecoverableSignature, + _msg32: *const c_uchar) + -> c_int { + unimplemented!(); + } +} diff --git a/src/recovery.rs b/src/recovery/mod.rs similarity index 96% rename from src/recovery.rs rename to src/recovery/mod.rs index 73e5367..fc88bf5 100644 --- a/src/recovery.rs +++ b/src/recovery/mod.rs @@ -1,4 +1,3 @@ -// TODO header // Bitcoin secp256k1 bindings // Written in 2014 by // Dawid Ciężarkiewicz @@ -19,12 +18,14 @@ //! signature. use core::ptr; -use ffi; use key; use super::{Secp256k1, Message, Error, Signature, Verification, Signing}; +use super::ffi as super_ffi; pub use key::SecretKey; pub use key::PublicKey; +mod ffi; + /// A tag used for recovering the public key from a compact signature #[derive(Copy, Clone, PartialEq, Eq, Debug)] pub struct RecoveryId(i32); @@ -36,7 +37,6 @@ pub struct RecoverableSignature(ffi::RecoverableSignature); impl RecoveryId { #[inline] /// Allows library users to create valid recovery IDs from i32. -/// TODO pub fn from_i32(id: i32) -> Result { match id { 0 | 1 | 2 | 3 => Ok(RecoveryId(id)), @@ -63,7 +63,7 @@ impl RecoverableSignature { if data.len() != 64 { Err(Error::InvalidSignature) } else if ffi::secp256k1_ecdsa_recoverable_signature_parse_compact( - ffi::secp256k1_context_no_precomp, + super_ffi::secp256k1_context_no_precomp, &mut ret, data.as_ptr(), recid.0, @@ -89,7 +89,7 @@ impl RecoverableSignature { let mut recid = 0i32; unsafe { let err = ffi::secp256k1_ecdsa_recoverable_signature_serialize_compact( - ffi::secp256k1_context_no_precomp, + super_ffi::secp256k1_context_no_precomp, ret.as_mut_ptr(), &mut recid, self.as_ptr(), @@ -103,10 +103,10 @@ impl RecoverableSignature { /// for verification #[inline] pub fn to_standard(&self) -> Signature { - let mut ret = unsafe { ffi::Signature::blank() }; + let mut ret = unsafe { super_ffi::Signature::blank() }; unsafe { let err = ffi::secp256k1_ecdsa_recoverable_signature_convert( - ffi::secp256k1_context_no_precomp, + super_ffi::secp256k1_context_no_precomp, &mut ret, self.as_ptr(), ); @@ -140,7 +140,7 @@ impl Secp256k1 { &mut ret, msg.as_ptr(), sk.as_ptr(), - ffi::secp256k1_nonce_function_rfc6979, + super_ffi::secp256k1_nonce_function_rfc6979, ptr::null() ), 1 @@ -157,7 +157,7 @@ impl Secp256k1 { pub fn recover(&self, msg: &Message, sig: &RecoverableSignature) -> Result { - let mut pk = unsafe { ffi::PublicKey::blank() }; + let mut pk = unsafe { super_ffi::PublicKey::blank() }; unsafe { if ffi::secp256k1_ecdsa_recover(self.ctx, &mut pk,