diff --git a/.travis.yml b/.travis.yml index 5a712bc..eb86ecf 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,7 +9,7 @@ matrix: install: - git clone https://github.com/bitcoin/secp256k1.git - cd secp256k1 - - ./autogen.sh && ./configure && make && sudo make install + - ./autogen.sh && ./configure --enable-module-ecdh --enable-module-recovery && make && sudo make install - sudo ldconfig /usr/local/lib - cd .. - | diff --git a/src/ecdh.rs b/src/ecdh.rs new file mode 100644 index 0000000..039caf1 --- /dev/null +++ b/src/ecdh.rs @@ -0,0 +1,130 @@ +// Bitcoin secp256k1 bindings +// Written in 2015 by +// 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 . +// + +//! # ECDH +//! Support for shared secret computations +//! + +use std::ops; + +use super::Secp256k1; +use key::{SecretKey, PublicKey}; +use ffi; + +/// A tag used for recovering the public key from a compact signature +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +pub struct SharedSecret(ffi::SharedSecret); + +impl SharedSecret { + /// Creates a new shared secret from a pubkey and secret key + #[inline] + pub fn new(secp: &Secp256k1, point: &PublicKey, scalar: &SecretKey) -> SharedSecret { + unsafe { + let mut ss = ffi::SharedSecret::blank(); + let res = ffi::secp256k1_ecdh(secp.ctx, &mut ss, point.as_ptr(), scalar.as_ptr()); + debug_assert_eq!(res, 1); + SharedSecret(ss) + } + } + + /// Creates a new shared secret from a FFI shared secret + #[inline] + pub fn from_ffi(ss: ffi::SharedSecret) -> SharedSecret { + SharedSecret(ss) + } + + /// Obtains a raw pointer suitable for use with FFI functions + #[inline] + pub fn as_ptr(&self) -> *const ffi::SharedSecret { + &self.0 as *const _ + } +} + +impl ops::Index for SharedSecret { + type Output = u8; + + #[inline] + fn index(&self, index: usize) -> &u8 { + &self.0[index] + } +} + +impl ops::Index> for SharedSecret { + type Output = [u8]; + + #[inline] + fn index(&self, index: ops::Range) -> &[u8] { + &self.0[index] + } +} + +impl ops::Index> for SharedSecret { + type Output = [u8]; + + #[inline] + fn index(&self, index: ops::RangeFrom) -> &[u8] { + &self.0[index.start..] + } +} + +impl ops::Index for SharedSecret { + type Output = [u8]; + + #[inline] + fn index(&self, _: ops::RangeFull) -> &[u8] { + &self.0[..] + } +} + +#[cfg(test)] +mod tests { + use rand::thread_rng; + use super::SharedSecret; + use super::super::Secp256k1; + + #[test] + fn ecdh() { + let s = Secp256k1::with_caps(::ContextFlag::SignOnly); + let (sk1, pk1) = s.generate_keypair(&mut thread_rng()).unwrap(); + let (sk2, pk2) = s.generate_keypair(&mut thread_rng()).unwrap(); + + let sec1 = SharedSecret::new(&s, &pk1, &sk2); + let sec2 = SharedSecret::new(&s, &pk2, &sk1); + let sec_odd = SharedSecret::new(&s, &pk1, &sk1); + assert_eq!(sec1, sec2); + assert!(sec_odd != sec2); + } +} + +#[cfg(all(test, feature = "unstable"))] +mod benches { + use rand::{Rng, thread_rng}; + use test::{Bencher, black_box}; + + use super::{Secp256k1, Message}; + + #[bench] + pub fn bench_ecdh(bh: &mut Bencher) { + let s = Secp256k1::with_caps(::ContextFlag::SignOnly); + let (sk, pk) = s.generate_keypair(&mut thread_rng()).unwrap(); + + let s = Secp256k1::new(); + let mut r = CounterRng(0); + bh.iter( || { + let res = SharedSecret::new(&s, &pk, &sk); + black_box(res); + }); + } +} + diff --git a/src/ffi.rs b/src/ffi.rs index c24cb33..dccf464 100644 --- a/src/ffi.rs +++ b/src/ffi.rs @@ -64,17 +64,45 @@ impl PublicKey { /// Library-internal representation of a Secp256k1 signature #[repr(C)] #[allow(raw_pointer_derive)] -pub struct Signature([c_uchar; 65]); -impl_array_newtype!(Signature, c_uchar, 65); +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)] +#[allow(raw_pointer_derive)] +pub struct RecoverableSignature([c_uchar; 65]); +impl_array_newtype!(RecoverableSignature, c_uchar, 65); +impl_raw_debug!(RecoverableSignature); + impl Signature { - /// Create a new (zeroed) public key usable for the FFI interface - pub fn new() -> Signature { Signature([0; 65]) } - /// Create a new (uninitialized) public key usable for the FFI interface + /// Create a new (zeroed) signature usable for the FFI interface + pub fn new() -> Signature { Signature([0; 64]) } + /// Create a new (uninitialized) signature usable for the FFI interface pub unsafe fn blank() -> Signature { mem::uninitialized() } } +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() } +} + +/// Library-internal representation of an ECDH shared secret +#[repr(C)] +#[allow(raw_pointer_derive)] +pub struct SharedSecret([c_uchar; 32]); +impl_array_newtype!(SharedSecret, c_uchar, 32); +impl_raw_debug!(SharedSecret); + +impl SharedSecret { + /// Create a new (zeroed) signature usable for the FFI interface + pub fn new() -> SharedSecret { SharedSecret([0; 32]) } + /// Create a new (uninitialized) signature usable for the FFI interface + pub unsafe fn blank() -> SharedSecret { mem::uninitialized() } +} + unsafe impl Send for Context {} unsafe impl Sync for Context {} @@ -95,6 +123,13 @@ extern "C" { seed32: *const c_uchar) -> c_int; + // TODO secp256k1_context_set_illegal_callback + // TODO secp256k1_context_set_error_callback + // (Actually, I don't really want these exposed; if either of these + // are ever triggered it indicates a bug in rust-secp256k1, since + // one goal is to use Rust's type system to eliminate all possible + // bad inputs.) + // Pubkeys pub fn secp256k1_ec_pubkey_parse(cx: Context, pk: *mut PublicKey, input: *const c_uchar, in_len: c_int) @@ -110,30 +145,49 @@ extern "C" { input: *const c_uchar, in_len: c_int) -> c_int; - pub fn secp256k1_ecdsa_signature_parse_compact(cx: Context, sig: *mut Signature, - input64: *const c_uchar, recid: c_int) - -> c_int; - pub fn secp256k1_ecdsa_signature_serialize_der(cx: Context, output: *const c_uchar, out_len: c_int, sig: *const Signature) -> c_int; - pub fn secp256k1_ecdsa_signature_serialize_compact(cx: Context, output64: *const c_uchar, - recid: *mut c_int, sig: *const Signature) - -> c_int; + pub fn secp256k1_ecdsa_recoverable_signature_parse_compact(cx: Context, sig: *mut RecoverableSignature, + input64: *const c_uchar, recid: c_int) + -> c_int; + + pub fn secp256k1_ecdsa_recoverable_signature_serialize_compact(cx: Context, output64: *const c_uchar, + recid: *mut c_int, sig: *const RecoverableSignature) + -> c_int; + + pub fn secp256k1_ecdsa_recoverable_signature_convert(cx: Context, sig: *mut Signature, + input: *const RecoverableSignature) + -> c_int; // ECDSA - pub fn secp256k1_ecdsa_verify(cx: Context, msg32: *const c_uchar, - sig: *const Signature, pk: *const PublicKey) + pub fn secp256k1_ecdsa_verify(cx: Context, + sig: *const Signature, + msg32: *const c_uchar, + pk: *const PublicKey) -> c_int; - pub fn secp256k1_ecdsa_sign(cx: Context, msg32: *const c_uchar, - sig: *mut Signature, sk: *const c_uchar, - noncefn: NonceFn, noncedata: *const c_void) + pub fn secp256k1_ecdsa_sign(cx: Context, + sig: *mut Signature, + msg32: *const c_uchar, + sk: *const c_uchar, + noncefn: NonceFn, + noncedata: *const c_void) -> c_int; - pub fn secp256k1_ecdsa_recover(cx: Context, msg32: *const c_uchar, - sig: *const Signature, pk: *mut PublicKey) + pub fn secp256k1_ecdsa_sign_recoverable(cx: 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: Context, + pk: *mut PublicKey, + sig: *const RecoverableSignature, + msg32: *const c_uchar) -> c_int; // EC @@ -168,8 +222,14 @@ extern "C" { pub fn secp256k1_ec_pubkey_combine(cx: Context, out: *mut PublicKey, - n: c_int, - ins: *const *const PublicKey) + ins: *const *const PublicKey, + n: c_int) -> c_int; + + pub fn secp256k1_ecdh(cx: Context, + out: *mut SharedSecret, + point: *const PublicKey, + scalar: *const c_uchar) + -> c_int; } diff --git a/src/lib.rs b/src/lib.rs index 825d785..3b33836 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -50,6 +50,7 @@ use rand::Rng; #[macro_use] mod macros; pub mod constants; +pub mod ecdh; pub mod ffi; pub mod key; @@ -61,6 +62,10 @@ pub struct RecoveryId(i32); #[derive(Copy, Clone, PartialEq, Eq, Debug)] pub struct Signature(ffi::Signature); +/// An ECDSA signature with a recovery ID for pubkey recovery +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +pub struct RecoverableSignature(ffi::RecoverableSignature); + impl Signature { #[inline] /// Converts a DER-encoded byte slice to a signature @@ -77,25 +82,6 @@ impl Signature { } } - #[inline] - /// Converts a compact-encoded byte slice to a signature. This - /// representation is nonstandard and defined by the libsecp256k1 - /// library. - pub fn from_compact(secp: &Secp256k1, data: &[u8], recid: RecoveryId) -> Result { - let mut ret = unsafe { ffi::Signature::blank() }; - - unsafe { - if data.len() != 64 { - Err(Error::InvalidSignature) - } else if ffi::secp256k1_ecdsa_signature_parse_compact(secp.ctx, &mut ret, - data.as_ptr(), recid.0) == 1 { - Ok(Signature(ret)) - } else { - Err(Error::InvalidSignature) - } - } - } - /// Creates a new public key from a FFI public key #[inline] pub fn from_ffi(sig: ffi::Signature) -> Signature { @@ -109,6 +95,51 @@ impl 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(secp: &Secp256k1, 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(secp.ctx, &mut ret, + data.as_ptr(), recid.0) == 1 { + Ok(RecoverableSignature(ret)) + } else { + Err(Error::InvalidSignature) + } + } + } + + /// Creates a new public key from a FFI public key + #[inline] + pub fn from_ffi(sig: ffi::RecoverableSignature) -> RecoverableSignature { + RecoverableSignature(sig) + } + + /// Obtains a raw pointer suitable for use with FFI functions + #[inline] + pub fn as_ptr(&self) -> *const ffi::RecoverableSignature { + &self.0 as *const _ + } + + /// Converts a recoverable signature to a non-recoverable one (this is needed + /// for verification + #[inline] + pub fn to_standard(&self, secp: &Secp256k1) -> Signature { + let mut ret = unsafe { ffi::Signature::blank() }; + unsafe { + let err = ffi::secp256k1_ecdsa_recoverable_signature_convert(secp.ctx, &mut ret, self.as_ptr()); + assert!(err == 1); + } + Signature(ret) + } +} + impl ops::Index for Signature { type Output = u8; @@ -314,17 +345,36 @@ impl Secp256k1 { 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(self.ctx, msg.as_ptr(), &mut ret, + assert_eq!(ffi::secp256k1_ecdsa_sign(self.ctx, &mut ret, msg.as_ptr(), sk.as_ptr(), ffi::secp256k1_nonce_function_rfc6979, ptr::null()), 1); } Ok(Signature::from_ffi(ret)) } + /// Constructs a signature for `msg` using the secret key `sk` and nonce `nonce`. + /// Requires a signing-capable context. + pub fn sign_recoverable(&self, msg: &Message, sk: &key::SecretKey) + -> Result { + if self.caps == ContextFlag::VerifyOnly || self.caps == ContextFlag::None { + return Err(Error::IncapableContext); + } + + 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); + } + Ok(RecoverableSignature::from_ffi(ret)) + } + /// Determines the public key for which `sig` is a valid signature for /// `msg`. Returns through the out-pointer `pubkey`. Requires a verify-capable /// context. - pub fn recover(&self, msg: &Message, sig: &Signature) + pub fn recover(&self, msg: &Message, sig: &RecoverableSignature) -> Result { if self.caps == ContextFlag::SignOnly || self.caps == ContextFlag::None { return Err(Error::IncapableContext); @@ -333,8 +383,8 @@ impl Secp256k1 { let mut pk = unsafe { ffi::PublicKey::blank() }; unsafe { - if ffi::secp256k1_ecdsa_recover(self.ctx, msg.as_ptr(), - &sig.0, &mut pk) != 1 { + if ffi::secp256k1_ecdsa_recover(self.ctx, &mut pk, + sig.as_ptr(), msg.as_ptr()) != 1 { return Err(Error::InvalidSignature); } }; @@ -354,8 +404,8 @@ impl Secp256k1 { if !pk.is_valid() { Err(Error::InvalidPublicKey) - } else if unsafe { ffi::secp256k1_ecdsa_verify(self.ctx, msg.as_ptr(), - &sig.0, pk.as_ptr()) } == 0 { + } else if unsafe { ffi::secp256k1_ecdsa_verify(self.ctx, sig.as_ptr(), msg.as_ptr(), + pk.as_ptr()) } == 0 { Err(Error::IncorrectSignature) } else { Ok(()) @@ -370,7 +420,7 @@ mod tests { use key::{SecretKey, PublicKey}; use super::constants; - use super::{Secp256k1, Signature, Message, RecoveryId, ContextFlag}; + use super::{Secp256k1, Signature, RecoverableSignature, Message, RecoveryId, ContextFlag}; use super::Error::{InvalidMessage, InvalidPublicKey, IncorrectSignature, InvalidSignature, IncapableContext}; @@ -397,8 +447,14 @@ mod tests { assert_eq!(vrfy.sign(&msg, &sk), Err(IncapableContext)); assert!(sign.sign(&msg, &sk).is_ok()); assert!(full.sign(&msg, &sk).is_ok()); + assert_eq!(none.sign_recoverable(&msg, &sk), Err(IncapableContext)); + assert_eq!(vrfy.sign_recoverable(&msg, &sk), Err(IncapableContext)); + assert!(sign.sign_recoverable(&msg, &sk).is_ok()); + assert!(full.sign_recoverable(&msg, &sk).is_ok()); 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).unwrap(); + let sigr = full.sign_recoverable(&msg, &sk).unwrap(); // Try verifying assert_eq!(none.verify(&msg, &sig, &pk), Err(IncapableContext)); @@ -407,18 +463,18 @@ mod tests { assert!(full.verify(&msg, &sig, &pk).is_ok()); // Try pk recovery - assert_eq!(none.recover(&msg, &sig), Err(IncapableContext)); - assert_eq!(none.recover(&msg, &sig), Err(IncapableContext)); - assert_eq!(sign.recover(&msg, &sig), Err(IncapableContext)); - assert_eq!(sign.recover(&msg, &sig), Err(IncapableContext)); - assert!(vrfy.recover(&msg, &sig).is_ok()); - assert!(vrfy.recover(&msg, &sig).is_ok()); - assert!(full.recover(&msg, &sig).is_ok()); - assert!(full.recover(&msg, &sig).is_ok()); + assert_eq!(none.recover(&msg, &sigr), Err(IncapableContext)); + assert_eq!(none.recover(&msg, &sigr), Err(IncapableContext)); + assert_eq!(sign.recover(&msg, &sigr), Err(IncapableContext)); + assert_eq!(sign.recover(&msg, &sigr), Err(IncapableContext)); + assert!(vrfy.recover(&msg, &sigr).is_ok()); + assert!(vrfy.recover(&msg, &sigr).is_ok()); + assert!(full.recover(&msg, &sigr).is_ok()); + assert!(full.recover(&msg, &sigr).is_ok()); - assert_eq!(vrfy.recover(&msg, &sig), - full.recover(&msg, &sig)); - assert_eq!(full.recover(&msg, &sig), Ok(pk)); + 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_vec(&none, true), &sk[..]); @@ -437,13 +493,13 @@ mod tests { #[test] fn invalid_pubkey() { let s = Secp256k1::new(); - let sig = Signature::from_compact(&s, &[1; 64], RecoveryId(0)).unwrap(); + let sig = RecoverableSignature::from_compact(&s, &[1; 64], RecoveryId(0)).unwrap(); let pk = PublicKey::new(); 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(InvalidPublicKey)); + assert_eq!(s.verify(&msg, &sig.to_standard(&s), &pk), Err(InvalidPublicKey)); } #[test] @@ -456,8 +512,8 @@ mod tests { let sk = SecretKey::from_slice(&s, &one).unwrap(); let msg = Message::from_slice(&one).unwrap(); - let sig = s.sign(&msg, &sk).unwrap(); - assert_eq!(Ok(sig), Signature::from_compact(&s, &[ + let sig = s.sign_recoverable(&msg, &sk).unwrap(); + assert_eq!(Ok(sig), RecoverableSignature::from_compact(&s, &[ 0x66, 0x73, 0xff, 0xad, 0x21, 0x47, 0x74, 0x1f, 0x04, 0x77, 0x2b, 0x6f, 0x92, 0x1f, 0x0b, 0xa6, 0xaf, 0x0c, 0x1e, 0x77, 0xfc, 0x43, 0x9e, 0x65, @@ -533,14 +589,15 @@ mod tests { let (sk, pk) = s.generate_keypair(&mut thread_rng()).unwrap(); - let sig = s.sign(&msg, &sk).unwrap(); + let sigr = s.sign_recoverable(&msg, &sk).unwrap(); + let sig = sigr.to_standard(&s); 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, &sig).unwrap(); + let recovered_key = s.recover(&msg, &sigr).unwrap(); assert!(recovered_key != pk); } @@ -555,7 +612,7 @@ mod tests { let (sk, pk) = s.generate_keypair(&mut thread_rng()).unwrap(); - let sig = s.sign(&msg, &sk).unwrap(); + let sig = s.sign_recoverable(&msg, &sk).unwrap(); assert_eq!(s.recover(&msg, &sig), Ok(pk)); } @@ -568,10 +625,10 @@ mod tests { let msg = Message::from_slice(&[0x55; 32]).unwrap(); // Zero is not a valid sig - let sig = Signature::from_compact(&s, &[0; 64], RecoveryId(0)).unwrap(); + let sig = RecoverableSignature::from_compact(&s, &[0; 64], RecoveryId(0)).unwrap(); assert_eq!(s.recover(&msg, &sig), Err(InvalidSignature)); // ...but 111..111 is - let sig = Signature::from_compact(&s, &[1; 64], RecoveryId(0)).unwrap(); + let sig = RecoverableSignature::from_compact(&s, &[1; 64], RecoveryId(0)).unwrap(); assert!(s.recover(&msg, &sig).is_ok()); } @@ -593,7 +650,7 @@ mod tests { #[test] fn test_debug_output() { let s = Secp256k1::new(); - let sig = Signature::from_compact(&s, &[ + let sig = RecoverableSignature::from_compact(&s, &[ 0x66, 0x73, 0xff, 0xad, 0x21, 0x47, 0x74, 0x1f, 0x04, 0x77, 0x2b, 0x6f, 0x92, 0x1f, 0x0b, 0xa6, 0xaf, 0x0c, 0x1e, 0x77, 0xfc, 0x43, 0x9e, 0x65, @@ -603,7 +660,7 @@ mod tests { 0xff, 0x20, 0x80, 0xc4, 0xa3, 0x9a, 0xae, 0x06, 0x8d, 0x12, 0xee, 0xd0, 0x09, 0xb6, 0x8c, 0x89], RecoveryId(1)).unwrap(); - assert_eq!(&format!("{:?}", sig), "Signature(98882e09f4ed6dc3659e43fc771e0cafa60b1f926f2b77041f744721adff7366898cb609d0ee128d06ae9aa3c48020ff9f705e02f80e1280a8ade05216971a4c01)"); + 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,