From 5562e0ff5b8dfc95f7583f8e993a039463912c96 Mon Sep 17 00:00:00 2001 From: LLFourn Date: Tue, 14 Jan 2025 15:57:08 +1100 Subject: [PATCH 1/2] =?UTF-8?q?[=E2=9D=84]=20Add=20binonce::SecretNonce?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Sometimes you don't want the keypair. When you just want to sign you shouldn't be forced to recompute it. --- schnorr_fun/src/binonce.rs | 108 +++++++++++++++++-------- schnorr_fun/src/frost/session.rs | 12 ++- schnorr_fun/src/musig.rs | 10 ++- schnorr_fun/tests/musig_sign_verify.rs | 5 +- schnorr_fun/tests/musig_tweak.rs | 7 +- 5 files changed, 93 insertions(+), 49 deletions(-) diff --git a/schnorr_fun/src/binonce.rs b/schnorr_fun/src/binonce.rs index 3d4e4318..4a28d28f 100644 --- a/schnorr_fun/src/binonce.rs +++ b/schnorr_fun/src/binonce.rs @@ -17,7 +17,7 @@ pub struct Nonce(pub [Point; 2]); impl Nonce { /// Reads the pair of nonces from 66 bytes (two 33-byte serialized points). /// - /// If either pair of 33 bytes is `[0u8;32]` that point is interpreted as `Zero`. + /// If either pair of 33 bytes is `[0u8;33]` that point is interpreted as `Zero`. pub fn from_bytes(bytes: [u8; 66]) -> Option { let R1 = Point::from_slice(&bytes[..33])?; let R2 = Point::from_slice(&bytes[33..])?; @@ -35,7 +35,7 @@ impl Nonce { /// Serializes a public nonce as as 66 bytes (two 33-byte serialized points). /// - /// If either point is `Zero` it will be serialized as `[0u8;32]`. + /// If either point is `Zero` it will be serialized as `[0u8;33]`. pub fn to_bytes(&self) -> [u8; 66] { let mut bytes = [0u8; 66]; bytes[..33].copy_from_slice(self.0[0].to_bytes().as_ref()); @@ -72,7 +72,7 @@ impl Nonce { } secp256kfun::impl_fromstr_deserialize! { - name => "public nonce pair", + name => "public binonce", fn from_bytes(bytes: [u8;66]) -> Option> { Nonce::from_bytes(bytes) } @@ -86,51 +86,64 @@ secp256kfun::impl_display_serialize! { /// A pair of secret nonces along with the public portion. /// -/// A nonce key pair can be created manually with [`from_secrets`] +/// A nonce key pair can be created manually with [`from_secret`] /// -/// [`from_secrets`]: Self::from_secrets +/// [`from_secret`]: Self::from_secret #[derive(Debug, Clone, PartialEq)] pub struct NonceKeyPair { /// The public nonce pub public: Nonce, /// The secret nonce - pub secret: [Scalar; 2], + pub secret: SecretNonce, } -impl NonceKeyPair { - /// Load nonces from two secret scalars - pub fn from_secrets(secret: [Scalar; 2]) -> Self { - let [ref r1, ref r2] = secret; - let R1 = g!(r1 * G).normalize(); - let R2 = g!(r2 * G).normalize(); - NonceKeyPair { - public: Nonce([R1, R2]), - secret, - } - } - /// Deserializes a nonce key pair from 64-bytes (two 32-byte serialized scalars). +/// A pair of secret nonces. +/// +/// ⚠ An attacker getting this allows them to extract your secret share from a signature share. +#[derive(Debug, Clone, PartialEq)] +pub struct SecretNonce(pub [Scalar; 2]); + +impl SecretNonce { + /// Deserializes a secret binonce from 64-bytes (two 32-byte serialized scalars). pub fn from_bytes(bytes: [u8; 64]) -> Option { let r1 = Scalar::from_slice(&bytes[..32])?.non_zero()?; let r2 = Scalar::from_slice(&bytes[32..])?.non_zero()?; - let R1 = g!(r1 * G).normalize(); - let R2 = g!(r2 * G).normalize(); - let pub_nonce = Nonce([R1, R2]); - Some(NonceKeyPair { - public: pub_nonce, - secret: [r1, r2], - }) + Some(Self([r1, r2])) } - /// Serializes a nonce key pair to 64-bytes (two 32-bytes serialized scalars). + /// Serializes a secret binonce to 64-bytes (two 32-bytes serialized scalars). pub fn to_bytes(&self) -> [u8; 64] { let mut bytes = [0u8; 64]; - bytes[..32].copy_from_slice(self.secret[0].to_bytes().as_ref()); - bytes[32..].copy_from_slice(self.secret[1].to_bytes().as_ref()); + bytes[..32].copy_from_slice(self.0[0].to_bytes().as_ref()); + bytes[32..].copy_from_slice(self.0[1].to_bytes().as_ref()); bytes } + /// Generate a nonce secret binonce from an rng + pub fn random(rng: &mut impl RngCore) -> Self { + Self([Scalar::random(rng), Scalar::random(rng)]) + } + + /// Convert a secret nonce into a key pair by computing the public nonce + pub fn into_keypair(self) -> NonceKeyPair { + NonceKeyPair::from_secret(self) + } +} + +impl NonceKeyPair { + /// Load nonces from two secret scalars + pub fn from_secret(secret: SecretNonce) -> Self { + let [ref r1, ref r2] = secret.0; + let R1 = g!(r1 * G).normalize(); + let R2 = g!(r2 * G).normalize(); + NonceKeyPair { + public: Nonce([R1, R2]), + secret, + } + } + /// Get the secret portion of the nonce key pair (don't share this!) - pub fn secret(&self) -> &[Scalar; 2] { + pub fn secret(&self) -> &SecretNonce { &self.secret } @@ -139,21 +152,46 @@ impl NonceKeyPair { self.public } - /// Generate a nonce keypair from an rng + /// Generate a random secret nonce and conver to a keypair pub fn random(rng: &mut impl RngCore) -> Self { - Self::from_secrets([Scalar::random(rng), Scalar::random(rng)]) + Self::from_secret(SecretNonce::random(rng)) + } +} + +impl AsRef for NonceKeyPair { + fn as_ref(&self) -> &SecretNonce { + self.secret() + } +} + +impl AsRef for SecretNonce { + fn as_ref(&self) -> &SecretNonce { + self + } +} + +secp256kfun::impl_fromstr_deserialize! { + name => "secret binonce", + fn from_bytes(bytes: [u8;64]) -> Option { + SecretNonce::from_bytes(bytes) + } +} + +secp256kfun::impl_display_serialize! { + fn to_bytes(value: &SecretNonce) -> [u8;64] { + value.to_bytes() } } secp256kfun::impl_fromstr_deserialize! { - name => "secret nonce pair", + name => "secret binonce", fn from_bytes(bytes: [u8;64]) -> Option { - NonceKeyPair::from_bytes(bytes) + Some(NonceKeyPair::from_secret(SecretNonce::from_bytes(bytes)?)) } } secp256kfun::impl_display_serialize! { - fn to_bytes(nkp: &NonceKeyPair) -> [u8;64] { - nkp.to_bytes() + fn to_bytes(value: &NonceKeyPair) -> [u8;64] { + value.secret.to_bytes() } } diff --git a/schnorr_fun/src/frost/session.rs b/schnorr_fun/src/frost/session.rs index 51030112..755a1a39 100644 --- a/schnorr_fun/src/frost/session.rs +++ b/schnorr_fun/src/frost/session.rs @@ -1,8 +1,12 @@ -use crate::{binonce, frost::PartyIndex, Signature}; +use crate::{ + binonce::{self, SecretNonce}, + frost::PartyIndex, + Signature, +}; use alloc::collections::{BTreeMap, BTreeSet}; use secp256kfun::{poly, prelude::*}; -use super::{NonceKeyPair, PairedSecretShare, SharedKey, SignatureShare, VerificationShare}; +use super::{PairedSecretShare, SharedKey, SignatureShare, VerificationShare}; /// A FROST signing session used to *verify* signatures. /// /// Created using [`coordinator_sign_session`]. @@ -214,7 +218,7 @@ impl PartySignSession { pub fn sign( &self, secret_share: &PairedSecretShare, - secret_nonce: NonceKeyPair, + secret_nonce: impl AsRef, ) -> SignatureShare { if self.public_key != secret_share.public_key() { panic!("the share's shared key is not the same as the shared key of the session"); @@ -224,7 +228,7 @@ impl PartySignSession { } let secret_share = secret_share.secret_share(); let lambda = poly::eval_basis_poly_at_0(secret_share.index, self.parties.iter().cloned()); - let [mut r1, mut r2] = secret_nonce.secret; + let [mut r1, mut r2] = secret_nonce.as_ref().0; r1.conditional_negate(self.binonce_needs_negation); r2.conditional_negate(self.binonce_needs_negation); diff --git a/schnorr_fun/src/musig.rs b/schnorr_fun/src/musig.rs index 5791a37c..c5f3c62c 100644 --- a/schnorr_fun/src/musig.rs +++ b/schnorr_fun/src/musig.rs @@ -71,7 +71,11 @@ //! //! [the excellent paper]: https://eprint.iacr.org/2020/1261.pdf //! [secp256k1-zkp]: https://github.com/ElementsProject/secp256k1-zkp/pull/131 -use crate::{adaptor::EncryptedSignature, binonce, Message, Schnorr, Signature}; +use crate::{ + adaptor::EncryptedSignature, + binonce::{self, SecretNonce}, + Message, Schnorr, Signature, +}; use alloc::vec::Vec; use secp256kfun::{ hash::{Hash32, HashAdd, Tag}, @@ -556,7 +560,7 @@ impl MuSig { session: &SignSession, my_index: usize, keypair: &KeyPair, - local_secret_nonce: binonce::NonceKeyPair, + local_secret_nonce: impl AsRef, ) -> Scalar { assert_eq!( keypair.public_key(), @@ -569,7 +573,7 @@ impl MuSig { let mut a = agg_key.coefs[my_index]; a.conditional_negate(agg_key.needs_negation); - let [mut r1, mut r2] = local_secret_nonce.secret; + let [mut r1, mut r2] = local_secret_nonce.as_ref().0; r1.conditional_negate(session.nonce_needs_negation); r2.conditional_negate(session.nonce_needs_negation); s!(c * a * x_i + r1 + b * r2).public() diff --git a/schnorr_fun/tests/musig_sign_verify.rs b/schnorr_fun/tests/musig_sign_verify.rs index c0c4abd1..ca074399 100644 --- a/schnorr_fun/tests/musig_sign_verify.rs +++ b/schnorr_fun/tests/musig_sign_verify.rs @@ -1,7 +1,6 @@ #![cfg(feature = "serde")] use schnorr_fun::{ binonce, - binonce::NonceKeyPair, fun::{marker::*, serde, Point, Scalar}, musig, Message, }; @@ -26,7 +25,7 @@ impl Maybe { #[derive(Clone, Debug)] struct SecNonce { - nonce: NonceKeyPair, + nonce: binonce::SecretNonce, pk: Point, } @@ -34,7 +33,7 @@ impl SecNonce { pub fn from_bytes(bytes: [u8; 97]) -> Option { let mut nonce = [0u8; 64]; nonce.copy_from_slice(&bytes[..64]); - let nonce = binonce::NonceKeyPair::from_bytes(nonce)?; + let nonce = binonce::SecretNonce::from_bytes(nonce)?; Some(SecNonce { nonce, pk: Point::from_slice(&bytes[64..])?, diff --git a/schnorr_fun/tests/musig_tweak.rs b/schnorr_fun/tests/musig_tweak.rs index b20e458b..9793b5e4 100644 --- a/schnorr_fun/tests/musig_tweak.rs +++ b/schnorr_fun/tests/musig_tweak.rs @@ -3,8 +3,7 @@ use std::{rc::Rc, sync::Arc}; use schnorr_fun::{ - binonce, - binonce::NonceKeyPair, + binonce::{self, NonceKeyPair}, fun::{marker::*, serde, Point, Scalar}, musig, Message, }; @@ -28,7 +27,7 @@ impl Maybe { } struct SecNonce { - nonce: NonceKeyPair, + nonce: binonce::SecretNonce, _pk: Point, } @@ -36,7 +35,7 @@ impl SecNonce { pub fn from_bytes(bytes: [u8; 97]) -> Option { let mut nonce = [0u8; 64]; nonce.copy_from_slice(&bytes[..64]); - let nonce = binonce::NonceKeyPair::from_bytes(nonce)?; + let nonce = binonce::SecretNonce::from_bytes(nonce)?; Some(SecNonce { nonce, _pk: Point::from_slice(&bytes[64..])?, From 35b5c12fd6ca730dfe74b95255a64997e26a369b Mon Sep 17 00:00:00 2001 From: LLFourn Date: Thu, 16 Jan 2025 21:58:13 +1100 Subject: [PATCH 2/2] Add some PartialEqs --- schnorr_fun/src/frost/mod.rs | 2 +- schnorr_fun/src/schnorr.rs | 2 +- secp256kfun/src/nonce.rs | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/schnorr_fun/src/frost/mod.rs b/schnorr_fun/src/frost/mod.rs index c957b52d..49165c6e 100644 --- a/schnorr_fun/src/frost/mod.rs +++ b/schnorr_fun/src/frost/mod.rs @@ -124,7 +124,7 @@ pub type PartyIndex = Scalar; /// /// - `H`: hash type for challenges, and binding coefficient. /// - `NG`: nonce generator for FROST nonces (only used if you explicitly call nonce generation functions). -#[derive(Clone)] +#[derive(Clone, Debug, PartialEq)] pub struct Frost { /// The instance of the Schnorr signature scheme. pub schnorr: Schnorr, diff --git a/schnorr_fun/src/schnorr.rs b/schnorr_fun/src/schnorr.rs index b2746ac3..df4a4583 100644 --- a/schnorr_fun/src/schnorr.rs +++ b/schnorr_fun/src/schnorr.rs @@ -20,7 +20,7 @@ use crate::{ /// [_Fiat-Shamir_]: https://en.wikipedia.org/wiki/Fiat%E2%80%93Shamir_heuristic /// [`NonceGen`]: crate::fun::hash::NonceGen /// [BIP-340]: https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki -#[derive(Clone)] +#[derive(Clone, Debug, PartialEq)] pub struct Schnorr { /// The [`NonceGen`] used to generate nonces. /// diff --git a/secp256kfun/src/nonce.rs b/secp256kfun/src/nonce.rs index 52c0954e..bc58517c 100644 --- a/secp256kfun/src/nonce.rs +++ b/secp256kfun/src/nonce.rs @@ -85,7 +85,7 @@ impl NonceRng for std::sync::Mutex { /// [`ThreadRng`]: https://docs.rs/rand/latest/rand/rngs/struct.ThreadRng.html /// [`OsRng`]: rand_core::OsRng /// [`GlobalRng`]: crate::nonce::GlobalRng -#[derive(Debug, Default, Clone)] +#[derive(Debug, Default, Clone, PartialEq)] pub struct Synthetic { rng: R, nonce_hash: H, @@ -125,7 +125,7 @@ impl Synthetic { /// ``` /// /// [`ThreadRng`]: https://docs.rs/rand/latest/rand/rngs/struct.ThreadRng.html -#[derive(Debug, Default, Clone)] +#[derive(Debug, Default, Clone, PartialEq)] pub struct GlobalRng { // Using fn(R) ensures that it is sync even if R is not sync inner: PhantomData, @@ -145,7 +145,7 @@ pub struct GlobalRng { /// let nonce_gen = Deterministic::::default().tag(b"BIP0340"); /// ``` /// [`Synthetic`]: crate::nonce::Synthetic -#[derive(Clone, Debug, Default)] +#[derive(Clone, Debug, Default, PartialEq)] pub struct Deterministic { nonce_hash: H, }