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,