From d79989bc954bb493685235ce47ecbe2062f5360c Mon Sep 17 00:00:00 2001 From: Tobin Harding Date: Wed, 9 Feb 2022 07:55:02 +0000 Subject: [PATCH 1/4] Remove erroneous duplicate feature When we removed the "global-context-less-secure" a duplicate feature snuck in, remove it. --- src/lib.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index c007c98..3ee86e4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -198,7 +198,7 @@ use core::{mem, fmt, str}; use ffi::{CPtr, types::AlignedType}; #[cfg(feature = "global-context")] -#[cfg_attr(docsrs, doc(cfg(any(feature = "global-context", feature = "global-context"))))] +#[cfg_attr(docsrs, doc(cfg(feature = "global-context")))] pub use context::global::SECP256K1; #[cfg(feature = "bitcoin_hashes")] @@ -458,6 +458,14 @@ impl Secp256k1 { } } +/// Generates a random keypair using the global [`SECP256K1`] context. +#[inline] +#[cfg(all(feature = "global-context", feature = "rand"))] +#[cfg_attr(docsrs, doc(cfg(all(feature = "global-context", feature = "rand"))))] +pub fn generate_keypair(rng: &mut R) -> (key::SecretKey, key::PublicKey) { + SECP256K1.generate_keypair(rng) +} + /// Utility function used to parse hex into a target u8 buffer. Returns /// the number of bytes converted or an error if it encounters an invalid /// character or unexpected end of string. From e2d47a29e2f10be1fe5cc07d4229f45b0c1a84cb Mon Sep 17 00:00:00 2001 From: Tobin Harding Date: Wed, 9 Feb 2022 07:57:36 +0000 Subject: [PATCH 2/4] Remove unnecessary import statement The global context is already in scope in tests since we use a glob import. No clue why Clippy does not warn for this. Remove unnecessary import statement in test function. --- src/lib.rs | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 3ee86e4..159f637 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -57,6 +57,22 @@ //! # } //! ``` //! +//! If the "global-context" feature is enabled you have access to an alternate API. +//! +//! ```rust +//! # #[cfg(all(feature="global-context", feature = "std", feature="rand-std", features = "bitcoin_hashes"))] { +//! use secp256k1::rand::thread_rng; +//! use secp256k1::{generate_keypair, Message}; +//! use secp256k1::hashes::sha256; +//! +//! let (secret_key, public_key) = generate_keypair(&mut thread_rng()); +//! let message = Message::from_hashed_data::("Hello World!".as_bytes()); +//! +//! let sig = secret_key.sign_ecdsa(&message, &secret_key); +//! assert!(sig.verify(&message, &public_key).is_ok()); +//! # } +//! ``` +//! //! The above code requires `rust-secp256k1` to be compiled with the `rand-std` and `bitcoin_hashes` //! feature enabled, to get access to [`generate_keypair`](struct.Secp256k1.html#method.generate_keypair) //! Alternately, keys and messages can be parsed from slices, like @@ -968,8 +984,6 @@ mod tests { #[cfg(feature = "global-context")] #[test] fn test_global_context() { - use super::SECP256K1; - let sk_data = hex!("e6dd32f8761625f105c39a39f19370b3521d845a12456d60ce44debd0a362641"); let sk = SecretKey::from_slice(&sk_data).unwrap(); let msg_data = hex!("a4965ca63b7d8562736ceec36dfa5a11bf426eb65be8ea3f7a49ae363032da0d"); From 3ecb5e41b3375e38d00f864b07291e0e494c689e Mon Sep 17 00:00:00 2001 From: Tobin Harding Date: Wed, 9 Feb 2022 09:02:53 +0000 Subject: [PATCH 3/4] Refactor from_secret_key definition The `from_secret_key` method definition currently uses non-standard indentation. Improve uniformity by using 'standard' indentation. --- src/key.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/key.rs b/src/key.rs index 562f02e..45d631c 100644 --- a/src/key.rs +++ b/src/key.rs @@ -338,9 +338,7 @@ impl PublicKey { /// # } /// ``` #[inline] - pub fn from_secret_key(secp: &Secp256k1, - sk: &SecretKey) - -> PublicKey { + pub fn from_secret_key(secp: &Secp256k1,sk: &SecretKey) -> PublicKey { unsafe { let mut pk = ffi::PublicKey::new(); // We can assume the return value because it's not possible to construct From eb453b8227f6600600f215ff2d515abe5733e2dd Mon Sep 17 00:00:00 2001 From: Tobin Harding Date: Tue, 8 Feb 2022 06:11:05 +0000 Subject: [PATCH 4/4] Add global context API Our API often involves a `Secp256k1` parameter, when users enable the `global-context` feature they must then pass `SECP256K1` into these functions. This is kind of clunky since the global is by definition available everywhere. Make the API more ergonomic for `global-context` builds by adding various API functions/methods that use the global context implicitly. --- src/ecdsa/mod.rs | 11 ++++++++++ src/ecdsa/recovery.rs | 9 ++++++++ src/key.rs | 49 +++++++++++++++++++++++++++++++++++++++++++ src/schnorr.rs | 11 ++++++++++ 4 files changed, 80 insertions(+) diff --git a/src/ecdsa/mod.rs b/src/ecdsa/mod.rs index f7baa71..1c593e3 100644 --- a/src/ecdsa/mod.rs +++ b/src/ecdsa/mod.rs @@ -12,6 +12,9 @@ mod recovery; #[cfg_attr(docsrs, doc(cfg(feature = "recovery")))] pub use self::recovery::{RecoveryId, RecoverableSignature}; +#[cfg(feature = "global-context")] +use SECP256K1; + /// An ECDSA signature #[derive(Copy, Clone, PartialEq, Eq)] pub struct Signature(pub(crate) ffi::Signature); @@ -269,6 +272,14 @@ impl Signature { } ret } + + /// Verifies an ECDSA signature for `msg` using `pk` and the global [`SECP256K1`] context. + #[inline] + #[cfg(feature = "global-context")] + #[cfg_attr(docsrs, doc(cfg(feature = "global-context")))] + pub fn verify(&self, msg: &Message, pk: &PublicKey) -> Result<(), Error> { + SECP256K1.verify_ecdsa(msg, self, pk) + } } impl CPtr for Signature { diff --git a/src/ecdsa/recovery.rs b/src/ecdsa/recovery.rs index ca367de..b70e1e7 100644 --- a/src/ecdsa/recovery.rs +++ b/src/ecdsa/recovery.rs @@ -121,6 +121,15 @@ impl RecoverableSignature { Signature(ret) } } + + /// Determines the public key for which this [`Signature`] is valid for `msg`. Requires a + /// verify-capable context. + #[inline] + #[cfg(feature = "global-context")] + #[cfg_attr(docsrs, doc(cfg(feature = "global-context")))] + pub fn recover(&self, msg: &Message) -> Result { + SECP256K1.recover_ecdsa(msg, self) + } } diff --git a/src/key.rs b/src/key.rs index 45d631c..2f5ecc9 100644 --- a/src/key.rs +++ b/src/key.rs @@ -28,6 +28,11 @@ use Verification; use constants; use ffi::{self, CPtr}; +#[cfg(feature = "global-context")] +use {Message, ecdsa, SECP256K1}; +#[cfg(all(feature = "global-context", feature = "rand-std"))] +use schnorr; + /// Secret 256-bit key used as `x` in an ECDSA signature. /// /// # Examples @@ -279,6 +284,14 @@ impl SecretKey { } } } + + /// Constructs an ECDSA signature for `msg` using the global [`SECP256K1`] context. + #[inline] + #[cfg(feature = "global-context")] + #[cfg_attr(docsrs, doc(cfg(feature = "global-context")))] + pub fn sign_ecdsa(&self, msg: Message) -> ecdsa::Signature { + SECP256K1.sign_ecdsa(&msg, self) + } } #[cfg(feature = "serde")] @@ -349,6 +362,14 @@ impl PublicKey { } } + /// Creates a new public key from a [`SecretKey`] and the global [`SECP256K1`] context. + #[inline] + #[cfg(feature = "global-context")] + #[cfg_attr(docsrs, doc(cfg(feature = "global-context")))] + pub fn from_secret_key_global(sk: &SecretKey) -> PublicKey { + PublicKey::from_secret_key(&SECP256K1, sk) + } + /// Creates a public key directly from a slice. #[inline] pub fn from_slice(data: &[u8]) -> Result { @@ -738,6 +759,18 @@ impl KeyPair { } } + /// Creates a Schnorr [`KeyPair`] directly from a secret key string and the global [`SECP256K1`] context. + /// + /// # Errors + /// + /// [`Error::InvalidSecretKey`] if corresponding public key for the provided secret key is not even. + #[inline] + #[cfg(feature = "global-context")] + #[cfg_attr(docsrs, doc(cfg(feature = "global-context")))] + pub fn from_seckey_str_global(s: &str) -> Result { + KeyPair::from_seckey_str(SECP256K1, s) + } + /// Generates a new random secret key. /// # Examples /// @@ -768,6 +801,14 @@ impl KeyPair { } } + /// Generates a new random secret key using the global [`SECP256K1`] context. + #[inline] + #[cfg(all(feature = "global-context", feature = "rand"))] + #[cfg_attr(docsrs, doc(cfg(all(feature = "global-context", feature = "rand"))))] + pub fn new_global(rng: &mut R) -> KeyPair { + KeyPair::new(SECP256K1, rng) + } + /// Serializes the key pair as a secret key byte value. #[inline] pub fn serialize_secret(&self) -> [u8; constants::SECRET_KEY_SIZE] { @@ -830,6 +871,14 @@ impl KeyPair { pub fn public_key(&self) -> XOnlyPublicKey { XOnlyPublicKey::from_keypair(self) } + + /// Constructs an schnorr signature for `msg` using the global [`SECP256K1`] context. + #[inline] + #[cfg(all(feature = "global-context", feature = "rand-std"))] + #[cfg_attr(docsrs, doc(cfg(all(feature = "global-context", feature = "rand-std"))))] + pub fn sign_schnorr(&self, msg: Message) -> schnorr::Signature { + SECP256K1.sign_schnorr(&msg, self) + } } impl From for SecretKey { diff --git a/src/schnorr.rs b/src/schnorr.rs index c709573..e2be4ec 100644 --- a/src/schnorr.rs +++ b/src/schnorr.rs @@ -13,6 +13,9 @@ use ffi::{self, CPtr}; use {constants, Secp256k1}; use {Message, Signing, Verification, KeyPair, XOnlyPublicKey}; +#[cfg(all(feature = "global-context", feature = "rand-std"))] +use SECP256K1; + /// Represents a Schnorr signature. pub struct Signature([u8; constants::SCHNORRSIG_SIGNATURE_SIZE]); impl_array_newtype!(Signature, u8, constants::SCHNORRSIG_SIGNATURE_SIZE); @@ -88,6 +91,14 @@ impl Signature { _ => Err(Error::InvalidSignature), } } + + /// Verifies a schnorr signature for `msg` using `pk` and the global [`SECP256K1`] context. + #[inline] + #[cfg(all(feature = "global-context", feature = "rand-std"))] + #[cfg_attr(docsrs, doc(cfg(all(feature = "global-context", feature = "rand-std"))))] + pub fn verify(&self, msg: &Message, pk: &XOnlyPublicKey) -> Result<(), Error> { + SECP256K1.verify_schnorr(self, msg, pk) + } } impl Secp256k1 {