Fix for upstream API changes; add ECDH support

I didn't mean for both of these to go into the same commit, but given how
small the ECDH code was, and the fact that no commit prior to this one will
compile (as both libsecp256k1 and rustc have changed so much), I'm letting
it slide.
This commit is contained in:
Andrew Poelstra 2015-09-18 15:22:48 -05:00
parent 8d6f384dac
commit 016d781f2e
4 changed files with 317 additions and 70 deletions

View File

@ -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 ..
- |

130
src/ecdh.rs Normal file
View File

@ -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 <http://creativecommons.org/publicdomain/zero/1.0/>.
//
//! # 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<usize> for SharedSecret {
type Output = u8;
#[inline]
fn index(&self, index: usize) -> &u8 {
&self.0[index]
}
}
impl ops::Index<ops::Range<usize>> for SharedSecret {
type Output = [u8];
#[inline]
fn index(&self, index: ops::Range<usize>) -> &[u8] {
&self.0[index]
}
}
impl ops::Index<ops::RangeFrom<usize>> for SharedSecret {
type Output = [u8];
#[inline]
fn index(&self, index: ops::RangeFrom<usize>) -> &[u8] {
&self.0[index.start..]
}
}
impl ops::Index<ops::RangeFull> 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);
});
}
}

View File

@ -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;
}

View File

@ -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<Signature, Error> {
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<RecoverableSignature, Error> {
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<usize> 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<RecoverableSignature, Error> {
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<key::PublicKey, Error> {
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,