Skip to content

Commit 1cf2429

Browse files
committed
Merge #425: Add sign_ecdsa_with_noncedata and sign_ecdsa_recoverable_with_noncedata
f93ca81 Add sign_ecdsa_with_noncedata and sign_ecdsa_recoverable_with_noncedata (junderw) Pull request description: Fixes #424 As discussed on [IRC](https://gnusha.org/bitcoin-rust/2022-03-19.log) (starts at 09:14). These methods will allow for users to generate multiple signatures with the same private key and message by utilizing one of the `Variants` mention in RFC6979 which is exposed by libsecp256k1 via the `noncedata` argument. The reasoning behind adding this is to allow our library to migrate from using the -sys crate. Currently we support using this noncedata argument, and would like to continue doing so while at the same time migrating away from -sys crate. ACKs for top commit: apoelstra: ACK f93ca81 Tree-SHA512: 494d4f9046960779e199b18ff908fe74feda66a5cfc066c9ae6f3836fcaabd56defaa2138a913b25f1af3aa7dd48986e058804223224b76b303837c0c7adbaed
2 parents 49905b0 + f93ca81 commit 1cf2429

File tree

3 files changed

+121
-8
lines changed

3 files changed

+121
-8
lines changed

src/ecdsa/mod.rs

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -340,20 +340,44 @@ impl<C: Signing> Secp256k1<C> {
340340
self.sign_ecdsa(msg, sk)
341341
}
342342

343-
/// Constructs a signature for `msg` using the secret key `sk` and RFC6979 nonce
344-
/// Requires a signing-capable context.
345-
pub fn sign_ecdsa(&self, msg: &Message, sk: &SecretKey) -> Signature {
343+
fn sign_ecdsa_with_noncedata_pointer(
344+
&self,
345+
msg: &Message,
346+
sk: &SecretKey,
347+
noncedata_ptr: *const ffi::types::c_void,
348+
) -> Signature {
346349
unsafe {
347350
let mut ret = ffi::Signature::new();
348351
// We can assume the return value because it's not possible to construct
349352
// an invalid signature from a valid `Message` and `SecretKey`
350353
assert_eq!(ffi::secp256k1_ecdsa_sign(self.ctx, &mut ret, msg.as_c_ptr(),
351354
sk.as_c_ptr(), ffi::secp256k1_nonce_function_rfc6979,
352-
ptr::null()), 1);
355+
noncedata_ptr), 1);
353356
Signature::from(ret)
354357
}
355358
}
356359

