From a68e5455f900787226f85f54a3088df2defd2b87 Mon Sep 17 00:00:00 2001 From: "Tobin C. Harding" Date: Thu, 20 Apr 2023 13:35:09 +1000 Subject: [PATCH] WIP: Add initial sign/rerandomization impl Add new sign/re-randomization API using atomics. Includes a few examples of how we could move forward with a separate API for "std" builds to non-std builds. --- examples/generate_keys.rs | 11 +- src/context.rs | 252 ++++++++++++++++++++++++++++++++++++++ src/ecdh.rs | 10 +- src/ecdsa/global.rs | 86 +++++++++++++ src/ecdsa/mod.rs | 8 +- src/ecdsa/recovery.rs | 8 +- src/key.rs | 106 +++++++++------- src/lib.rs | 39 +++--- src/schnorr.rs | 3 + 9 files changed, 441 insertions(+), 82 deletions(-) create mode 100644 src/ecdsa/global.rs diff --git a/examples/generate_keys.rs b/examples/generate_keys.rs index 2a47df19f..195ba91e9 100644 --- a/examples/generate_keys.rs +++ b/examples/generate_keys.rs @@ -1,16 +1,17 @@ +#![cfg(feature = "std")] + extern crate secp256k1; -use secp256k1::{PublicKey, Secp256k1, SecretKey}; +use secp256k1::{PublicKey, SecretKey}; fn main() { - let secp = Secp256k1::new(); let mut rng = rand::thread_rng(); // First option: - let (seckey, pubkey) = secp.generate_keypair(&mut rng); + let (seckey, pubkey) = secp256k1::generate_keypair(&mut rng); - assert_eq!(pubkey, PublicKey::from_secret_key(&secp, &seckey)); + assert_eq!(pubkey, PublicKey::from_secret_key(&seckey)); // Second option: let seckey = SecretKey::new(&mut rng); - let _pubkey = PublicKey::from_secret_key(&secp, &seckey); + let _pubkey = PublicKey::from_secret_key(&seckey); } diff --git a/src/context.rs b/src/context.rs index 61ab985ad..f19dec1e3 100644 --- a/src/context.rs +++ b/src/context.rs @@ -10,6 +10,258 @@ use crate::ffi::types::{c_uint, c_void, AlignedType}; use crate::ffi::{self, CPtr}; use crate::{Error, Secp256k1}; +/// TODO: Rename to global and remove the other one. +#[cfg(feature = "std")] +pub mod _global { + use core::convert::TryFrom; + use core::sync::atomic::{AtomicBool, AtomicU8, AtomicUsize, Ordering}; + use std::ops::Deref; + use std::sync::Once; + + use super::alloc_only::{SignOnly, VerifyOnly}; + use crate::ffi::CPtr; + use crate::{ffi, Secp256k1}; + + struct GlobalVerifyContext { + __private: (), + } + + impl Deref for GlobalVerifyContext { + type Target = Secp256k1; + + fn deref(&self) -> &Self::Target { + static ONCE: Once = Once::new(); + static mut CONTEXT: Option> = None; + ONCE.call_once(|| unsafe { + let ctx = Secp256k1::verification_only(); + CONTEXT = Some(ctx); + }); + unsafe { CONTEXT.as_ref().unwrap() } + } + } + + struct GlobalSignContext { + __private: (), + } + + impl Deref for GlobalSignContext { + type Target = Secp256k1; + + fn deref(&self) -> &Self::Target { + static ONCE: Once = Once::new(); + static mut CONTEXT: Option> = None; + ONCE.call_once(|| unsafe { + let ctx = Secp256k1::signing_only(); + CONTEXT = Some(ctx); + }); + unsafe { CONTEXT.as_ref().unwrap() } + } + } + + static GLOBAL_VERIFY_CONTEXT: &GlobalVerifyContext = &GlobalVerifyContext { __private: () }; + + static GLOBAL_SIGN_CONTEXTS: [&GlobalSignContext; 2] = + [&GlobalSignContext { __private: () }, &GlobalSignContext { __private: () }]; + + static SIGN_CONTEXTS_DIRTY: [AtomicBool; 2] = [AtomicBool::new(false), AtomicBool::new(false)]; + + /// The sign contexts semaphore, stores two flags in the lowest bits and the reader count + /// in the remaining bits. Thus adding or subtracting 4 increments/decrements the counter. + /// + /// The two flags are: + /// * Active context bit - least significant (0b1) + /// * Swap bit - second least significant (0b10) (see [`needs_swap`]). + static SIGN_CONTEXTS_SEM: AtomicUsize = AtomicUsize::new(0); + + /// Re-randomization lock, true==locked, false==unlocked. + static RERAND_LOCK: AtomicBool = AtomicBool::new(false); + + /// Stores the seed for RNG. Notably it doesn't matter that a thread may read "inconsistent" + /// content because it's all random data. If the array is being overwritten while being read it + /// cannot worsen entropy and the exact data doesn't matter. + /// + /// We still have to use atomics because multiple mutable accesses is undefined behavior in Rust. + static GLOBAL_SEED: [AtomicU8; 32] = init_seed_buffer(); + + /// Rerandomizes inactive context using first half of `seed` and stores the second half in the + /// global seed buffer used for later rerandomizations. + pub fn reseed(seed: &[u8; 64]) { + if rerand_lock() { + let last = sign_contexts_inc(); + let other = 1 - active_context(last); + + _rerandomize(other, <&[u8; 32]>::try_from(&seed[0..32]).expect("32 bytes")); + clear_context_dirty(other); + rerand_unlock(); + + sign_contexts_dec(); + + // We unlock before setting the swap bit so that soon as another + // reader sees the swap bit set they can grab the rand lock. + sign_contexts_set_swap_bit(); + } + write_global_seed(<&[u8; 32]>::try_from(&seed[32..64]).expect("32 bytes")); + } + + /// Perform function using the current active global verification context. + /// + /// # Safety + /// + /// TODO: Write safety docs. + pub unsafe fn with_global_verify_context R, R>(f: F) -> R { + f(GLOBAL_VERIFY_CONTEXT.ctx.as_ptr()) + } + + /// Perform function using the current active global signing context. + /// + /// # Safety + /// + /// TODO: Write safety docs. + pub unsafe fn with_global_signing_context R, R>(f: F) -> R { + let last = sign_contexts_inc(); + + // Shift 2 for the 2 flag bits. + if last >= usize::MAX >> 2 { + // Having this many threads should be impossible so if this happens it's because of a bug. + panic!("too many readers"); + } + + let active = active_context(last); + + let res = f(GLOBAL_SIGN_CONTEXTS[active].ctx.as_ptr()); + set_context_dirty(active); + + let last = sign_contexts_dec(); + + // No readers and needs swap. + if last & !1 == 0b10 { + if let Some(ctx) = sign_contexts_swap(last) { + rerandomize_with_global_seed(ctx); + } + } + res + } + + /// Returns the index (into GLOBAL_SIGN_CONTEXTS) of the active context. + fn active_context(sem: usize) -> usize { sem & 1 } + + /// Attempts to lock the rerand lock. + /// + /// # Returns + /// + /// `true` if lock was acquired, false otherwise. + fn rerand_lock() -> bool { + RERAND_LOCK.compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed).is_ok() + } + + /// Attempts to unlock the rerand lock. + /// + /// # Returns + /// + /// `true` if the lock was unlocked by this operation. + fn rerand_unlock() -> bool { + RERAND_LOCK.compare_exchange(true, false, Ordering::Acquire, Ordering::Relaxed).is_ok() + } + + /// Increments the sign-contexts reader semaphore. + // FIXME: What happens if we have more than usize::MAX >> 2 readers i.e., overflow? + fn sign_contexts_inc() -> usize { SIGN_CONTEXTS_SEM.fetch_add(4, Ordering::Acquire) } + + /// Decrements the sign-contexts reader semaphore. + fn sign_contexts_dec() -> usize { SIGN_CONTEXTS_SEM.fetch_sub(4, Ordering::Acquire) } + + /// Swap the active context and clear the swap bit. + /// + /// # Panics + /// + /// If `lock` has count > 0. + /// + /// # Returns + /// + /// The now-inactive context index (ie, the index of the context swapped out). + fn sign_contexts_swap(sem: usize) -> Option { + assert!(sem & !0b11 == 0); // reader count == 0 + let new = (sem & !0b10) ^ 0b01; // turn off swap bit, toggle active bit. + match SIGN_CONTEXTS_SEM.compare_exchange(sem, new, Ordering::Relaxed, Ordering::Relaxed) { + Ok(last) => Some(active_context(last)), + // Another reader signaled before we had a chance to swap. + Err(_) => None, + } + } + + /// Unconditionally turns on the "needs swap" bit. + fn sign_contexts_set_swap_bit() { SIGN_CONTEXTS_SEM.fetch_or(0b10, Ordering::Relaxed); } + + fn set_context_dirty(ctx: usize) { + assert!(ctx < 2); + SIGN_CONTEXTS_DIRTY[ctx].store(true, Ordering::Relaxed); + } + + fn clear_context_dirty(ctx: usize) { + assert!(ctx < 2); + SIGN_CONTEXTS_DIRTY[ctx].store(true, Ordering::Relaxed); + } + + fn write_global_seed(seed: &[u8; 32]) { + for (i, b) in seed.iter().enumerate() { + GLOBAL_SEED[i].store(*b, Ordering::Relaxed); + } + } + + /// Rerandomize the global signing context using randomness in the global seed. + fn rerandomize_with_global_seed(ctx: usize) { + let mut buf = [0_u8; 32]; + for (i, b) in buf.iter_mut().enumerate() { + let atomic = &GLOBAL_SEED[i]; + *b = atomic.load(Ordering::Relaxed); + } + rerandomize(ctx, &buf) + } + + /// Rerandomize global context index `ctx` using randomness in `seed`. + fn rerandomize(ctx: usize, seed: &[u8; 32]) { + assert!(ctx < 2); + if rerand_lock() { + _rerandomize(ctx, seed); + clear_context_dirty(ctx); + rerand_unlock(); + + // We unlock before setting the swap bit so that soon as another + // reader sees the swap bit set they can grab the rand lock. + sign_contexts_set_swap_bit(); + } + } + + /// Should be called with the RERAND_LOCK held. + fn _rerandomize(ctx: usize, seed: &[u8; 32]) { + let secp = GLOBAL_SIGN_CONTEXTS[ctx]; + unsafe { + let err = ffi::secp256k1_context_randomize(secp.ctx, seed.as_c_ptr()); + // This function cannot fail; it has an error return for future-proofing. + // We do not expose this error since it is impossible to hit, and we have + // precedent for not exposing impossible errors (for example in + // `PublicKey::from_secret_key` where it is impossible to create an invalid + // secret key through the API.) + // However, if this DOES fail, the result is potentially weaker side-channel + // resistance, which is deadly and undetectable, so we take out the entire + // thread to be on the safe side. + assert_eq!(err, 1); + } + } + + // TODO: Find better way to do this. + #[rustfmt::skip] + const fn init_seed_buffer() -> [AtomicU8; 32] { + let buf: [AtomicU8; 32] = [ + AtomicU8::new(0), AtomicU8::new(0), AtomicU8::new(0), AtomicU8::new(0), AtomicU8::new(0), AtomicU8::new(0), AtomicU8::new(0), AtomicU8::new(0), + AtomicU8::new(0), AtomicU8::new(0), AtomicU8::new(0), AtomicU8::new(0), AtomicU8::new(0), AtomicU8::new(0), AtomicU8::new(0), AtomicU8::new(0), + AtomicU8::new(0), AtomicU8::new(0), AtomicU8::new(0), AtomicU8::new(0), AtomicU8::new(0), AtomicU8::new(0), AtomicU8::new(0), AtomicU8::new(0), + AtomicU8::new(0), AtomicU8::new(0), AtomicU8::new(0), AtomicU8::new(0), AtomicU8::new(0), AtomicU8::new(0), AtomicU8::new(0), AtomicU8::new(0), + ]; + buf + } +} + #[cfg(all(feature = "global-context", feature = "std"))] /// Module implementing a singleton pattern for a global `Secp256k1` context. pub mod global { diff --git a/src/ecdh.rs b/src/ecdh.rs index e01366e53..9c46017a4 100644 --- a/src/ecdh.rs +++ b/src/ecdh.rs @@ -195,9 +195,8 @@ mod tests { #[test] #[cfg(feature = "rand-std")] fn ecdh() { - let s = Secp256k1::signing_only(); - let (sk1, pk1) = s.generate_keypair(&mut rand::thread_rng()); - let (sk2, pk2) = s.generate_keypair(&mut rand::thread_rng()); + let (sk1, pk1) = crate::generate_keypair(&mut rand::thread_rng()); + let (sk2, pk2) = crate::generate_keypair(&mut rand::thread_rng()); let sec1 = SharedSecret::new(&pk2, &sk1); let sec2 = SharedSecret::new(&pk1, &sk2); @@ -231,9 +230,8 @@ mod tests { use crate::ecdh::shared_secret_point; - let s = Secp256k1::signing_only(); - let (sk1, _) = s.generate_keypair(&mut rand::thread_rng()); - let (_, pk2) = s.generate_keypair(&mut rand::thread_rng()); + let (sk1, _) = crate::generate_keypair(&mut rand::thread_rng()); + let (_, pk2) = crate::generate_keypair(&mut rand::thread_rng()); let secret_sys = SharedSecret::new(&pk2, &sk1); diff --git a/src/ecdsa/global.rs b/src/ecdsa/global.rs new file mode 100644 index 000000000..16b1ae727 --- /dev/null +++ b/src/ecdsa/global.rs @@ -0,0 +1,86 @@ +//! Drop in replacement for all the methods currently implemented on the global context (SECP256K1). + +use core::ptr; + +use super::Signature; +use crate::ffi::CPtr; +use crate::{ffi, Error, Message, PublicKey, SecretKey}; + +/// Constructs a signature for `msg` using the secret key `sk` and RFC6979 nonce +pub fn sign_ecdsa(msg: &Message, sk: &SecretKey) -> Signature { + sign_ecdsa_with_noncedata_pointer(msg, sk, None) +} + +/// Constructs a signature for `msg` using the secret key `sk` and RFC6979 nonce +/// and includes 32 bytes of noncedata in the nonce generation via inclusion in +/// one of the hash operations during nonce generation. This is useful when multiple +/// signatures are needed for the same Message and SecretKey while still using RFC6979. +/// Requires a signing-capable context. +pub fn sign_ecdsa_with_noncedata(msg: &Message, sk: &SecretKey, noncedata: &[u8; 32]) -> Signature { + sign_ecdsa_with_noncedata_pointer(msg, sk, Some(noncedata)) +} + +/// Checks that `sig` is a valid ECDSA signature for `msg` using the public +/// key `pubkey`. Returns `Ok(())` on success. Note that this function cannot +/// be used for Bitcoin consensus checking since there may exist signatures +/// which OpenSSL would verify but not libsecp256k1, or vice-versa. Requires a +/// verify-capable context. +/// +/// ```rust +/// # #[cfg(feature = "rand-std")] { +/// # use secp256k1::{rand, Secp256k1, Message, Error}; +/// # +/// # let secp = Secp256k1::new(); +/// # let (secret_key, public_key) = secp.generate_keypair(&mut rand::thread_rng()); +/// # +/// let message = Message::from_slice(&[0xab; 32]).expect("32 bytes"); +/// let sig = secp.sign_ecdsa(&message, &secret_key); +/// assert_eq!(secp.verify_ecdsa(&message, &sig, &public_key), Ok(())); +/// +/// let message = Message::from_slice(&[0xcd; 32]).expect("32 bytes"); +/// assert_eq!(secp.verify_ecdsa(&message, &sig, &public_key), Err(Error::IncorrectSignature)); +/// # } +/// ``` +#[inline] +pub fn verify_ecdsa(msg: &Message, sig: &Signature, pk: &PublicKey) -> Result<(), Error> { + unsafe { + crate::context::_global::with_global_verify_context(|ctx| { + if ffi::secp256k1_ecdsa_verify(ctx, sig.as_c_ptr(), msg.as_c_ptr(), pk.as_c_ptr()) == 0 + { + Err(Error::IncorrectSignature) + } else { + Ok(()) + } + }) + } +} + +fn sign_ecdsa_with_noncedata_pointer( + msg: &Message, + sk: &SecretKey, + noncedata: Option<&[u8; 32]>, +) -> Signature { + unsafe { + let mut ret = ffi::Signature::new(); + let noncedata_ptr = match noncedata { + Some(arr) => arr.as_c_ptr() as *const _, + None => ptr::null(), + }; + crate::context::_global::with_global_signing_context(|ctx| { + // 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( + ctx, + &mut ret, + msg.as_c_ptr(), + sk.as_c_ptr(), + ffi::secp256k1_nonce_function_rfc6979, + noncedata_ptr + ), + 1 + ); + }); + Signature::from(ret) + } +} diff --git a/src/ecdsa/mod.rs b/src/ecdsa/mod.rs index fd01cbbd1..4e23b8d44 100644 --- a/src/ecdsa/mod.rs +++ b/src/ecdsa/mod.rs @@ -3,6 +3,8 @@ //! Structs and functionality related to the ECDSA signature algorithm. //! +#[cfg(feature = "std")] +pub mod global; #[cfg(feature = "recovery")] mod recovery; pub mod serialized_signature; @@ -13,8 +15,6 @@ use core::{fmt, ptr, str}; pub use self::recovery::{RecoverableSignature, RecoveryId}; pub use self::serialized_signature::SerializedSignature; use crate::ffi::CPtr; -#[cfg(feature = "global-context")] -use crate::SECP256K1; use crate::{ ffi, from_hex, Error, Message, PublicKey, Secp256k1, SecretKey, Signing, Verification, }; @@ -190,12 +190,12 @@ impl Signature { ret } - /// Verifies an ECDSA signature for `msg` using `pk` and the global [`SECP256K1`] context. + /// Verifies an ECDSA signature for `msg` using `pk` and the global signing context. /// The signature must be normalized or verification will fail (see [`Signature::normalize_s`]). #[inline] #[cfg(feature = "global-context")] pub fn verify(&self, msg: &Message, pk: &PublicKey) -> Result<(), Error> { - SECP256K1.verify_ecdsa(msg, self, pk) + crate::ecdsa::global::verify_ecdsa(msg, self, pk) } } diff --git a/src/ecdsa/recovery.rs b/src/ecdsa/recovery.rs index ab337d016..80496d95c 100644 --- a/src/ecdsa/recovery.rs +++ b/src/ecdsa/recovery.rs @@ -229,7 +229,7 @@ mod tests { let msg = Message::from_slice(&msg).unwrap(); // Try key generation - let (sk, pk) = full.generate_keypair(&mut rand::thread_rng()); + let (sk, pk) = crate::generate_keypair(&mut rand::thread_rng()); // Try signing assert_eq!(sign.sign_ecdsa_recoverable(&msg, &sk), full.sign_ecdsa_recoverable(&msg, &sk)); @@ -309,7 +309,7 @@ mod tests { let msg = crate::random_32_bytes(&mut rand::thread_rng()); let msg = Message::from_slice(&msg).unwrap(); - let (sk, pk) = s.generate_keypair(&mut rand::thread_rng()); + let (sk, pk) = crate::generate_keypair(&mut rand::thread_rng()); let sigr = s.sign_ecdsa_recoverable(&msg, &sk); let sig = sigr.to_standard(); @@ -331,7 +331,7 @@ mod tests { let msg = crate::random_32_bytes(&mut rand::thread_rng()); let msg = Message::from_slice(&msg).unwrap(); - let (sk, pk) = s.generate_keypair(&mut rand::thread_rng()); + let (sk, pk) = crate::generate_keypair(&mut rand::thread_rng()); let sig = s.sign_ecdsa_recoverable(&msg, &sk); @@ -349,7 +349,7 @@ mod tests { let noncedata = [42u8; 32]; - let (sk, pk) = s.generate_keypair(&mut rand::thread_rng()); + let (sk, pk) = crate::generate_keypair(&mut rand::thread_rng()); let sig = s.sign_ecdsa_recoverable_with_noncedata(&msg, &sk, &noncedata); diff --git a/src/key.rs b/src/key.rs index c7559c754..b89c0f5f9 100644 --- a/src/key.rs +++ b/src/key.rs @@ -130,12 +130,11 @@ impl str::FromStr for SecretKey { /// Basic usage: /// /// ``` -/// # #[cfg(feature = "alloc")] { -/// use secp256k1::{SecretKey, Secp256k1, PublicKey}; +/// # #[cfg(feature = "std")] { +/// use secp256k1::{SecretKey, PublicKey}; /// -/// let secp = Secp256k1::new(); /// let secret_key = SecretKey::from_slice(&[0xcd; 32]).expect("32 bytes, within curve order"); -/// let public_key = PublicKey::from_secret_key(&secp, &secret_key); +/// let public_key = PublicKey::from_secret_key(&secret_key); /// # } /// ``` /// [`bincode`]: https://docs.rs/bincode @@ -354,6 +353,14 @@ impl SecretKey { /// /// This is equivalent to using [`PublicKey::from_secret_key`]. #[inline] + #[cfg(feature = "std")] + pub fn public_key(&self) -> PublicKey { PublicKey::from_secret_key(self) } + + /// Returns the [`PublicKey`] for this [`SecretKey`]. + /// + /// This is equivalent to using [`PublicKey::from_secret_key`]. + #[inline] + #[cfg(not(feature = "std"))] pub fn public_key(&self, secp: &Secp256k1) -> PublicKey { PublicKey::from_secret_key(secp, self) } @@ -431,12 +438,12 @@ impl PublicKey { /// # #[cfg(feature = "rand-std")] { /// use secp256k1::{rand, Secp256k1, SecretKey, PublicKey}; /// - /// let secp = Secp256k1::new(); /// let secret_key = SecretKey::new(&mut rand::thread_rng()); - /// let public_key = PublicKey::from_secret_key(&secp, &secret_key); + /// let public_key = PublicKey::from_secret_key(&secret_key); /// # } /// ``` #[inline] + #[cfg(not(feature = "std"))] pub fn from_secret_key(secp: &Secp256k1, sk: &SecretKey) -> PublicKey { unsafe { let mut pk = ffi::PublicKey::new(); @@ -448,11 +455,19 @@ impl PublicKey { } } - /// Creates a new public key from a [`SecretKey`] and the global [`SECP256K1`] context. - #[inline] - #[cfg(feature = "global-context")] - pub fn from_secret_key_global(sk: &SecretKey) -> PublicKey { - PublicKey::from_secret_key(SECP256K1, sk) + /// This is just a demo of the new _global API + #[cfg(feature = "std")] + pub fn from_secret_key(sk: &SecretKey) -> PublicKey { + unsafe { + crate::context::_global::with_global_signing_context(|ctx| { + let mut pk = ffi::PublicKey::new(); + // We can assume the return value because it's not possible to construct + // an invalid `SecretKey` without transmute trickery or something. + let res = ffi::secp256k1_ec_pubkey_create(ctx, &mut pk, sk.as_c_ptr()); + debug_assert_eq!(res, 1); + PublicKey(pk) + }) + } } /// Creates a public key directly from a slice. @@ -1573,9 +1588,7 @@ mod test { #[test] #[cfg(feature = "rand-std")] fn keypair_slice_round_trip() { - let s = Secp256k1::new(); - - let (sk1, pk1) = s.generate_keypair(&mut rand::thread_rng()); + let (sk1, pk1) = crate::generate_keypair(&mut rand::thread_rng()); assert_eq!(SecretKey::from_slice(&sk1[..]), Ok(sk1)); assert_eq!(PublicKey::from_slice(&pk1.serialize()[..]), Ok(pk1)); assert_eq!(PublicKey::from_slice(&pk1.serialize_uncompressed()[..]), Ok(pk1)); @@ -1620,7 +1633,7 @@ mod test { } #[test] - #[cfg(all(feature = "rand", feature = "alloc"))] + #[cfg(all(feature = "std", feature = "rand", feature = "alloc"))] fn test_out_of_range() { struct BadRng(u8); impl RngCore for BadRng { @@ -1647,8 +1660,7 @@ mod test { } } - let s = Secp256k1::new(); - s.generate_keypair(&mut BadRng(0xff)); + crate::generate_keypair(&mut BadRng(0xff)); } #[test] @@ -1707,10 +1719,9 @@ mod test { } #[test] - #[cfg(all(feature = "rand", feature = "alloc"))] + #[cfg(all(feature = "std", feature = "rand", feature = "alloc"))] fn test_debug_output() { - let s = Secp256k1::new(); - let (sk, _) = s.generate_keypair(&mut StepRng::new(1, 1)); + let (sk, _) = crate::generate_keypair(&mut StepRng::new(1, 1)); assert_eq!(&format!("{:?}", sk), "SecretKey(#d3e0c51a23169bb5)"); @@ -1733,13 +1744,18 @@ mod test { ]; #[cfg(not(secp256k1_fuzz))] + #[cfg(not(feature = "std"))] let s = Secp256k1::signing_only(); let sk = SecretKey::from_slice(&SK_BYTES).expect("sk"); // In fuzzing mode secret->public key derivation is different, so // hard-code the expected result. #[cfg(not(secp256k1_fuzz))] + #[cfg(feature = "std")] + let pk = PublicKey::from_secret_key(&sk); + #[cfg(not(feature = "std"))] let pk = PublicKey::from_secret_key(&s, &sk); + #[cfg(secp256k1_fuzz)] let pk = PublicKey::from_slice(&[ 0x02, 0x18, 0x84, 0x57, 0x81, 0xf6, 0x31, 0xc4, 0x8f, 0x1c, 0x97, 0x09, 0xe2, 0x30, @@ -1828,10 +1844,9 @@ mod test { // In fuzzing mode the Y coordinate is expected to match the X, so this // test uses invalid public keys. #[cfg(not(secp256k1_fuzz))] - #[cfg(all(feature = "alloc", feature = "rand"))] + #[cfg(all(feature = "std", feature = "rand", feature = "alloc"))] fn test_pubkey_serialize() { - let s = Secp256k1::new(); - let (_, pk1) = s.generate_keypair(&mut StepRng::new(1, 1)); + let (_, pk1) = crate::generate_keypair(&mut StepRng::new(1, 1)); assert_eq!( &pk1.serialize_uncompressed()[..], &[ @@ -1855,8 +1870,8 @@ mod test { fn tweak_add_arbitrary_data() { let s = Secp256k1::new(); - let (sk, pk) = s.generate_keypair(&mut rand::thread_rng()); - assert_eq!(PublicKey::from_secret_key(&s, &sk), pk); // Sanity check. + let (sk, pk) = crate::generate_keypair(&mut rand::thread_rng()); + assert_eq!(PublicKey::from_secret_key(&sk), pk); // Sanity check. // TODO: This would be better tested with a _lot_ of different tweaks. let tweak = Scalar::random(); @@ -1866,7 +1881,7 @@ mod test { let tweaked_pk = pk.add_exp_tweak(&s, &tweak).unwrap(); assert_ne!(pk, tweaked_pk); - assert_eq!(PublicKey::from_secret_key(&s, &tweaked_sk), tweaked_pk); + assert_eq!(PublicKey::from_secret_key(&tweaked_sk), tweaked_pk); } #[test] @@ -1874,7 +1889,7 @@ mod test { fn tweak_add_zero() { let s = Secp256k1::new(); - let (sk, pk) = s.generate_keypair(&mut rand::thread_rng()); + let (sk, pk) = crate::generate_keypair(&mut rand::thread_rng()); let tweak = Scalar::ZERO; @@ -1889,8 +1904,8 @@ mod test { fn tweak_mul_arbitrary_data() { let s = Secp256k1::new(); - let (sk, pk) = s.generate_keypair(&mut rand::thread_rng()); - assert_eq!(PublicKey::from_secret_key(&s, &sk), pk); // Sanity check. + let (sk, pk) = crate::generate_keypair(&mut rand::thread_rng()); + assert_eq!(PublicKey::from_secret_key(&sk), pk); // Sanity check. // TODO: This would be better tested with a _lot_ of different tweaks. let tweak = Scalar::random(); @@ -1900,14 +1915,13 @@ mod test { let tweaked_pk = pk.mul_tweak(&s, &tweak).unwrap(); assert_ne!(pk, tweaked_pk); - assert_eq!(PublicKey::from_secret_key(&s, &tweaked_sk), tweaked_pk); + assert_eq!(PublicKey::from_secret_key(&tweaked_sk), tweaked_pk); } #[test] #[cfg(feature = "rand-std")] fn tweak_mul_zero() { - let s = Secp256k1::new(); - let (sk, _) = s.generate_keypair(&mut rand::thread_rng()); + let (sk, _) = crate::generate_keypair(&mut rand::thread_rng()); let tweak = Scalar::ZERO; assert!(sk.mul_tweak(&tweak).is_err()) @@ -1918,9 +1932,9 @@ mod test { fn test_negation() { let s = Secp256k1::new(); - let (sk, pk) = s.generate_keypair(&mut rand::thread_rng()); + let (sk, pk) = crate::generate_keypair(&mut rand::thread_rng()); - assert_eq!(PublicKey::from_secret_key(&s, &sk), pk); // Sanity check. + assert_eq!(PublicKey::from_secret_key(&sk), pk); // Sanity check. let neg = sk.negate(); assert_ne!(sk, neg); @@ -1932,7 +1946,7 @@ mod test { let back_pk = neg.negate(&s); assert_eq!(pk, back_pk); - assert_eq!(PublicKey::from_secret_key(&s, &back_sk), pk); + assert_eq!(PublicKey::from_secret_key(&back_sk), pk); } #[test] @@ -1948,11 +1962,10 @@ mod test { s.finish() } - let s = Secp256k1::new(); let mut set = HashSet::new(); const COUNT: usize = 1024; for _ in 0..COUNT { - let (_, pk) = s.generate_keypair(&mut rand::thread_rng()); + let (_, pk) = crate::generate_keypair(&mut rand::thread_rng()); let hash = hash(&pk); assert!(!set.contains(&hash)); set.insert(hash); @@ -2021,10 +2034,8 @@ mod test { #[test] #[cfg(feature = "rand-std")] fn create_pubkey_combine() { - let s = Secp256k1::new(); - - let (sk1, pk1) = s.generate_keypair(&mut rand::thread_rng()); - let (sk2, pk2) = s.generate_keypair(&mut rand::thread_rng()); + let (sk1, pk1) = crate::generate_keypair(&mut rand::thread_rng()); + let (sk2, pk2) = crate::generate_keypair(&mut rand::thread_rng()); let sum1 = pk1.combine(&pk2); assert!(sum1.is_ok()); @@ -2033,7 +2044,7 @@ mod test { assert_eq!(sum1, sum2); let tweaked = sk1.add_tweak(&Scalar::from(sk2)).unwrap(); - let sksum = PublicKey::from_secret_key(&s, &tweaked); + let sksum = PublicKey::from_secret_key(&tweaked); assert_eq!(Ok(sksum), sum1); } @@ -2087,14 +2098,17 @@ mod test { ]; static PK_STR: &str = "0218845781f631c48f1c9709e23092067d06837f30aa0cd0544ac887fe91ddd166"; - #[cfg(not(secp256k1_fuzz))] - let s = Secp256k1::new(); let sk = SecretKey::from_slice(&SK_BYTES).unwrap(); // In fuzzing mode secret->public key derivation is different, so // hard-code the expected result. #[cfg(not(secp256k1_fuzz))] + #[cfg(feature = "std")] + let pk = PublicKey::from_secret_key(&sk); + #[cfg(not(secp256k1_fuzz))] + #[cfg(not(feature = "std"))] let pk = PublicKey::from_secret_key(&s, &sk); + #[cfg(secp256k1_fuzz)] let pk = PublicKey::from_slice(&PK_BYTES).expect("pk"); @@ -2250,9 +2264,13 @@ mod test { #[test] #[cfg(all(not(secp256k1_fuzz), feature = "alloc"))] fn convert_secret_key_to_public_key() { + #[cfg(not(feature = "std"))] let secp = Secp256k1::new(); let (sk, want, _kp, _xonly) = keys(); + #[cfg(feature = "std")] + let got = sk.public_key(); + #[cfg(not(feature = "std"))] let got = sk.public_key(&secp); assert_eq!(got, want) diff --git a/src/lib.rs b/src/lib.rs index 14d65f224..65b4c263b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -67,7 +67,7 @@ //! //! let secp = Secp256k1::new(); //! let secret_key = SecretKey::from_slice(&[0xcd; 32]).expect("32 bytes, within curve order"); -//! let public_key = PublicKey::from_secret_key(&secp, &secret_key); +//! let public_key = PublicKey::from_secret_key(&secret_key); //! // This is unsafe unless the supplied byte slice is the output of a cryptographic hash function. //! // See the above example for how to use this library together with `bitcoin-hashes-std`. //! let message = Message::from_slice(&[0xab; 32]).expect("32 bytes"); @@ -429,22 +429,24 @@ impl Secp256k1 { /// Generates a random keypair. Convenience function for [`SecretKey::new`] and /// [`PublicKey::from_secret_key`]. #[inline] - #[cfg(feature = "rand")] + #[cfg(all(feature = "rand", not(feature = "std")))] pub fn generate_keypair( &self, rng: &mut R, ) -> (key::SecretKey, key::PublicKey) { let sk = key::SecretKey::new(rng); - let pk = key::PublicKey::from_secret_key(self, &sk); + let pk = key::PublicKey::from_secret_key(&self, &sk); (sk, pk) } } /// Generates a random keypair using the global [`SECP256K1`] context. #[inline] -#[cfg(all(feature = "global-context", feature = "rand"))] +#[cfg(all(feature = "std", feature = "rand"))] pub fn generate_keypair(rng: &mut R) -> (key::SecretKey, key::PublicKey) { - SECP256K1.generate_keypair(rng) + let sk = key::SecretKey::new(rng); + let pk = key::PublicKey::from_secret_key(&sk); + (sk, pk) } /// Utility function used to parse hex into a target u8 buffer. Returns @@ -539,7 +541,7 @@ mod tests { let vrfy: Secp256k1 = Secp256k1 { ctx: ctx_vrfy, phantom: PhantomData }; - let (sk, pk) = full.generate_keypair(&mut rand::thread_rng()); + let (sk, pk) = crate::generate_keypair(&mut rand::thread_rng()); let msg = Message::from_slice(&[2u8; 32]).unwrap(); // Try signing assert_eq!(sign.sign_ecdsa(&msg, &sk), full.sign_ecdsa(&msg, &sk)); @@ -571,7 +573,7 @@ mod tests { let mut sign = unsafe { Secp256k1::from_raw_signing_only(ctx_sign.ctx) }; let mut vrfy = unsafe { Secp256k1::from_raw_verification_only(ctx_vrfy.ctx) }; - let (sk, pk) = full.generate_keypair(&mut rand::thread_rng()); + let (sk, pk) = crate::generate_keypair(&mut rand::thread_rng()); let msg = Message::from_slice(&[2u8; 32]).unwrap(); // Try signing assert_eq!(sign.sign_ecdsa(&msg, &sk), full.sign_ecdsa(&msg, &sk)); @@ -617,7 +619,7 @@ mod tests { // drop(buf_vfy); // The buffer can't get dropped before the context. // println!("{:?}", buf_ful[5]); // Can't even read the data thanks to the borrow checker. - let (sk, pk) = full.generate_keypair(&mut rand::thread_rng()); + let (sk, pk) = crate::generate_keypair(&mut rand::thread_rng()); let msg = Message::from_slice(&[2u8; 32]).unwrap(); // Try signing assert_eq!(sign.sign_ecdsa(&msg, &sk), full.sign_ecdsa(&msg, &sk)); @@ -639,7 +641,7 @@ mod tests { let msg = Message::from_slice(&msg).unwrap(); // Try key generation - let (sk, pk) = full.generate_keypair(&mut rand::thread_rng()); + let (sk, pk) = crate::generate_keypair(&mut rand::thread_rng()); // Try signing assert_eq!(sign.sign_ecdsa(&msg, &sk), full.sign_ecdsa(&msg, &sk)); @@ -667,7 +669,7 @@ mod tests { let msg = crate::random_32_bytes(&mut rand::thread_rng()); let msg = Message::from_slice(&msg).unwrap(); - let (sk, _) = s.generate_keypair(&mut rand::thread_rng()); + let (sk, _) = crate::generate_keypair(&mut rand::thread_rng()); let sig1 = s.sign_ecdsa(&msg, &sk); let der = sig1.serialize_der(); let sig2 = ecdsa::Signature::from_der(&der[..]).unwrap(); @@ -758,7 +760,7 @@ mod tests { let msg = crate::random_32_bytes(&mut rand::thread_rng()); let msg = Message::from_slice(&msg).unwrap(); - let (sk, pk) = s.generate_keypair(&mut rand::thread_rng()); + let (sk, pk) = crate::generate_keypair(&mut rand::thread_rng()); let sig = s.sign_ecdsa(&msg, &sk); assert_eq!(s.verify_ecdsa(&msg, &sig, &pk), Ok(())); let noncedata_sig = s.sign_ecdsa_with_noncedata(&msg, &sk, &noncedata); @@ -807,7 +809,7 @@ mod tests { let sig = s.sign_ecdsa(&msg, &key); let low_r_sig = s.sign_ecdsa_low_r(&msg, &key); let grind_r_sig = s.sign_ecdsa_grind_r(&msg, &key, 1); - let pk = PublicKey::from_secret_key(&s, &key); + let pk = PublicKey::from_secret_key(&key); assert_eq!(s.verify_ecdsa(&msg, &sig, &pk), Ok(())); assert_eq!(s.verify_ecdsa(&msg, &low_r_sig, &pk), Ok(())); assert_eq!(s.verify_ecdsa(&msg, &grind_r_sig, &pk), Ok(())); @@ -824,7 +826,7 @@ mod tests { let msg = crate::random_32_bytes(&mut rand::thread_rng()); let msg = Message::from_slice(&msg).unwrap(); - let (sk, pk) = s.generate_keypair(&mut rand::thread_rng()); + let (sk, pk) = crate::generate_keypair(&mut rand::thread_rng()); let sig = s.sign_ecdsa(&msg, &sk); @@ -995,17 +997,16 @@ mod tests { assert_tokens(&sig.readable(), &[Token::String(SIG_STR)]); } - #[cfg(feature = "global-context")] + #[cfg(feature = "std")] #[test] fn test_global_context() { - use crate::SECP256K1; let sk_data = hex!("e6dd32f8761625f105c39a39f19370b3521d845a12456d60ce44debd0a362641"); let sk = SecretKey::from_slice(&sk_data).unwrap(); let msg_data = hex!("a4965ca63b7d8562736ceec36dfa5a11bf426eb65be8ea3f7a49ae363032da0d"); let msg = Message::from_slice(&msg_data).unwrap(); // Check usage as explicit parameter - let pk = PublicKey::from_secret_key(SECP256K1, &sk); + let pk = PublicKey::from_secret_key(&sk); // Check usage as self let sig = SECP256K1.sign_ecdsa(&msg, &sk); @@ -1044,7 +1045,7 @@ mod benches { let s = Secp256k1::new(); let mut r = StepRng::new(1, 1); bh.iter(|| { - let (sk, pk) = s.generate_keypair(&mut r); + let (sk, pk) = crate::generate_keypair(&mut r); black_box(sk); black_box(pk); }); @@ -1055,7 +1056,7 @@ mod benches { let s = Secp256k1::new(); let msg = crate::random_32_bytes(&mut rand::thread_rng()); let msg = Message::from_slice(&msg).unwrap(); - let (sk, _) = s.generate_keypair(&mut rand::thread_rng()); + let (sk, _) = crate::generate_keypair(&mut rand::thread_rng()); bh.iter(|| { let sig = s.sign_ecdsa(&msg, &sk); @@ -1068,7 +1069,7 @@ mod benches { let s = Secp256k1::new(); let msg = crate::random_32_bytes(&mut rand::thread_rng()); let msg = Message::from_slice(&msg).unwrap(); - let (sk, pk) = s.generate_keypair(&mut rand::thread_rng()); + let (sk, pk) = crate::generate_keypair(&mut rand::thread_rng()); let sig = s.sign_ecdsa(&msg, &sk); bh.iter(|| { diff --git a/src/schnorr.rs b/src/schnorr.rs index eb651e82a..e5ddc1716 100644 --- a/src/schnorr.rs +++ b/src/schnorr.rs @@ -341,6 +341,9 @@ mod tests { let sk = SecretKey::from_keypair(&keypair); assert_eq!(SecretKey::from_str(sk_str).unwrap(), sk); let pk = crate::key::PublicKey::from_keypair(&keypair); + #[cfg(feature = "std")] + assert_eq!(crate::key::PublicKey::from_secret_key(&sk), pk); + #[cfg(not(feature = "std"))] assert_eq!(crate::key::PublicKey::from_secret_key(&secp, &sk), pk); let (xpk, _parity) = keypair.x_only_public_key(); assert_eq!(XOnlyPublicKey::from(pk), xpk);