360+
/// Constructs a signature for `msg` using the secret key `sk` and RFC6979 nonce
361+
/// Requires a signing-capable context.
362+
pub fn sign_ecdsa(&self, msg: &Message, sk: &SecretKey) -> Signature {
363+
self.sign_ecdsa_with_noncedata_pointer(msg, sk, ptr::null())
364+
}
365+
366+
/// Constructs a signature for `msg` using the secret key `sk` and RFC6979 nonce
367+
/// and includes 32 bytes of noncedata in the nonce generation via inclusion in
368+
/// one of the hash operations during nonce generation. This is useful when multiple
369+
/// signatures are needed for the same Message and SecretKey while still using RFC6979.
370+
/// Requires a signing-capable context.
371+
pub fn sign_ecdsa_with_noncedata(
372+
&self,
373+
msg: &Message,
374+
sk: &SecretKey,
375+
noncedata: &[u8; 32],
376+
) -> Signature {
377+
let noncedata_ptr = noncedata.as_ptr() as *const ffi::types::c_void;
378+
self.sign_ecdsa_with_noncedata_pointer(msg, sk, noncedata_ptr)
379+
}
380+
357381
fn sign_grind_with_check(
358382
&self, msg: &Message,
359383
sk: &SecretKey,

src/ecdsa/recovery.rs

Lines changed: 73 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -159,9 +159,12 @@ impl<C: Signing> Secp256k1<C> {
159159
self.sign_ecdsa_recoverable(msg, sk)
160160
}
161161

162-
/// Constructs a signature for `msg` using the secret key `sk` and RFC6979 nonce
163-
/// Requires a signing-capable context.
164-
pub fn sign_ecdsa_recoverable(&self, msg: &Message, sk: &key::SecretKey) -> RecoverableSignature {
162+
fn sign_ecdsa_recoverable_with_noncedata_pointer(
163+
&self,
164+
msg: &Message,
165+
sk: &key::SecretKey,
166+
noncedata_ptr: *const super_ffi::types::c_void,
167+
) -> RecoverableSignature {
165168
let mut ret = ffi::RecoverableSignature::new();
166169
unsafe {
167170
// We can assume the return value because it's not possible to construct
@@ -173,14 +176,35 @@ impl<C: Signing> Secp256k1<C> {
173176
msg.as_c_ptr(),
174177
sk.as_c_ptr(),
175178
super_ffi::secp256k1_nonce_function_rfc6979,
176-
ptr::null()
179+
noncedata_ptr
177180
),
178181
1
179182
);
180183
}
181184

182185
RecoverableSignature::from(ret)
183186
}
187+
188+
/// Constructs a signature for `msg` using the secret key `sk` and RFC6979 nonce
189+
/// Requires a signing-capable context.
190+
pub fn sign_ecdsa_recoverable(&self, msg: &Message, sk: &key::SecretKey) -> RecoverableSignature {
191+
self.sign_ecdsa_recoverable_with_noncedata_pointer(msg, sk, ptr::null())
192+
}
193+
194+
/// Constructs a signature for `msg` using the secret key `sk` and RFC6979 nonce
195+
/// and includes 32 bytes of noncedata in the nonce generation via inclusion in
196+
/// one of the hash operations during nonce generation. This is useful when multiple
197+
/// signatures are needed for the same Message and SecretKey while still using RFC6979.
198+
/// Requires a signing-capable context.
199+
pub fn sign_ecdsa_recoverable_with_noncedata(
200+
&self,
201+
msg: &Message,
202+
sk: &key::SecretKey,
203+
noncedata: &[u8; 32],
204+
) -> RecoverableSignature {
205+
let noncedata_ptr = noncedata.as_ptr() as *const super_ffi::types::c_void;
206+
self.sign_ecdsa_recoverable_with_noncedata_pointer(msg, sk, noncedata_ptr)
207+
}
184208
}
185209

186210
impl<C: Verification> Secp256k1<C> {
@@ -276,6 +300,32 @@ mod tests {
276300
RecoveryId(1)))
277301
}
278302

303+
#[test]
304+
#[cfg(not(fuzzing))] // fixed sig vectors can't work with fuzz-sigs
305+
#[cfg(all(feature="std", feature = "rand-std"))]
306+
fn sign_with_noncedata() {
307+
let mut s = Secp256k1::new();
308+
s.randomize(&mut thread_rng());
309+
let one: [u8; 32] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
310+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1];
311+
312+
let sk = SecretKey::from_slice(&one).unwrap();
313+
let msg = Message::from_slice(&one).unwrap();
314+
let noncedata = [42u8; 32];
315+
316+
let sig = s.sign_ecdsa_recoverable_with_noncedata(&msg, &sk, &noncedata);
317+
assert_eq!(Ok(sig), RecoverableSignature::from_compact(&[
318+
0xb5, 0x0b, 0xb6, 0x79, 0x5f, 0x31, 0x74, 0x8a,
319+
0x4d, 0x37, 0xc3, 0xa9, 0x7e, 0xbd, 0x06, 0xa2,
320+
0x2e, 0xa3, 0x37, 0x71, 0x04, 0x0f, 0x5c, 0x05,
321+
0xd6, 0xe2, 0xbb, 0x2d, 0x38, 0xc6, 0x22, 0x7c,
322+
0x34, 0x3b, 0x66, 0x59, 0xdb, 0x96, 0x99, 0x59,
323+
0xd9, 0xfd, 0xdb, 0x44, 0xbd, 0x0d, 0xd9, 0xb9,
324+
0xdd, 0x47, 0x66, 0x6a, 0xb5, 0x28, 0x71, 0x90,
325+
0x1d, 0x17, 0x61, 0xeb, 0x82, 0xec, 0x87, 0x22],
326+
RecoveryId(0)))
327+
}
328+
279329
#[test]
280330
#[cfg(all(feature="std", feature = "rand-std"))]
281331
fn sign_and_verify_fail() {
@@ -317,6 +367,25 @@ mod tests {
317367
assert_eq!(s.recover_ecdsa(&msg, &sig), Ok(pk));
318368
}
319369

370+
#[test]
371+
#[cfg(all(feature="std", feature = "rand-std"))]
372+
fn sign_with_recovery_and_noncedata() {
373+
let mut s = Secp256k1::new();
374+
s.randomize(&mut thread_rng());
375+
376+
let mut msg = [0u8; 32];
377+
thread_rng().fill_bytes(&mut msg);
378+
let msg = Message::from_slice(&msg).unwrap();
379+
380+
let noncedata = [42u8; 32];
381+
382+
let (sk, pk) = s.generate_keypair(&mut thread_rng());
383+
384+
let sig = s.sign_ecdsa_recoverable_with_noncedata(&msg, &sk, &noncedata);
385+
386+
assert_eq!(s.recover_ecdsa(&msg, &sig), Ok(pk));
387+
}
388+
320389
#[test]
321390
#[cfg(all(feature="std", feature = "rand-std"))]
322391
fn bad_recovery() {

src/lib.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -807,13 +807,16 @@ mod tests {
807807
s.randomize(&mut thread_rng());
808808

809809
let mut msg = [0u8; 32];
810+
let noncedata = [42u8; 32];
810811
for _ in 0..100 {
811812
thread_rng().fill_bytes(&mut msg);
812813
let msg = Message::from_slice(&msg).unwrap();
813814

814815
let (sk, pk) = s.generate_keypair(&mut thread_rng());
815816
let sig = s.sign_ecdsa(&msg, &sk);
816817
assert_eq!(s.verify_ecdsa(&msg, &sig, &pk), Ok(()));
818+
let noncedata_sig = s.sign_ecdsa_with_noncedata(&msg, &sk, &noncedata);
819+
assert_eq!(s.verify_ecdsa(&msg, &noncedata_sig, &pk), Ok(()));
817820
let low_r_sig = s.sign_ecdsa_low_r(&msg, &sk);
818821
assert_eq!(s.verify_ecdsa(&msg, &low_r_sig, &pk), Ok(()));
819822
let grind_r_sig = s.sign_ecdsa_grind_r(&msg, &sk, 1);
@@ -927,6 +930,23 @@ mod tests {
927930
assert!(from_hex("ag", &mut [0u8; 4]).is_err());
928931
}
929932

933+
#[test]
934+
#[cfg(not(fuzzing))] // fuzz-sigs have fixed size/format
935+
#[cfg(any(feature = "alloc", feature = "std"))]
936+
fn test_noncedata() {
937+
let secp = Secp256k1::new();
938+
let msg = hex!("887d04bb1cf1b1554f1b268dfe62d13064ca67ae45348d50d1392ce2d13418ac");
939+
let msg = Message::from_slice(&msg).unwrap();
940+
let noncedata = [42u8; 32];
941+
let sk = SecretKey::from_str("57f0148f94d13095cfda539d0da0d1541304b678d8b36e243980aab4e1b7cead").unwrap();
942+
let expected_sig = hex!("24861b3edd4e7da43319c635091405feced6efa4ec99c3c3c35f6c3ba0ed8816116772e84994084db85a6c20589f6a85af569d42275c2a5dd900da5776b99d5d");
943+
let expected_sig = ecdsa::Signature::from_compact(&expected_sig).unwrap();
944+
945+
let sig = secp.sign_ecdsa_with_noncedata(&msg, &sk, &noncedata);
946+
947+
assert_eq!(expected_sig, sig);
948+
}
949+
930950
#[test]
931951
#[cfg(not(fuzzing))] // fixed sig vectors can't work with fuzz-sigs
932952
#[cfg(any(feature = "alloc", feature = "std"))]

0 commit comments

Comments
 (0)