diff --git a/schnorr_fun/src/adaptor/mod.rs b/schnorr_fun/src/adaptor/mod.rs index e1714fe6..ba9893b7 100644 --- a/schnorr_fun/src/adaptor/mod.rs +++ b/schnorr_fun/src/adaptor/mod.rs @@ -54,7 +54,7 @@ use crate::{ g, marker::*, nonce::NonceGen, - s, Point, Scalar, + s, Point, Scalar, G, }, KeyPair, Message, Schnorr, Signature, }; @@ -78,7 +78,7 @@ pub trait EncryptedSign { ) -> EncryptedSignature; } -impl EncryptedSign for Schnorr +impl EncryptedSign for Schnorr where CH: Digest + Clone, NG: NonceGen, @@ -98,7 +98,7 @@ where public => [X, Y, message] ); - let R = g!(r * { self.G() } + Y) + let R = g!(r * G + Y) // R_hat = r * G is sampled pseudorandomly for every Y which means R_hat + Y is also // be pseudoranodm and therefore will not be zero. // NOTE: Crucially we add Y to the nonce derivation to ensure this is true. @@ -110,7 +110,7 @@ where // key before decrypting it r.conditional_negate(needs_negation); - let c = self.challenge(&R.to_xonly(), X, message); + let c = self.challenge(R.to_xonly(), X, message); let s_hat = s!(r + c * x).mark::(); EncryptedSignature { @@ -188,12 +188,12 @@ pub trait Adaptor { ) -> Option; } -impl Adaptor for Schnorr +impl Adaptor for Schnorr where CH: Digest + Clone, { fn encryption_key_for(&self, decryption_key: &Scalar) -> Point { - g!(decryption_key * { self.G() }).normalize() + g!(decryption_key * G).normalize() } #[must_use] @@ -216,9 +216,9 @@ where // !needs_negation => R_hat = R - Y let R_hat = g!(R + { Y.conditional_negate(!needs_negation) }); - let c = self.challenge(&R.to_xonly(), &X.to_xonly(), message); + let c = self.challenge(R.to_xonly(), X.to_xonly(), message); - R_hat == g!(s_hat * { self.G() } - c * X) + R_hat == g!(s_hat * G - c * X) } fn decrypt_signature( @@ -257,7 +257,7 @@ where let mut y = s!(s - s_hat); y.conditional_negate(*needs_negation); - let implied_encryption_key = g!(y * { self.G() }); + let implied_encryption_key = g!(y * G); if implied_encryption_key == *encryption_key { Some(y.expect_nonzero("unreachable - encryption_key is NonZero and y*G equals it")) @@ -271,48 +271,55 @@ where mod test { use super::*; - use crate::nonce::{self, Deterministic}; - use secp256kfun::TEST_SOUNDNESS; + use crate::nonce::{Deterministic, GlobalRng, Synthetic}; + use rand::rngs::ThreadRng; + use secp256kfun::proptest::prelude::*; + use sha2::Sha256; #[cfg(target_arch = "wasm32")] use wasm_bindgen_test::wasm_bindgen_test as test; - fn test_schnorr(schnorr: Schnorr) { - for _ in 0..TEST_SOUNDNESS { - let signing_keypair = schnorr.new_keypair(Scalar::random(&mut rand::thread_rng())); - let verification_key = signing_keypair.verification_key(); - let decryption_key = Scalar::random(&mut rand::thread_rng()); - let encryption_key = schnorr.encryption_key_for(&decryption_key); - let message = Message::::plain("test", b"give 100 coins to Bob".as_ref()); - - let encrypted_signature = - schnorr.encrypted_sign(&signing_keypair, &encryption_key, message); - - assert!(schnorr.verify_encrypted_signature( - &signing_keypair.verification_key(), - &encryption_key, - message, - &encrypted_signature, - )); + proptest! { + #[test] + fn signing_tests_deterministic(secret_key in any::(), decryption_key in any::()) { + let schnorr = Schnorr::>::default(); + test_it(schnorr, secret_key, decryption_key); + } - let decryption_key = decryption_key.mark::(); - let signature = - schnorr.decrypt_signature(decryption_key.clone(), encrypted_signature.clone()); - assert!(schnorr.verify(&verification_key, message, &signature)); - let rec_decryption_key = schnorr - .recover_decryption_key(&encryption_key, &encrypted_signature, &signature) - .expect("recovery works"); - assert_eq!(rec_decryption_key, decryption_key); + #[test] + fn signing_tests_synthetic(secret_key in any::(), decryption_key in any::()) { + let schnorr = Schnorr::>>::default(); + test_it(schnorr, secret_key, decryption_key); } + } - #[test] - fn sign_plain_message() { - use rand::rngs::ThreadRng; - use sha2::Sha256; - test_schnorr(Schnorr::new(Deterministic::::default())); - test_schnorr(Schnorr::new(nonce::Synthetic::< - Sha256, - nonce::GlobalRng, - >::default())); + fn test_it( + schnorr: Schnorr, + secret_key: Scalar, + decryption_key: Scalar, + ) { + let signing_keypair = schnorr.new_keypair(secret_key); + let verification_key = signing_keypair.verification_key(); + let encryption_key = schnorr.encryption_key_for(&decryption_key); + let message = Message::::plain("test", b"give 100 coins to Bob".as_ref()); + + let encrypted_signature = + schnorr.encrypted_sign(&signing_keypair, &encryption_key, message); + + assert!(schnorr.verify_encrypted_signature( + &signing_keypair.verification_key(), + &encryption_key, + message, + &encrypted_signature, + )); + + let decryption_key = decryption_key.mark::(); + let signature = + schnorr.decrypt_signature(decryption_key.clone(), encrypted_signature.clone()); + assert!(schnorr.verify(&verification_key, message, &signature)); + let rec_decryption_key = schnorr + .recover_decryption_key(&encryption_key, &encrypted_signature, &signature) + .expect("recovery works"); + assert_eq!(rec_decryption_key, decryption_key); } } diff --git a/schnorr_fun/src/keypair.rs b/schnorr_fun/src/keypair.rs index d8bf91af..6693ce87 100644 --- a/schnorr_fun/src/keypair.rs +++ b/schnorr_fun/src/keypair.rs @@ -15,7 +15,7 @@ use secp256kfun::{marker::*, Point, Scalar, XOnly}; /// ``` /// /// [`Schnorr`]: crate::Schnorr -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq)] pub struct KeyPair { pub(crate) sk: Scalar, pub(crate) pk: XOnly, @@ -27,9 +27,9 @@ impl KeyPair { &self.sk } - /// Returns a reference to the public key. - pub fn public_key(&self) -> &XOnly { - &self.pk + /// The public key + pub fn public_key(&self) -> XOnly { + self.pk } /// Gets a reference to the key-pair as a tuple @@ -39,8 +39,8 @@ impl KeyPair { /// # use schnorr_fun::{Schnorr, fun::Scalar}; /// # let keypair = schnorr_fun::test_instance!().new_keypair(Scalar::one()); /// let (secret_key, public_key) = keypair.as_tuple(); - pub fn as_tuple(&self) -> (&Scalar, &XOnly) { - (&self.sk, &self.pk) + pub fn as_tuple(&self) -> (&Scalar, XOnly) { + (&self.sk, self.pk) } /// Returns the full `Point` for the public key which is used in [`verify`]. diff --git a/schnorr_fun/src/lib.rs b/schnorr_fun/src/lib.rs index 6b0caca4..2a4cc02d 100755 --- a/schnorr_fun/src/lib.rs +++ b/schnorr_fun/src/lib.rs @@ -2,20 +2,30 @@ #![no_std] #![allow(non_snake_case)] #![doc = include_str!("../README.md")] -#![deny(warnings, missing_docs)] +#![warn(missing_docs)] + #[cfg(all(feature = "alloc", not(feature = "std")))] #[macro_use] extern crate alloc; +#[cfg(all(feature = "alloc", not(feature = "std")))] +pub(crate) use alloc::vec::Vec; #[cfg(feature = "std")] #[macro_use] extern crate std; +#[cfg(feature = "std")] +pub(crate) use std::vec::Vec; #[cfg(feature = "serde")] extern crate serde_crate as serde; pub use secp256kfun as fun; pub use secp256kfun::nonce; + +// musig needs vecs +#[cfg(feature = "alloc")] +pub mod musig; + mod signature; pub use signature::Signature; pub mod adaptor; diff --git a/schnorr_fun/src/message.rs b/schnorr_fun/src/message.rs index cd313043..b35765fe 100644 --- a/schnorr_fun/src/message.rs +++ b/schnorr_fun/src/message.rs @@ -29,7 +29,7 @@ impl<'a, 'b, S: Secrecy> Message<'a, S> { /// /// [here]: https://github.com/sipa/bips/issues/207#issuecomment-673681901 pub fn plain(app_tag: &'static str, bytes: &'a [u8]) -> Self { - assert!(app_tag.len() <= 64, "tag must not be 64 bytes or less"); + assert!(app_tag.len() <= 64, "tag must be 64 bytes or less"); assert!(!app_tag.is_empty(), "tag must not be empty"); Message { bytes: bytes.mark::(), @@ -39,7 +39,7 @@ impl<'a, 'b, S: Secrecy> Message<'a, S> { } impl HashInto for Message<'_, S> { - fn hash_into(&self, hash: &mut impl Digest) { + fn hash_into(self, hash: &mut impl Digest) { if let Some(prefix) = self.app_tag { let mut padded_prefix = [0u8; 64]; padded_prefix[..prefix.len()].copy_from_slice(prefix.as_bytes()); diff --git a/schnorr_fun/src/musig.rs b/schnorr_fun/src/musig.rs new file mode 100644 index 00000000..4dd93bc8 --- /dev/null +++ b/schnorr_fun/src/musig.rs @@ -0,0 +1,1325 @@ +//! The MuSig2 multisignature scheme. +//! +//! ## Synopsis +//! +//! ``` +//! use schnorr_fun::{musig::{MuSig, Party}, Schnorr, Message, nonce::Deterministic}; +//! use sha2::Sha256; +//! // use sha256 with deterministic nonce generation +//! let musig = MuSig::>>::default(); +//! // create a keylist +//! # use schnorr_fun::fun::Scalar; +//! # let kp1 = musig.schnorr.new_keypair(Scalar::random(&mut rand::thread_rng())); +//! # let kp3 = musig.schnorr.new_keypair(Scalar::random(&mut rand::thread_rng())); +//! # let p1_pubkey = kp1.public_key(); +//! # let p3_pubkey = kp3.public_key(); +//! # let my_keypair = musig.schnorr.new_keypair(Scalar::random(&mut rand::thread_rng())); +//! # let _keylist = musig.new_keylist(vec![ +//! # Party::Local(kp1), +//! # Party::Remote(my_keypair.public_key()), +//! # Party::Local(kp3), +//! # ]); +//! let keylist = musig.new_keylist(vec![ +//! Party::Remote(p1_pubkey), +//! Party::Local(my_keypair), +//! Party::Remote(p3_pubkey), +//! ]); +//! let message = Message::plain("my-app", b"chancellor on brink of second bailout for banks"); +//! // generate our aggregate key +//! let agg_key = keylist.agg_public_key(); +//! // start a MuSig2 session by first exchanging nonces. +//! // Since we're using deterministic nonces it's important we only use the session id once +//! let my_nonces = musig.gen_nonces(&keylist, b"session-id-1337"); +//! // send this to the other parties +//! let my_public_nonce = my_nonces[0].public(); +//! # let nonces = musig.gen_nonces(&_keylist, b"session-id-1337"); +//! # let p1_nonce = nonces[0].public(); +//! # let p3_nonce = nonces[1].public(); +//! # let mut _session = musig.start_sign_session_deterministic(&_keylist, my_nonces.iter().map(|n| n.public()), b"session-id-1337", message).unwrap(); +//! // Once you've got the nonces from the other two (p1_nonce and p3_nonce) you can start the signing session. +//! let mut session = musig.start_sign_session(&keylist, my_nonces, [p1_nonce, p3_nonce], message).unwrap(); +//! // but since we're using deterministic nonce generation we can just remember the session id. +//! // You should guarantee that this is not called ever again with the same session id!!!! +//! let mut session = musig.start_sign_session_deterministic(&keylist, [p1_nonce, p3_nonce], b"session-id-1337", message).unwrap(); +//! // sign with our (single) local keypair +//! let my_sig = musig.sign_all(&keylist, &mut session)[0]; +//! # let _sigs = musig.sign_all(&_keylist, &mut _session); +//! # let p1_sig = _sigs[0]; +//! # let p3_sig = _sigs[1]; +//! // receive p1_sig and p3_sig from somewhere and check they're valid +//! assert!(musig.verify_partial_signature(&keylist, &session, 0, p1_sig)); +//! assert!(musig.verify_partial_signature(&keylist, &session, 2, p3_sig)); +//! // combine them with ours into the final signature +//! let sig = musig.combine_partial_signatures(&keylist, &session, [my_sig, p1_sig, p3_sig]); +//! // check it's a valid normal Schnorr signature +//! musig.schnorr.verify(&keylist.agg_verification_key(), message, &sig); +//! ``` +//! +//! ## Description +//! +//! The MuSig2 multisignature scheme lets you aggregate multiple public keys into a single public +//! key that requires all of the corresponding secret keys to authorize a signature under the aggregate key. +//! +//! This implementation is protocol compatible with the implementation merged into +//! [secp256k1-zkp]. +//! +//! See [the excellent paper] for the abstract details of the protocol. +//! +//! [the excellent paper]: https://eprint.iacr.org/2020/1261.pdf +//! [secp256k1-zkp]: https://github.com/ElementsProject/secp256k1-zkp/pull/131 +use crate::{adaptor::EncryptedSignature, KeyPair, Message, Schnorr, Signature, Vec}; +use secp256kfun::{ + derive_nonce, + digest::{generic_array::typenum::U32, Digest}, + g, + hash::{HashAdd, Tagged}, + marker::*, + nonce::{Deterministic, NonceGen}, + s, Point, Scalar, XOnly, G, +}; + +/// The MuSig context. +pub struct MuSig { + /// The hash used to compress the key list to 32 bytes. + pub pk_hash: H, + /// The hash used to generate each key's coefficient. + pub coeff_hash: H, + /// The hash used to generate the nonce coefficients. + pub nonce_coeff_hash: H, + /// The instance of the underlying Schnorr context. + pub schnorr: S, +} + +impl MuSig { + fn _new(schnorr: S) -> Self { + Self { + pk_hash: H::default().tagged(b"KeyAgg list"), + coeff_hash: H::default().tagged(b"KeyAgg coefficient"), + nonce_coeff_hash: H::default().tagged(b"MuSig/noncecoef"), + schnorr, + } + } +} + +impl Default for MuSig { + fn default() -> Self { + MuSig::_new(S::default()) + } +} + +impl MuSig { + /// Creates a MuSig context that can only do key aggregation. + /// + /// # Example + /// + /// ``` + /// # use schnorr_fun::fun::{ XOnly }; + /// # let key1 = XOnly::random(&mut rand::thread_rng()); + /// # let key2 = XOnly::random(&mut rand::thread_rng()); + /// use schnorr_fun::musig::{MuSig, Party}; + /// use sha2::Sha256; + /// let musig = MuSig::::keyagg_only(); + /// let keylist = musig.new_keylist(vec![Party::Remote(key1), Party::Remote(key2)]); + /// println!("{:?}", keylist.agg_public_key()) + /// ``` + pub fn keyagg_only() -> Self { + Self::_new(()) + } +} + +impl MuSig> { + /// Generate a new MuSig context from a Schnorr context. + pub fn new(schnorr: Schnorr) -> Self { + Self::_new(schnorr) + } +} + +/// A party in the protocol. +/// +/// A party is either local (we know the secret key) or remote (we only know the public key). +#[derive(Debug, Clone)] +pub enum Party { + /// A local party (we have the keypair) + Local(KeyPair), + /// A remote party (we only know the public key) + Remote(XOnly), +} + +/// A list of keys aggregated into a single key. +/// +/// Created using [`MuSig::new_keylist`]. +/// +/// The `KeyList` can't be serialized but it's very efficient to re-create it from the initial list of keys. +/// +/// [`MuSig::new_keylist`] +#[derive(Debug, Clone)] +pub struct KeyList { + /// The parties involved in the key aggregation. + parties: Vec, + /// The coefficients of each key + coefs: Vec>, + /// The aggregate key + agg_key: Point, + /// The + tweak: Scalar, + needs_negation: bool, +} + +impl KeyList { + /// The `XOnly` aggregated key for the keylist. + pub fn agg_public_key(&self) -> XOnly { + self.agg_key.to_xonly() + } + /// The aggregated key for the keylist as a `Point`. + pub fn agg_verification_key(&self) -> Point { + self.agg_key + } + /// An iterator over the **public keys** of each party in the keylist. + pub fn keys(&self) -> impl Iterator + '_ { + self.parties.iter().map(|party| match party { + Party::Local(keypair) => keypair.public_key(), + Party::Remote(xonly) => *xonly, + }) + } + + /// Returns an iterator over the parties + pub fn parties(&self) -> impl Iterator { + self.parties.iter() + } + + /// Clear all secret keys from the parties. + /// + /// i.e. convert all [`Party::Local`] into [`Party::Remote`] + pub fn clear_secrets(self) -> KeyList { + Self { + parties: self.keys().map(|key| Party::Remote(key)).collect(), + ..self + } + } + + /// *Tweak* the aggregated key with a scalar so that the resulting key is equal to the existing + /// key plus `tweak * G`. The tweak mutates the public key while still allowing the original set + /// of signers to sign under the new key. + /// + /// This is how you embed a taproot commitment into a key. + /// + /// ## Return value + /// + /// Returns a new keylist with the same parties but a different aggregated public key. In the + /// unusual case that the tweak is exactly equal to the negation of the aggregated secret key + /// it returns `None`. + pub fn tweak(&self, tweak: Scalar) -> Option { + let mut tweak = s!(self.tweak + tweak).mark::(); + let (agg_key, needs_negation) = g!(self.agg_key + tweak * G) + .mark::()? + .into_point_with_even_y(); + + tweak.conditional_negate(needs_negation); + + let needs_negation = self.needs_negation ^ needs_negation; + + Some(KeyList { + parties: self.parties.clone(), + coefs: self.coefs.clone(), + agg_key, + tweak, + needs_negation, + }) + } +} + +impl + Clone, S> MuSig { + /// Generates a new key list from a list of parties. + /// + /// Each party can be local (you know the secret key) or remote (you only know the public key). + /// + /// ## Example + /// + /// ``` + /// use schnorr_fun::{ + /// fun::{Point, Scalar, XOnly}, + /// musig::{MuSig, Party}, + /// nonce::Deterministic, + /// Schnorr, + /// }; + /// # let my_secret_key = Scalar::random(&mut rand::thread_rng()); + /// # let their_public_key = XOnly::random(&mut rand::thread_rng()); + /// use sha2::Sha256; + /// let musig = MuSig::>>::default(); + /// let my_keypair = musig.schnorr.new_keypair(my_secret_key); + /// // Note the keys have to come in the same order on the other side! + /// let keylist = musig.new_keylist(vec![ + /// Party::Local(my_keypair), + /// Party::Remote(their_public_key), + /// ]); + /// ``` + pub fn new_keylist(&self, parties: Vec) -> KeyList { + let keys = parties + .iter() + .map(|party| match party { + Party::Local(keypair) => keypair.public_key(), + Party::Remote(xonly) => *xonly, + }) + .collect::>(); + + let coeff_hash = { + let L = self.pk_hash.clone().add(&keys[..]).finalize(); + self.coeff_hash.clone().add(L.as_slice()) + }; + + let mut second = None; + let coefs = keys + .iter() + .map(|key| { + // This is the logic for IsSecond from appendix B of the MuSig2 paper + if second.is_none() && key != &keys[0] { + second = Some(key); + } + if second != Some(key) { + Scalar::from_hash(coeff_hash.clone().add(key)) + } else { + Scalar::one() + } + .mark::() + }) + .collect::>(); + let points = keys.into_iter().map(|x| x.to_point()).collect::>(); + + let (agg_key, needs_negation) = crate::fun::op::lincomb(coefs.iter(), points.iter()) + .expect_nonzero("computationally unreachable: linear combination of hash randomised points cannot add to zero") + .into_point_with_even_y(); + + KeyList { + parties, + coefs, + agg_key, + tweak: Scalar::zero().mark::(), + needs_negation, + } + } +} + +/// A nonce (pair of points) that each party must share with the others in the first stage of signing. +#[derive(Clone, Copy, PartialEq, Debug)] +pub struct Nonce(pub [Point; 2]); + +impl Nonce { + /// Reads the pair of nonces from 66 bytes (two 33-byte serialized points). + pub fn from_bytes(bytes: [u8; 66]) -> Option { + let R1 = Point::from_slice(&bytes[..33])?; + let R2 = Point::from_slice(&bytes[33..])?; + Some(Nonce([R1, R2])) + } + + /// Serializes a public nonce as as 66 bytes (two 33-byte serialized points). + pub fn to_bytes(&self) -> [u8; 66] { + let mut bytes = [0u8; 66]; + bytes[..33].copy_from_slice(self.0[0].to_bytes().as_ref()); + bytes[33..].copy_from_slice(self.0[1].to_bytes().as_ref()); + bytes + } +} + +secp256kfun::impl_fromstr_deserialize! { + name => "MuSig2 public nonce pair", + fn from_bytes(bytes: [u8;66]) -> Option { + Nonce::from_bytes(bytes) + } +} + +secp256kfun::impl_display_serialize! { + fn to_bytes(nonce: &Nonce) -> [u8;66] { + nonce.to_bytes() + } +} + +/// A pair of secret nonces along with the public portion. +/// +/// A nonce key pair can be created manually with [`from_secrets`] or with [`MuSig::gen_nonces`]. +/// +/// [`from_secrets`]: Self::from_secrets +/// [`MuSig::gen_nonces`]: Self::gen_nonces +#[derive(Debug, Clone, PartialEq)] +pub struct NonceKeyPair { + /// The public nonce + public: Nonce, + /// The secret nonce + secret: [Scalar; 2], +} + +impl NonceKeyPair { + /// Creates a keypair from two secret scalars. + /// + /// ## Security + /// + /// You must never use the same `NonceKeyPair` into two signing sessions. + /// + /// ## Example + /// ``` + /// use schnorr_fun::{fun::Scalar, musig::NonceKeyPair}; + /// let nkp = NonceKeyPair::from_secrets([ + /// Scalar::random(&mut rand::thread_rng()), + /// Scalar::random(&mut rand::thread_rng()), + /// ]); + /// ``` + 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). + pub fn from_bytes(bytes: [u8; 64]) -> Option { + let r1 = Scalar::from_slice(&bytes[..32])?.mark::()?; + let r2 = Scalar::from_slice(&bytes[32..])?.mark::()?; + 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], + }) + } + + /// Serializes a nonce key pair 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 + } + + /// Get the secret portion of the nonce key pair (don't share this!) + pub fn secret(&self) -> &[Scalar; 2] { + &self.secret + } + + /// Get the public portion of the nonce key pair (share this!) + pub fn public(&self) -> Nonce { + self.public + } +} + +secp256kfun::impl_fromstr_deserialize! { + name => "MuSig secret nonce pair", + fn from_bytes(bytes: [u8;64]) -> Option { + NonceKeyPair::from_bytes(bytes) + } +} + +secp256kfun::impl_display_serialize! { + fn to_bytes(nkp: &NonceKeyPair) -> [u8;64] { + nkp.to_bytes() + } +} + +impl + Clone, NG: NonceGen> MuSig> { + /// Generate nonces for your local keys in keylist. + /// + /// It is very important to carefully consider the implications of your choice of underlying + /// [`NonceGen`]. + /// + /// Using a [`Synthetic`] nonce generator will mean you don't have to worry about passing a + /// unique `sid` (session id) to this function for each signing session. The downside is that + /// you must recall the result of `gen_nonces` somewhere and store it for use when you want to + /// start the signing session with [`start_sign_session`]. + /// + /// Using a [`Deterministic`] nonce generator means you **must** never start two signing + /// sessions with nonces generated from the same `sid`. If you do your secret key will be + /// recoverable from the two partial signatures you created with the same nonce. The upside is + /// that you can call [`start_sign_session_deterministic`] with the `sid` you originally passed + /// to `gen_nonces` without having to store the output of `gen_nonces`. + /// + /// Note that the API allows you to BYO nonces by creating `NonceKeyPair`s manually. + /// + /// [`NonceGen`]: secp256kfun::nonce::NonceGen + /// [`Synthetic`]: secp256kfun::nonce::Synthetic + /// [`Deterministic`]: secp256kfun::nonce::Deterministic + /// [`start_sign_session`]: Self::start_sign_session + /// [`start_sign_session_deterministic`]: Self::start_sign_session_deterministic + pub fn gen_nonces(&self, keylist: &KeyList, sid: &[u8]) -> Vec { + keylist + .parties + .iter() + .filter_map(|party| match party { + Party::Local(keypair) => { + let r1 = derive_nonce!( + nonce_gen => self.schnorr.nonce_gen(), + secret => keypair.secret_key(), + public => [ b"r1", keypair.public_key(), keylist.agg_public_key(), sid] + ); + let r2 = derive_nonce!( + nonce_gen => self.schnorr.nonce_gen(), + secret => keypair.secret_key(), + public => [ b"r2", keypair.public_key(), keylist.agg_public_key(), sid] + ); + let R1 = g!(r1 * G).normalize(); + let R2 = g!(r2 * G).normalize(); + Some(NonceKeyPair { + public: Nonce([R1, R2]), + secret: [r1, r2], + }) + } + Party::Remote(_) => None, + }) + .collect() + } +} + +/// Marker type for indicating the [`SignSession`] is being used to create an ordinary Schnorr +/// signature. +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr( + feature = "serde", + derive(serde::Deserialize, serde::Serialize), + serde(crate = "serde_crate") +)] +pub struct Ordinary; + +/// Marks the [`SignSession`] as being used to create an adaptor (a.k.a. one-time encrypted) +/// signature. +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr( + feature = "serde", + derive(serde::Deserialize, serde::Serialize), + serde(crate = "serde_crate") +)] +pub struct Adaptor { + y_needs_negation: bool, +} + +/// A signing session. +/// +/// Created by [`start_sign_session`] or [`start_encrypted_sign_session`]. +/// The type parameter records whether you are trying to jointly generate a signature or an adaptor signature. +/// +/// ## Security +/// +/// This struct has **secret nonces** in it up until you call [`clear_secrets`] or [`sign_all`]. If +/// a malicious party gains access to it before and you generate a partial signature with this session they +/// will be able to recover your secret key. If this is a concern simply avoid serializing this +/// struct (until you've cleared it) and recreate it only when you need it. +/// +/// [`start_sign_session`]: MuSig::start_sign_session +/// [`start_encrypted_sign_session`]: MuSig::start_encrypted_sign_session +/// [`clear_secrets`]: SignSession::clear_secrets +/// [`sign_all`]: MuSig::sign_all +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr( + feature = "serde", + derive(serde::Deserialize, serde::Serialize), + serde(crate = "serde_crate") +)] +pub struct SignSession { + b: Scalar, + c: Scalar, + local_secret_nonces: Vec<[Scalar; 2]>, + public_nonces: Vec, + R: Point, + signing_type: T, +} + +impl SignSession { + /// Removes all secret nonce data from the session. + /// + /// The session works as normal except that it can no longer be used for signing ([`sign_all`] will return an empty vector). + /// + /// [`sign_all`]: MuSig::sign_all + pub fn clear_secrets(&mut self) { + self.local_secret_nonces.drain(..); + } +} + +impl + Clone, NG> MuSig> { + /// Start a signing session. + /// + /// You must provide you local secret nonces (the public portion must be shared with the other signer(s)). + /// If you are using deterministic nonce generations it's possible to use [`start_sign_session_deterministic`] instead. + /// + /// ## Return Value + /// + /// Returns `None` in the case that the `remote_nonces` have been (maliciously) selected to + /// cancel out your local nonces. + /// This is not a security issue -- we just can't continue the protocol if this happens. + /// + /// # Panics + /// + /// Panics if number of local or remote nonces passed in does not align with the parties in + /// `keylist`. + /// + /// [`start_sign_session_deterministic`]: Self::start_sign_session_deterministic + pub fn start_sign_session( + &self, + keylist: &KeyList, + local_nonces: Vec, + remote_nonces: impl IntoIterator, + message: Message<'_, Public>, + ) -> Option { + let (b, c, local_secret_nonces, public_nonces, R, _) = self._start_sign_session( + keylist, + local_nonces, + remote_nonces, + message, + &Point::zero(), + )?; + Some(SignSession { + b, + c, + local_secret_nonces, + public_nonces, + R, + signing_type: Ordinary, + }) + } + + /// Start an encrypted signing session. + /// + /// i.e. a session to produce an adaptor signature under `encryption_key`. + /// See [`adaptor`] for a more general description of adaptor signatures. + /// + /// You must provide you local secret nonces (the public portion must be shared with the other + /// signer(s)). If you are using deterministic nonce generation it's possible to use + /// [`start_encrypted_sign_session_deterministic`] instead. + /// + /// ## Return Value + /// + /// Returns `None` in the case that the `remote_nonces` have been (maliciously) selected to + /// cancel out your local nonces. + /// This is not a security issue -- we just can't continue the protocol if this happens. + /// + /// # Panics + /// + /// Panics if number of local or remote nonces passed in does not align with the parties in + /// `keylist`. + /// + /// [`start_encrypted_sign_session_deterministic`]: Self::start_sign_session_deterministic + /// [`adaptor`]: crate::adaptor + pub fn start_encrypted_sign_session( + &self, + keylist: &KeyList, + local_nonces: Vec, + remote_nonces: impl IntoIterator, + message: Message<'_, Public>, + encryption_key: &Point, + ) -> Option> { + let (b, c, local_secret_nonces, public_nonces, R, y_needs_negation) = self + ._start_sign_session( + keylist, + local_nonces, + remote_nonces, + message, + encryption_key, + )?; + Some(SignSession { + b, + c, + local_secret_nonces, + public_nonces, + R, + signing_type: Adaptor { y_needs_negation }, + }) + } + + fn _start_sign_session( + &self, + keylist: &KeyList, + local_nonces: Vec, + remote_nonces: impl IntoIterator, + message: Message<'_, Public>, + encryption_key: &Point, + ) -> Option<( + Scalar, + Scalar, + Vec<[Scalar; 2]>, + Vec, + Point, + bool, + )> { + let mut remote_nonces = remote_nonces.into_iter(); + let mut local_pubnonces = local_nonces.iter().map(|nonce| nonce.public); + + let mut Rs = keylist + .parties + .iter() + .map(|party| match party { + Party::Local(_) => local_pubnonces + .next() + .expect("missing local nonce keypair -- must have one for each local party"), + Party::Remote(_) => remote_nonces.next().expect("missing remote nonce"), + }) + .collect::>(); + + assert!( + remote_nonces.next().is_none(), + "Too many remote nonces passed in" + ); + assert!( + local_pubnonces.next().is_none(), + "Too many local nonces passed in" + ); + + let agg_Rs = Rs + .iter() + .fold([Point::zero().mark::(); 2], |acc, nonce| { + [ + g!({ acc[0] } + { nonce.0[0] }), + g!({ acc[1] } + { nonce.0[1] }), + ] + }); + let agg_Rs = [ + g!({ agg_Rs[0] } + encryption_key) + .normalize() + .mark::()?, + agg_Rs[1].normalize().mark::()?, + ]; + + let b = { + let H = self.nonce_coeff_hash.clone(); + Scalar::from_hash(H.add(agg_Rs).add(keylist.agg_public_key()).add(message)) + } + .mark::<(Public, Zero)>(); + + let (R, r_needs_negation) = g!({ agg_Rs[0] } + b * { agg_Rs[1] } ) + .normalize() + .expect_nonzero("computationally unreachable: one of the coefficients is a hash output that commits to both point") + .into_point_with_even_y(); + + for R in &mut Rs { + R.0[0] = R.0[0].conditional_negate(r_needs_negation); + R.0[1] = R.0[1].conditional_negate(r_needs_negation); + } + + let local_secret_nonces = local_nonces + .into_iter() + .map(|local_nonce| { + let mut secret = local_nonce.secret; + secret[0].conditional_negate(r_needs_negation); + secret[1].conditional_negate(r_needs_negation); + secret + }) + .collect(); + + let c = self + .schnorr + .challenge(R.to_xonly(), keylist.agg_public_key(), message); + + Some((b, c, local_secret_nonces, Rs, R, r_needs_negation)) + } + + /// Generates partial signatures (or partial encrypted signatures depending on `T`) under each of the `Local` entries in `keylist`. + /// + /// The order of the partial signatures returned is the order of them in the keylist. + /// + /// This can only be called once per session as it clears the session (see also [`clear_secrets`]). + /// Calling `sign_all` again will return an empty vector. + /// + /// [`clear_secrets`]: SignSession::clear_secrets + pub fn sign_all( + &self, + keylist: &KeyList, + session: &mut SignSession, + ) -> Vec> { + let c = session.c; + let b = session.b; + + keylist + .parties + .iter() + .enumerate() + .filter_map(|(i, party)| match party { + Party::Local(keypair) => Some((i, keypair)), + Party::Remote(_) => None, + }) + .zip(session.local_secret_nonces.drain(..)) + .map(|((i, keypair), secret_nonces)| { + let x = keypair.secret_key(); + let [r1, r2] = secret_nonces; + let mut a = keylist.coefs[i]; + a.conditional_negate(keylist.needs_negation); + s!(c * a * x + r1 + b * r2).mark::<(Public, Zero)>() + }) + .collect() + } + + #[must_use] + /// Verifies a partial signature (or partial encrypted signature depending on `T`). + /// + /// You must provide the `index` of the party (the index of the key in `keylist`). + /// + /// # Panics + /// + /// Panics when `index` is equal to or greater than the number of parties in the keylist. + pub fn verify_partial_signature( + &self, + keylist: &KeyList, + session: &SignSession, + index: usize, + partial_sig: Scalar, + ) -> bool { + let c = session.c; + let b = session.b; + let s = &partial_sig; + let mut a = keylist.coefs[index].clone(); + a.conditional_negate(keylist.needs_negation); + let X = keylist.keys().nth(index).unwrap().to_point(); + let [ref R1, ref R2] = &session.public_nonces[index].0; + g!((c * a) * X + R1 + b * R2 - s * G).is_zero() + } + + /// Combines all the partial signatures into a single `Signature`. + /// + /// Note this does not check the validity of any of the partial signatures. You should either check + /// each one using [`verify_partial_signature`] or use [`verify`] on the returned `Signature` to check validity. + /// + /// [`verify`]: crate::Schnorr::verify + /// [`verify_partial_signature`]: Self::verify_partial_signature + pub fn combine_partial_signatures( + &self, + keylist: &KeyList, + session: &SignSession, + partial_sigs: impl IntoIterator>, + ) -> Signature { + let (R, s) = self._combine_partial_signatures(keylist, &session, partial_sigs); + Signature { R: R.to_xonly(), s } + } + + /// Combines all the partial encrypted signatures into one encrypted signature. + /// + /// Note this does not check the validity of any of the partial signatures. You should either check + /// each one using [`verify_partial_signature`] or use [`verify_encrypted_signature`] on the returned `Signature` to check validity. + /// + /// [`verify_encrypted_signature`]: crate::adaptor::Adaptor::verify_encrypted_signature + /// [`verify_partial_signature`]: Self::verify_partial_signature + pub fn combine_partial_encrypted_signatures( + &self, + keylist: &KeyList, + session: &SignSession, + partial_encrypted_sigs: impl IntoIterator>, + ) -> EncryptedSignature { + let (R, s_hat) = + self._combine_partial_signatures(keylist, &session, partial_encrypted_sigs); + EncryptedSignature { + R, + s_hat, + needs_negation: session.signing_type.y_needs_negation, + } + } + + fn _combine_partial_signatures( + &self, + keylist: &KeyList, + session: &SignSession, + partial_sigs: impl IntoIterator>, + ) -> (Point, Scalar) { + let ck = s!(session.c * keylist.tweak); + let sum_s = partial_sigs + .into_iter() + .reduce(|acc, s| s!(acc + s).mark::()) + .unwrap_or(Scalar::zero().mark::()); + let s = s!(sum_s + ck).mark::(); + (session.R, s) + } +} + +impl + Clone, HNG> MuSig>> +where + Deterministic: NonceGen, +{ + /// Same as [`start_sign_session`] but re-generate the local nonces deterministically from the + /// `sid` instead of passing them in. + /// + /// ## Security + /// + /// Each call to this function must have a unique `sid`. Never call it twice with the same + /// `sid`, otherwise you risk revealing your secret key with the two signatures generated from + /// it if `message` or `remote_nonces` changes. + /// + /// [`start_sign_session`]: Self::start_sign_session + pub fn start_sign_session_deterministic( + &self, + keylist: &KeyList, + remote_nonces: impl IntoIterator, + sid: &[u8], + message: Message<'_, Public>, + ) -> Option { + let local_nonces = self.gen_nonces(keylist, sid); + self.start_sign_session(keylist, local_nonces, remote_nonces, message) + } + + /// Same as [`start_encrypted_sign_session`] but re-generate the local nonces deterministically from the + /// `sid` instead of passing them in. + /// + /// ## Security + /// + /// Each call to this function must have a unique `sid`. Never call it twice with the same + /// `sid`, otherwise you risk revealing your secret key with the two signatures generated from + /// it if `message` or `remote_nonces` changes. + /// + /// [`start_encrypted_sign_session`]: Self::start_encrypted_sign_session + pub fn start_encrypted_sign_session_deterministic( + &self, + keylist: &KeyList, + remote_nonces: impl IntoIterator, + sid: &[u8], + message: Message<'_, Public>, + encryption_key: &Point, + ) -> Option> { + let local_nonces = self.gen_nonces(keylist, sid); + self.start_encrypted_sign_session( + keylist, + local_nonces, + remote_nonces, + message, + encryption_key, + ) + } +} + +#[cfg(test)] +mod test { + use crate::adaptor::Adaptor; + + use super::*; + use secp256kfun::{nonce::Deterministic, proptest::prelude::*}; + use sha2::Sha256; + + proptest! { + #[test] + fn test_end_to_end(sk1 in any::(), sk2 in any::(), sk3 in any::(), tweak in any::>(), use_tweak in any::()) { + let schnorr = Schnorr::::new(Deterministic::::default()); + let musig = MuSig::new(schnorr); + let keypair1 = musig + .schnorr + .new_keypair(sk1); + let keypair2 = musig + .schnorr + .new_keypair(sk2); + let keypair3 = musig + .schnorr + .new_keypair(sk3); + let mut keylist_p1 = musig.new_keylist(vec![ + Party::Local(keypair1.clone()), + Party::Remote(keypair2.public_key()), + Party::Local(keypair3.clone()), + ]); + let mut keylist_p2 = musig.new_keylist(vec![ + Party::Remote(keypair1.public_key()), + Party::Local(keypair2), + Party::Remote(keypair3.public_key()), + ]); + + if use_tweak { + keylist_p1 = keylist_p1.tweak(tweak).unwrap(); + keylist_p2 = keylist_p2.tweak(tweak).unwrap(); + } + assert_eq!(keylist_p1.agg_public_key(), keylist_p2.agg_public_key()); + + let p1_nonces = musig.gen_nonces(&keylist_p1, b"test"); + let p2_nonces = musig.gen_nonces(&keylist_p2, b"test"); + let message = + Message::::plain("test", b"Chancellor on brink of second bailout for banks"); + + let mut p1_session = musig + .start_sign_session( + &keylist_p1, + p1_nonces.clone(), + p2_nonces.iter().map(|nonce| nonce.public), + message, + ) + .unwrap(); + let mut p2_session = musig + .start_sign_session_deterministic( + &keylist_p2, + p1_nonces.iter().map(|nonce| nonce.public), + b"test", + message, + ) + .unwrap(); + let p1_sigs = musig.sign_all(&keylist_p1, &mut p1_session); + assert_eq!(musig.sign_all(&keylist_p1, &mut p1_session).len(), 0, "sign_all should hose the session"); + + assert_eq!(p1_sigs.len(), 2); + for (j, i) in [0, 2].iter().enumerate() { + assert!(musig.verify_partial_signature(&keylist_p2, &p2_session, *i, p1_sigs[j])); + assert!(musig.verify_partial_signature(&keylist_p1, &p1_session, *i, p1_sigs[j])); + } + + let p2_sigs = musig.sign_all(&keylist_p2, &mut p2_session); + assert_eq!(p2_sigs.len(), 1); + assert!(musig.verify_partial_signature(&keylist_p2, &p2_session, 1, p2_sigs[0])); + assert!(musig.verify_partial_signature(&keylist_p1, &p1_session, 1, p2_sigs[0])); + + let partial_sigs = [p1_sigs, p2_sigs].concat(); + let sig_p1 = musig.combine_partial_signatures(&keylist_p1, &p1_session, partial_sigs.clone()); + let sig_p2 = musig.combine_partial_signatures(&keylist_p2, &p2_session, partial_sigs); + assert_eq!(sig_p1, sig_p2); + assert!(musig + .schnorr + .verify(&keylist_p1.agg_verification_key(), message, &sig_p1)); + assert!(musig + .schnorr + .verify(&keylist_p2.agg_verification_key(), message, &sig_p2)); + } + + #[test] + fn test_musig_adaptor(sk1 in any::(), sk2 in any::(), sk3 in any::(), y in any::()) { + let schnorr = Schnorr::::new(Deterministic::::default()); + let musig = MuSig::new(schnorr); + let keypair1 = musig + .schnorr + .new_keypair(sk1); + let keypair2 = musig + .schnorr + .new_keypair(sk2); + let keypair3 = musig + .schnorr + .new_keypair(sk3); + let encryption_key = musig.schnorr.encryption_key_for(&y); + let keylist_p1 = musig.new_keylist(vec![ + Party::Local(keypair1.clone()), + Party::Remote(keypair2.public_key()), + Party::Local(keypair3.clone()), + ]); + let keylist_p2 = musig.new_keylist(vec![ + Party::Remote(keypair1.public_key()), + Party::Local(keypair2), + Party::Remote(keypair3.public_key()), + ]); + assert_eq!(keylist_p1.agg_public_key(), keylist_p2.agg_public_key()); + + let p1_nonces = musig.gen_nonces(&keylist_p1, b"test"); + let p2_nonces = musig.gen_nonces(&keylist_p2, b"test"); + let message = + Message::::plain("test", b"Chancellor on brink of second bailout for banks"); + + let mut p1_session = musig + .start_encrypted_sign_session( + &keylist_p1, + p1_nonces.clone(), + p2_nonces.iter().map(|nonce| nonce.public), + message, + &encryption_key + ) + .unwrap(); + let mut p2_session = musig + .start_encrypted_sign_session_deterministic( + &keylist_p2, + p1_nonces.iter().map(|nonce| nonce.public), + b"test", + message, + &encryption_key + ) + .unwrap(); + let p1_sigs = musig.sign_all(&keylist_p1, &mut p1_session); + + assert_eq!(p1_sigs.len(), 2); + for (j, i) in [0, 2].iter().enumerate() { + assert!(musig.verify_partial_signature(&keylist_p2, &p2_session, *i, p1_sigs[j])); + assert!(musig.verify_partial_signature(&keylist_p1, &p1_session, *i, p1_sigs[j])); + } + + let p2_sigs = musig.sign_all(&keylist_p2, &mut p2_session); + assert_eq!(p2_sigs.len(), 1); + assert!(musig.verify_partial_signature(&keylist_p2, &p2_session, 1, p2_sigs[0])); + assert!(musig.verify_partial_signature(&keylist_p1, &p1_session, 1, p2_sigs[0])); + + let partial_sigs = [p1_sigs, p2_sigs].concat(); + let sig_p1 = musig.combine_partial_encrypted_signatures(&keylist_p1, &p1_session, partial_sigs.clone()); + let sig_p2 = musig.combine_partial_encrypted_signatures(&keylist_p2, &p2_session, partial_sigs); + assert_eq!(sig_p1, sig_p2); + assert!(musig + .schnorr + .verify_encrypted_signature(&keylist_p1.agg_verification_key(), &encryption_key, message, &sig_p1)); + assert!(musig + .schnorr + .verify_encrypted_signature(&keylist_p2.agg_verification_key(), &encryption_key, message, &sig_p2)); + + } + + } + #[test] + fn sign_test_vectors() { + let musig = MuSig::>>::default(); + + let X1 = XOnly::from_bytes([ + 0xF9, 0x30, 0x8A, 0x01, 0x92, 0x58, 0xC3, 0x10, 0x49, 0x34, 0x4F, 0x85, 0xF8, 0x9D, + 0x52, 0x29, 0xB5, 0x31, 0xC8, 0x45, 0x83, 0x6F, 0x99, 0xB0, 0x86, 0x01, 0xF1, 0x13, + 0xBC, 0xE0, 0x36, 0xF9, + ]) + .unwrap(); + let X2 = XOnly::from_bytes([ + 0xDF, 0xF1, 0xD7, 0x7F, 0x2A, 0x67, 0x1C, 0x5F, 0x36, 0x18, 0x37, 0x26, 0xDB, 0x23, + 0x41, 0xBE, 0x58, 0xFE, 0xAE, 0x1D, 0xA2, 0xDE, 0xCE, 0xD8, 0x43, 0x24, 0x0F, 0x7B, + 0x50, 0x2B, 0xA6, 0x59, + ]) + .unwrap(); + let keypair = musig.schnorr.new_keypair( + Scalar::from_bytes([ + 0x7F, 0xB9, 0xE0, 0xE6, 0x87, 0xAD, 0xA1, 0xEE, 0xBF, 0x7E, 0xCF, 0xE2, 0xF2, 0x1E, + 0x73, 0xEB, 0xDB, 0x51, 0xA7, 0xD4, 0x50, 0x94, 0x8D, 0xFE, 0x8D, 0x76, 0xD7, 0xF2, + 0xD1, 0x00, 0x76, 0x71, + ]) + .unwrap() + .mark::() + .unwrap(), + ); + + let secnonce = NonceKeyPair::from_bytes([ + 0x50, 0x8B, 0x81, 0xA6, 0x11, 0xF1, 0x00, 0xA6, 0xB2, 0xB6, 0xB2, 0x96, 0x56, 0x59, + 0x08, 0x98, 0xAF, 0x48, 0x8B, 0xCF, 0x2E, 0x1F, 0x55, 0xCF, 0x22, 0xE5, 0xCF, 0xB8, + 0x44, 0x21, 0xFE, 0x61, 0xFA, 0x27, 0xFD, 0x49, 0xB1, 0xD5, 0x00, 0x85, 0xB4, 0x81, + 0x28, 0x5E, 0x1C, 0xA2, 0x05, 0xD5, 0x5C, 0x82, 0xCC, 0x1B, 0x31, 0xFF, 0x5C, 0xD5, + 0x4A, 0x48, 0x98, 0x29, 0x35, 0x59, 0x01, 0xF7, + ]) + .unwrap(); + + let agg_pubnonce = Nonce::from_bytes([ + 0x02, 0x84, 0x65, 0xFC, 0xF0, 0xBB, 0xDB, 0xCF, 0x44, 0x3A, 0xAB, 0xCC, 0xE5, 0x33, + 0xD4, 0x2B, 0x4B, 0x5A, 0x10, 0x96, 0x6A, 0xC0, 0x9A, 0x49, 0x65, 0x5E, 0x8C, 0x42, + 0xDA, 0xAB, 0x8F, 0xCD, 0x61, 0x03, 0x74, 0x96, 0xA3, 0xCC, 0x86, 0x92, 0x6D, 0x45, + 0x2C, 0xAF, 0xCF, 0xD5, 0x5D, 0x25, 0x97, 0x2C, 0xA1, 0x67, 0x5D, 0x54, 0x93, 0x10, + 0xDE, 0x29, 0x6B, 0xFF, 0x42, 0xF7, 0x2E, 0xEE, 0xA8, 0xC9, + ]) + .unwrap(); + let remote_nonces = vec![ + agg_pubnonce, + Nonce([-secnonce.public.0[0], -secnonce.public.0[1]]), + ]; + let msg = [ + 0xF9, 0x54, 0x66, 0xD0, 0x86, 0x77, 0x0E, 0x68, 0x99, 0x64, 0x66, 0x42, 0x19, 0x26, + 0x6F, 0xE5, 0xED, 0x21, 0x5C, 0x92, 0xAE, 0x20, 0xBA, 0xB5, 0xC9, 0xD7, 0x9A, 0xDD, + 0xDD, 0xF3, 0xC0, 0xCF, + ]; + { + let keylist = musig.new_keylist(vec![ + Party::Local(keypair.clone()), + Party::Remote(X1), + Party::Remote(X2), + ]); + let mut session = musig + .start_sign_session( + &keylist, + vec![secnonce.clone()], + remote_nonces.clone(), + Message::::raw(&msg), + ) + .unwrap(); + + let scalar = musig.sign_all(&keylist, &mut session)[0]; + let expected = Scalar::from_bytes([ + 0x68, 0x53, 0x7C, 0xC5, 0x23, 0x4E, 0x50, 0x5B, 0xD1, 0x40, 0x61, 0xF8, 0xDA, 0x9E, + 0x90, 0xC2, 0x20, 0xA1, 0x81, 0x85, 0x5F, 0xD8, 0xBD, 0xB7, 0xF1, 0x27, 0xBB, 0x12, + 0x40, 0x3B, 0x4D, 0x3B, + ]) + .unwrap(); + assert_eq!(scalar, expected) + } + + { + let keylist = musig.new_keylist(vec![ + Party::Remote(X1), + Party::Local(keypair.clone()), + Party::Remote(X2), + ]); + let mut session = musig + .start_sign_session( + &keylist, + vec![secnonce.clone()], + remote_nonces.clone(), + Message::::raw(&msg), + ) + .unwrap(); + + let scalar = musig.sign_all(&keylist, &mut session)[0]; + let expected = Scalar::from_bytes([ + 0x2D, 0xF6, 0x7B, 0xFF, 0xF1, 0x8E, 0x3D, 0xE7, 0x97, 0xE1, 0x3C, 0x64, 0x75, 0xC9, + 0x63, 0x04, 0x81, 0x38, 0xDA, 0xEC, 0x5C, 0xB2, 0x0A, 0x35, 0x7C, 0xEC, 0xA7, 0xC8, + 0x42, 0x42, 0x95, 0xEA, + ]) + .unwrap(); + assert_eq!(scalar, expected) + } + + { + let keylist = musig.new_keylist(vec![ + Party::Remote(X1), + Party::Remote(X2), + Party::Local(keypair.clone()), + ]); + let mut session = musig + .start_sign_session( + &keylist, + vec![secnonce.clone()], + remote_nonces.clone(), + Message::::raw(&msg), + ) + .unwrap(); + + let scalar = musig.sign_all(&keylist, &mut session)[0]; + let expected = Scalar::from_bytes([ + 0x0D, 0x5B, 0x65, 0x1E, 0x6D, 0xE3, 0x4A, 0x29, 0xA1, 0x2D, 0xE7, 0xA8, 0xB4, 0x18, + 0x3B, 0x4A, 0xE6, 0xA7, 0xF7, 0xFB, 0xE1, 0x5C, 0xDC, 0xAF, 0xA4, 0xA3, 0xD1, 0xBC, + 0xAA, 0xBC, 0x75, 0x17, + ]) + .unwrap(); + assert_eq!(scalar, expected) + } + + { + let tweak = Scalar::from_bytes([ + 0xE8, 0xF7, 0x91, 0xFF, 0x92, 0x25, 0xA2, 0xAF, 0x01, 0x02, 0xAF, 0xFF, 0x4A, 0x9A, + 0x72, 0x3D, 0x96, 0x12, 0xA6, 0x82, 0xA2, 0x5E, 0xBE, 0x79, 0x80, 0x2B, 0x26, 0x3C, + 0xDF, 0xCD, 0x83, 0xBB, + ]) + .unwrap(); + let keylist = musig + .new_keylist(vec![ + Party::Remote(X1), + Party::Remote(X2), + Party::Local(keypair.clone()), + ]) + .tweak(tweak) + .unwrap(); + let mut session = musig + .start_sign_session( + &keylist, + vec![secnonce.clone()], + remote_nonces.clone(), + Message::::raw(&msg), + ) + .unwrap(); + + let scalar = musig.sign_all(&keylist, &mut session)[0]; + let expected = Scalar::from_bytes([ + 0x5E, 0x24, 0xC7, 0x49, 0x6B, 0x56, 0x5D, 0xEB, 0xC3, 0xB9, 0x63, 0x9E, 0x6F, 0x13, + 0x04, 0xA2, 0x15, 0x97, 0xF9, 0x60, 0x3D, 0x3A, 0xB0, 0x5B, 0x49, 0x13, 0x64, 0x17, + 0x75, 0xE1, 0x37, 0x5B, + ]) + .unwrap(); + + assert!(musig.verify_partial_signature(&keylist, &mut session, 2, scalar)); + assert_eq!(scalar, expected) + } + + { + let sec_adaptor = Scalar::from_bytes([ + 0xD5, 0x6A, 0xD1, 0x85, 0x00, 0xF2, 0xD7, 0x8A, 0xB9, 0x54, 0x80, 0x53, 0x76, 0xF3, + 0x9D, 0x1B, 0x6D, 0x62, 0x04, 0x95, 0x12, 0x39, 0x04, 0x6D, 0x99, 0x3A, 0x9C, 0x31, + 0xE0, 0xF4, 0x78, 0x71, + ]) + .unwrap() + .expect_nonzero(""); + let pub_adatpor = g!(sec_adaptor * G); + + let keylist = musig.new_keylist(vec![ + Party::Remote(X1), + Party::Remote(X2), + Party::Local(keypair), + ]); + + let mut session = musig + .start_encrypted_sign_session( + &keylist, + vec![secnonce.clone()], + remote_nonces.clone(), + Message::::raw(&msg), + &pub_adatpor, + ) + .unwrap(); + + let scalar = musig.sign_all(&keylist, &mut session)[0]; + let expected = Scalar::from_bytes([ + 0xD7, 0x67, 0xD0, 0x7D, 0x9A, 0xB8, 0x19, 0x8C, 0x9F, 0x64, 0xE3, 0xFD, 0x9F, 0x7B, + 0x8B, 0xAA, 0xC6, 0x05, 0xF1, 0x8D, 0xFF, 0x18, 0x95, 0x24, 0x2D, 0x93, 0x95, 0xD9, + 0xC8, 0xE6, 0xDD, 0x7C, + ]) + .unwrap(); + assert_eq!(scalar, expected) + } + } + + #[test] + fn test_key_aggregation() { + let musig = MuSig::::keyagg_only(); + + // test taken from + // https://github.com/ElementsProject/secp256k1-zkp/blob/5d2df0541960554be5c0ba58d86e5fa479935000/src/modules/musig/tests_impl.h + let x1 = XOnly::from_bytes([ + 0xF9, 0x30, 0x8A, 0x01, 0x92, 0x58, 0xC3, 0x10, 0x49, 0x34, 0x4F, 0x85, 0xF8, 0x9D, + 0x52, 0x29, 0xB5, 0x31, 0xC8, 0x45, 0x83, 0x6F, 0x99, 0xB0, 0x86, 0x01, 0xF1, 0x13, + 0xBC, 0xE0, 0x36, 0xF9, + ]) + .unwrap(); + let x2 = XOnly::from_bytes([ + 0xDF, 0xF1, 0xD7, 0x7F, 0x2A, 0x67, 0x1C, 0x5F, 0x36, 0x18, 0x37, 0x26, 0xDB, 0x23, + 0x41, 0xBE, 0x58, 0xFE, 0xAE, 0x1D, 0xA2, 0xDE, 0xCE, 0xD8, 0x43, 0x24, 0x0F, 0x7B, + 0x50, 0x2B, 0xA6, 0x59, + ]) + .unwrap(); + let x3 = XOnly::from_bytes([ + 0x35, 0x90, 0xA9, 0x4E, 0x76, 0x8F, 0x8E, 0x18, 0x15, 0xC2, 0xF2, 0x4B, 0x4D, 0x80, + 0xA8, 0xE3, 0x14, 0x93, 0x16, 0xC3, 0x51, 0x8C, 0xE7, 0xB7, 0xAD, 0x33, 0x83, 0x68, + 0xD0, 0x38, 0xCA, 0x66, + ]) + .unwrap(); + + let x1_x2_x3 = vec![x1, x2, x3]; + let expected_x1_x2_x3 = XOnly::from_bytes([ + 0xE5, 0x83, 0x01, 0x40, 0x51, 0x21, 0x95, 0xD7, 0x4C, 0x83, 0x07, 0xE3, 0x96, 0x37, + 0xCB, 0xE5, 0xFB, 0x73, 0x0E, 0xBE, 0xAB, 0x80, 0xEC, 0x51, 0x4C, 0xF8, 0x8A, 0x87, + 0x7C, 0xEE, 0xEE, 0x0B, + ]) + .unwrap(); + assert_eq!( + musig + .new_keylist(x1_x2_x3.into_iter().map(Party::Remote).collect()) + .agg_public_key(), + expected_x1_x2_x3 + ); + + let x3_x2_x1 = vec![x3, x2, x1]; + let expected_x3_x2_x1 = XOnly::from_bytes([ + 0xD7, 0x0C, 0xD6, 0x9A, 0x26, 0x47, 0xF7, 0x39, 0x09, 0x73, 0xDF, 0x48, 0xCB, 0xFA, + 0x2C, 0xCC, 0x40, 0x7B, 0x8B, 0x2D, 0x60, 0xB0, 0x8C, 0x5F, 0x16, 0x41, 0x18, 0x5C, + 0x79, 0x98, 0xA2, 0x90, + ]) + .unwrap(); + assert_eq!( + musig + .new_keylist(x3_x2_x1.into_iter().map(Party::Remote).collect()) + .agg_public_key(), + expected_x3_x2_x1 + ); + + let x1_x1_x1 = vec![x1, x1, x1]; + let expected_x1_x1_x1 = XOnly::from_bytes([ + 0x81, 0xA8, 0xB0, 0x93, 0x91, 0x2C, 0x9E, 0x48, 0x14, 0x08, 0xD0, 0x97, 0x76, 0xCE, + 0xFB, 0x48, 0xAE, 0xB8, 0xB6, 0x54, 0x81, 0xB6, 0xBA, 0xAF, 0xB3, 0xC5, 0x81, 0x01, + 0x06, 0x71, 0x7B, 0xEB, + ]) + .unwrap(); + assert_eq!( + musig + .new_keylist(x1_x1_x1.into_iter().map(Party::Remote).collect()) + .agg_public_key(), + expected_x1_x1_x1 + ); + + let x1_x1_x2_x2 = vec![x1, x1, x2, x2]; + let expected_x1_x1_x2_x2 = XOnly::from_bytes([ + 0x2E, 0xB1, 0x88, 0x51, 0x88, 0x7E, 0x7B, 0xDC, 0x5E, 0x83, 0x0E, 0x89, 0xB1, 0x9D, + 0xDB, 0xC2, 0x80, 0x78, 0xF1, 0xFA, 0x88, 0xAA, 0xD0, 0xAD, 0x01, 0xCA, 0x06, 0xFE, + 0x4F, 0x80, 0x21, 0x0B, + ]) + .unwrap(); + assert_eq!( + musig + .new_keylist(x1_x1_x2_x2.into_iter().map(Party::Remote).collect()) + .agg_public_key(), + expected_x1_x1_x2_x2 + ); + } +} diff --git a/schnorr_fun/src/schnorr.rs b/schnorr_fun/src/schnorr.rs index 0f28421f..ba7a42b0 100644 --- a/schnorr_fun/src/schnorr.rs +++ b/schnorr_fun/src/schnorr.rs @@ -1,12 +1,12 @@ use crate::{ fun::{ - self, derive_nonce, + derive_nonce, digest::{generic_array::typenum::U32, Digest}, g, hash::{HashAdd, Tagged}, marker::*, nonce::{AddTag, NonceGen}, - s, Point, Scalar, XOnly, + s, Point, Scalar, XOnly, G, }, KeyPair, Message, Signature, }; @@ -14,18 +14,14 @@ use crate::{ /// An instance of a [BIP-340] style Schnorr signature scheme. /// /// Each instance is defined by its: -/// - `G`: Public generator (usually [`G`]) /// - `challenge_hash`: The hash function instance that is used to produce the [_Fiat-Shamir_] challenge. /// - `nonce_gen`: The [`NonceGen`] used to hash the signing inputs (and perhaps additional randomness) to produce the secret nonce. /// /// [_Fiat-Shamir_]: https://en.wikipedia.org/wiki/Fiat%E2%80%93Shamir_heuristic -/// [`G`]: crate::fun::G /// [`NonceGen`]: crate::fun::hash::NonceGen /// [BIP-340]: https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki #[derive(Clone)] -pub struct Schnorr { - /// The generator point the Schnorr signature scheme is defined with. - G: Point, +pub struct Schnorr { /// The [`NonceGen`] used to generate nonces. /// /// [`NonceGen`]: crate::nonce::NonceGen @@ -34,18 +30,23 @@ pub struct Schnorr { challenge_hash: CH, } -impl + Tagged> Schnorr { +impl + Tagged> Schnorr { /// Create a new instance that can only verify signatures. /// - /// The instance will use the standard value of [`G`]. + /// # Example + /// + /// ``` + /// use schnorr_fun::Schnorr; + /// use sha2::Sha256; /// - /// [`G`]: secp256kfun::G + /// let schnorr = Schnorr::::verify_only(); + /// ``` pub fn verify_only() -> Self { Self::new(()) } } -impl Schnorr +impl Schnorr where CH: Digest + Tagged, NG: AddTag, @@ -71,14 +72,31 @@ where pub fn new(nonce_gen: NG) -> Self { let nonce_gen = nonce_gen.add_tag("BIP0340"); Self { - G: fun::G.clone(), nonce_gen, challenge_hash: CH::default().tagged("BIP0340/challenge".as_bytes()), } } } -impl Schnorr +impl, NG: Default + AddTag> Default + for Schnorr +{ + /// Returns a Schnorr instance tagged in the default way according to BIP340. + /// + /// # Examples + /// + /// ``` + /// use schnorr_fun::{nonce::Deterministic, Schnorr}; + /// use sha2::Sha256; + /// + /// let schnorr = Schnorr::>::default(); + /// ``` + fn default() -> Self { + Self::new(NG::default()) + } +} + +impl Schnorr where CH: Digest + Clone, NG: NonceGen, @@ -110,8 +128,8 @@ where public => [X, message] ); - let R = XOnly::from_scalar_mul(&self.G, &mut r); - let c = self.challenge(&R, X, message); + let R = XOnly::from_scalar_mul(&G, &mut r); + let c = self.challenge(R, X, message); let s = s!(r + c * x).mark::(); Signature { R, s } @@ -125,12 +143,7 @@ where } } -impl + Clone, GT> Schnorr { - /// Returns the generator point being used for the scheme. - pub fn G(&self) -> &Point { - &self.G - } - +impl + Clone> Schnorr { /// Returns the challenge hash being used to sign/verify signatures pub fn challenge_hash(&self) -> CH { self.challenge_hash.clone() @@ -145,7 +158,7 @@ impl + Clone, GT> Schnorr { /// [`Point`]: crate::fun::Point /// [`EvenY`]: crate::fun::marker::EvenY pub fn new_keypair(&self, mut sk: Scalar) -> KeyPair { - let pk = XOnly::from_scalar_mul(&self.G, &mut sk); + let pk = XOnly::from_scalar_mul(&G, &mut sk); KeyPair { sk, pk } } @@ -160,15 +173,15 @@ impl + Clone, GT> Schnorr { /// /// ``` /// use schnorr_fun::{ - /// fun::{marker::*, s, Scalar, XOnly}, + /// fun::{marker::*, s, Scalar, XOnly, G}, /// Message, Schnorr, Signature, /// }; /// # let schnorr = schnorr_fun::test_instance!(); /// let message = Message::::plain("my-app", b"we rolled our own schnorr!"); /// let keypair = schnorr.new_keypair(Scalar::random(&mut rand::thread_rng())); /// let mut r = Scalar::random(&mut rand::thread_rng()); - /// let R = XOnly::from_scalar_mul(schnorr.G(), &mut r); - /// let challenge = schnorr.challenge(&R, keypair.public_key(), message); + /// let R = XOnly::from_scalar_mul(G, &mut r); + /// let challenge = schnorr.challenge(R, keypair.public_key(), message); /// let s = s!(r + challenge * { keypair.secret_key() }); /// let signature = Signature { R, s }; /// assert!(schnorr.verify(&keypair.verification_key(), message, &signature)); @@ -176,12 +189,7 @@ impl + Clone, GT> Schnorr { /// /// [BIP-340]: https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki /// [`Secrecy`]: secp256kfun::marker::Secrecy - pub fn challenge( - &self, - R: &XOnly, - X: &XOnly, - m: Message<'_, S>, - ) -> Scalar { + pub fn challenge(&self, R: XOnly, X: XOnly, m: Message<'_, S>) -> Scalar { let hash = self.challenge_hash.clone(); let challenge = Scalar::from_hash(hash.add(R).add(X).add(&m)); @@ -228,9 +236,9 @@ impl + Clone, GT> Schnorr { ) -> bool { let X = public_key; let (R, s) = signature.as_tuple(); - let c = self.challenge(R, &X.to_xonly(), message); - let R_implied = g!(s * self.G - c * X).mark::(); - R_implied == *R + let c = self.challenge(R, X.to_xonly(), message); + let R_implied = g!(s * G - c * X).mark::(); + R_implied == R } /// _Anticipates_ a Schnorr signature given the nonce `R` that will be used ahead of time. @@ -242,25 +250,55 @@ impl + Clone, GT> Schnorr { R: &Point, m: Message<'_, impl Secrecy>, ) -> Point { - let c = self.challenge(&R.to_xonly(), &X.to_xonly(), m); + let c = self.challenge(R.to_xonly(), X.to_xonly(), m); g!(R + c * X) } } #[cfg(test)] pub mod test { - use fun::nonce::Deterministic; + use crate::fun::nonce::Deterministic; use super::*; - use crate::fun::TEST_SOUNDNESS; + use crate::fun::proptest::prelude::*; #[cfg(target_arch = "wasm32")] use wasm_bindgen_test::wasm_bindgen_test as test; #[test] - fn anticipated_signature_on_should_correspond_to_actual_signature() { - for _ in 0..TEST_SOUNDNESS { + fn deterministic_nonces_for_different_message_kinds() { + use core::str::FromStr; + use sha2::Sha256; + let schnorr = Schnorr::::new(Deterministic::::default()); + let x = + Scalar::from_str("18451f9e08af9530814243e202a4a977130e672079f5c14dcf15bd4dee723072") + .unwrap(); + let keypair = schnorr.new_keypair(x); + assert_ne!( + schnorr.sign(&keypair, Message::::raw(b"foo")).R, + schnorr + .sign(&keypair, Message::::plain("one", b"foo")) + .R + ); + assert_ne!( + schnorr + .sign(&keypair, Message::::plain("one", b"foo")) + .R, + schnorr + .sign(&keypair, Message::::plain("two", b"foo")) + .R + ); + + // make sure deterministic signatures don't change + assert_eq!(schnorr.sign(&keypair, Message::::raw(b"foo")), Signature::::from_str("fe9e5d0319d5d221988d6fd7fe1c4bedd2fb4465f592f1002f461503332a266977bb4a0b00c00d07072c796212cbea0957ebaaa5139143761c45d997ebe36cbe").unwrap()); + assert_eq!(schnorr.sign(&keypair, Message::::plain("one", b"foo")), Signature::::from_str("2fcf6fd140bbc4048e802c62f028e24f6534e0d15d450963265b67eead774d8b4aa7638bec9d70aa60b97e86bc4a60bf43ad2ff58e981ee1bba4f45ce02ff2c0").unwrap()); + } + + proptest! { + + #[test] + fn anticipated_signature_on_should_correspond_to_actual_signature(sk in any::()) { let schnorr = crate::test_instance!(); - let keypair = schnorr.new_keypair(Scalar::random(&mut rand::thread_rng())); + let keypair = schnorr.new_keypair(sk); let msg = Message::::plain( "test", b"Chancellor on brink of second bailout for banks", @@ -274,18 +312,16 @@ pub mod test { assert_eq!( anticipated_signature, - g!(signature.s * schnorr.G), + g!(signature.s * G), "should anticipate the same value as actual signature" ) } - } - #[test] - fn sign_deterministic() { - let schnorr = crate::test_instance!(); - for _ in 0..TEST_SOUNDNESS { - let keypair_1 = schnorr.new_keypair(Scalar::random(&mut rand::thread_rng())); - let keypair_2 = schnorr.new_keypair(Scalar::random(&mut rand::thread_rng())); + #[test] + fn sign_deterministic(s1 in any::(), s2 in any::()) { + let schnorr = crate::test_instance!(); + let keypair_1 = schnorr.new_keypair(s1); + let keypair_2 = schnorr.new_keypair(s2); let msg_atkdwn = Message::::plain("test", b"attack at dawn"); let msg_rtrtnoon = Message::::plain("test", b"retreat at noon"); let signature_1 = schnorr.sign(&keypair_1, msg_atkdwn); @@ -295,37 +331,10 @@ pub mod test { assert!(schnorr.verify(&keypair_1.verification_key(), msg_atkdwn, &signature_1)); assert_eq!(signature_1, signature_2); - assert_ne!(signature_3.R, signature_1.R); - assert_ne!(signature_1.R, signature_4.R); + if keypair_1 != keypair_2 { + assert_ne!(signature_3.R, signature_1.R); + assert_ne!(signature_1.R, signature_4.R); + } } } - - #[test] - fn deterministic_nonces_for_different_message_kinds() { - use core::str::FromStr; - use sha2::Sha256; - let schnorr = Schnorr::::new(Deterministic::::default()); - let x = - Scalar::from_str("18451f9e08af9530814243e202a4a977130e672079f5c14dcf15bd4dee723072") - .unwrap(); - let keypair = schnorr.new_keypair(x); - assert_ne!( - schnorr.sign(&keypair, Message::::raw(b"foo")).R, - schnorr - .sign(&keypair, Message::::plain("one", b"foo")) - .R - ); - assert_ne!( - schnorr - .sign(&keypair, Message::::plain("one", b"foo")) - .R, - schnorr - .sign(&keypair, Message::::plain("two", b"foo")) - .R - ); - - // make sure deterministic signatures don't change - assert_eq!(schnorr.sign(&keypair, Message::::raw(b"foo")), Signature::::from_str("fe9e5d0319d5d221988d6fd7fe1c4bedd2fb4465f592f1002f461503332a266977bb4a0b00c00d07072c796212cbea0957ebaaa5139143761c45d997ebe36cbe").unwrap()); - assert_eq!(schnorr.sign(&keypair, Message::::plain("one", b"foo")), Signature::::from_str("2fcf6fd140bbc4048e802c62f028e24f6534e0d15d450963265b67eead774d8b4aa7638bec9d70aa60b97e86bc4a60bf43ad2ff58e981ee1bba4f45ce02ff2c0").unwrap()); - } } diff --git a/schnorr_fun/src/signature.rs b/schnorr_fun/src/signature.rs index 0ff08634..9257399a 100644 --- a/schnorr_fun/src/signature.rs +++ b/schnorr_fun/src/signature.rs @@ -47,8 +47,8 @@ impl Signature { /// # let signature = schnorr_fun::Signature::random(&mut rand::thread_rng()); /// let (R, s) = signature.as_tuple(); /// ``` - pub fn as_tuple(&self) -> (&XOnly, &Scalar) { - (&self.R, &self.s) + pub fn as_tuple(&self) -> (XOnly, &Scalar) { + (self.R, &self.s) } /// Marks the signature with a [`Secrecy`]. If it is marked as `Secret` the diff --git a/schnorr_fun/tests/bip340.rs b/schnorr_fun/tests/bip340.rs index dd0b2673..5bb20409 100644 --- a/schnorr_fun/tests/bip340.rs +++ b/schnorr_fun/tests/bip340.rs @@ -46,11 +46,11 @@ fn signing_test_vectors() { let line: Vec<&str> = line.split(',').collect(); let aux_bytes = hex::decode(line[3]).unwrap(); let fake_rng = AuxRng(&aux_bytes[..]); - let bip340 = Schnorr::::new(Synthetic::::new(fake_rng)); + let bip340 = Schnorr::::new(Synthetic::::new(fake_rng)); let secret_key = Scalar::::from_str(line[1]).unwrap(); let expected_public_key = XOnly::from_str(line[2]).unwrap(); let keypair = bip340.new_keypair(secret_key); - assert_eq!(keypair.public_key(), &expected_public_key); + assert_eq!(keypair.public_key(), expected_public_key); let message = hex::decode(line[4]).unwrap(); let signature = bip340.sign(&keypair, Message::::raw(&message)); let expected_signature = Signature::::from_str(line[5]).unwrap(); diff --git a/secp256kfun/Cargo.toml b/secp256kfun/Cargo.toml index 280a8306..38bb75c8 100644 --- a/secp256kfun/Cargo.toml +++ b/secp256kfun/Cargo.toml @@ -20,8 +20,8 @@ digest = "0.9" subtle = { package = "subtle-ng", version = "2" } rand_core = { version = "0.6" } serde_crate = { package = "serde", version = "1.0", optional = true, default-features = false, features = ["derive"] } -# secp256kfun_k256_backend = { path = "../../k256_backend/k256", features = ["expose-field"] } -secp256kfun_k256_backend = { version = "1.0.0", features = ["expose-field"] } +# secp256kfun_k256_backend = { path = "../../k256_backend/k256" } +secp256kfun_k256_backend = { version = "2.0.0" } secp256kfun_parity_backend = { version = "0.1.5", optional = true } secp256k1 = { version = "0.20", optional = true, default-features = false } proptest = { version = "1", optional = true } @@ -48,7 +48,7 @@ wasm-bindgen-test = "0.3" [features] default = ["std"] all = ["std", "serde", "libsecp_compat", "nightly"] -alloc = ["serde_crate/alloc"] +alloc = ["serde_crate/alloc", "secp256kfun_k256_backend/alloc"] std = ["alloc"] libsecp_compat = ["secp256k1"] serde = [ "serde_crate" ] diff --git a/secp256kfun/src/backend/k256.rs b/secp256kfun/src/backend/k256.rs index bac59df9..00f526ea 100644 --- a/secp256kfun/src/backend/k256.rs +++ b/secp256kfun/src/backend/k256.rs @@ -310,6 +310,13 @@ impl TimeSensitive for ConstantTime { fn xonly_eq(lhs: &XOnly, rhs: &XOnly) -> bool { lhs.0.ct_eq(&rhs.0).into() } + + fn lincomb_iter<'a, 'b, A: Iterator, B: Iterator>( + points: A, + scalars: B, + ) -> Point { + secp256kfun_k256_backend::lincomb_iter(points, scalars) + } } pub struct VariableTime; @@ -439,6 +446,14 @@ impl TimeSensitive for VariableTime { fn point_double_mul(x: &Scalar, A: &Point, y: &Scalar, B: &Point) -> Point { ConstantTime::point_double_mul(x, A, y, B) } + + #[cfg(feature = "alloc")] + fn lincomb_iter<'a, 'b, A: Iterator, B: Iterator>( + points: A, + scalars: B, + ) -> Point { + ConstantTime::lincomb_iter(points, scalars) + } } impl VariableTime { diff --git a/secp256kfun/src/backend/mod.rs b/secp256kfun/src/backend/mod.rs index 601863d8..cd4f28d6 100644 --- a/secp256kfun/src/backend/mod.rs +++ b/secp256kfun/src/backend/mod.rs @@ -76,4 +76,12 @@ pub trait TimeSensitive { fn scalar_invert(scalar: &Scalar) -> Scalar; fn scalar_mul_basepoint(scalar: &Scalar, base: &BasePoint) -> Point; fn xonly_eq(lhs: &XOnly, rhs: &XOnly) -> bool; + fn lincomb_iter<'a, 'b, A: Iterator, B: Iterator>( + points: A, + scalars: B, + ) -> Point { + points.zip(scalars).fold(Point::zero(), |acc, (X, k)| { + Self::point_add_point(&acc, &Self::scalar_mul_point(k, X)) + }) + } } diff --git a/secp256kfun/src/hash.rs b/secp256kfun/src/hash.rs index 8e9f4ff3..e23fbd09 100644 --- a/secp256kfun/src/hash.rs +++ b/secp256kfun/src/hash.rs @@ -56,42 +56,68 @@ where /// ``` /// use digest::Digest; /// use secp256kfun::hash::{HashAdd, HashInto}; +/// #[derive(Clone, Copy)] /// struct CryptoData([u8; 42]); /// /// impl HashInto for CryptoData { -/// fn hash_into(&self, hash: &mut impl digest::Digest) { +/// fn hash_into(self, hash: &mut impl digest::Digest) { /// hash.update(&self.0[..]) /// } /// } /// /// let cryptodata = CryptoData([42u8; 42]); -/// let hash = sha2::Sha256::default().add(&cryptodata).finalize(); +/// let hash = sha2::Sha256::default().add(cryptodata).finalize(); /// ``` pub trait HashInto { /// Asks the item to convert itself to bytes and add itself to `hash`. - fn hash_into(&self, hash: &mut impl digest::Digest); + fn hash_into(self, hash: &mut impl digest::Digest); } -impl HashInto for [u8] { - fn hash_into(&self, hash: &mut impl digest::Digest) { - hash.update(self) +impl HashInto for u8 { + fn hash_into(self, hash: &mut impl digest::Digest) { + hash.update(&[self]) } } -impl HashInto for str { - fn hash_into(&self, hash: &mut impl digest::Digest) { +impl<'a, T: HashInto + Clone> HashInto for &'a T { + fn hash_into(self, hash: &mut impl digest::Digest) { + self.clone().hash_into(hash) + } +} + +impl<'a, T> HashInto for &'a [T] +where + &'a T: HashInto, +{ + fn hash_into(self, hash: &mut impl digest::Digest) { + for item in self { + item.hash_into(hash) + } + } +} + +impl HashInto for &str { + fn hash_into(self, hash: &mut impl digest::Digest) { hash.update(self.as_bytes()) } } +impl HashInto for [T; N] { + fn hash_into(self, hash: &mut impl digest::Digest) { + for item in self { + item.hash_into(hash) + } + } +} + /// Extension trait for [`digest::Digest`] to make adding things to the hash convenient. pub trait HashAdd { /// Converts something that implements [`HashInto`] to bytes and then incorporate the result into the digest (`self`). - fn add(self, data: &HI) -> Self; + fn add(self, data: HI) -> Self; } impl HashAdd for D { - fn add(mut self, data: &HI) -> Self { + fn add(mut self, data: HI) -> Self { data.hash_into(&mut self); self } diff --git a/secp256kfun/src/macros.rs b/secp256kfun/src/macros.rs index a051e49c..506558d0 100644 --- a/secp256kfun/src/macros.rs +++ b/secp256kfun/src/macros.rs @@ -321,7 +321,7 @@ macro_rules! derive_nonce { use core::borrow::Borrow; use $crate::nonce::NonceGen; Scalar::from_hash( - $nonce_gen.begin_derivation($secret.borrow())$(.add($public.borrow()))+ + $nonce_gen.begin_derivation($secret.borrow())$(.add($public))+ ) }} } diff --git a/secp256kfun/src/op.rs b/secp256kfun/src/op.rs index 156a3fd2..5457d73b 100644 --- a/secp256kfun/src/op.rs +++ b/secp256kfun/src/op.rs @@ -85,6 +85,20 @@ pub fn point_add( Point::from_inner(PointBinary::add(A, B), Jacobian) } +/// Does a linear combination of points +pub fn lincomb<'a, T1: 'a, S1: 'a, Z1: 'a, S2: 'a, Z2: 'a>( + scalars: impl IntoIterator>, + points: impl IntoIterator>, +) -> Point { + Point::from_inner( + ConstantTime::lincomb_iter( + points.into_iter().map(|p| &p.0), + scalars.into_iter().map(|s| &s.0), + ), + Jacobian, + ) +} + pub(crate) trait PointBinary { fn add(&self, rhs: &Point) -> backend::Point; fn sub(&self, rhs: &Point) -> backend::Point; @@ -630,4 +644,22 @@ mod test { .unwrap(); assert_eq!(R_implied, R_expected); } + + use proptest::prelude::*; + + proptest! { + + #[test] + fn lincomb_against_mul(a in any::(), + b in any::(), + c in any::(), + A in any::(), + B in any::(), + C in any::() + ) { + use crate::op::*; + assert_eq!(lincomb([&a,&b,&c], [&A,&B,&C]), + point_add(&scalar_mul_point(&a, &A), &point_add(&scalar_mul_point(&b, &B), &scalar_mul_point(&c, &C)))) + } + } } diff --git a/secp256kfun/src/point.rs b/secp256kfun/src/point.rs index 7d62d896..3b0efdf7 100644 --- a/secp256kfun/src/point.rs +++ b/secp256kfun/src/point.rs @@ -387,7 +387,7 @@ impl Point { } impl HashInto for Point { - fn hash_into(&self, hash: &mut impl digest::Digest) { + fn hash_into(self, hash: &mut impl digest::Digest) { hash.update(self.to_bytes().as_ref()) } } diff --git a/secp256kfun/src/scalar.rs b/secp256kfun/src/scalar.rs index d6772581..6c50b7c2 100644 --- a/secp256kfun/src/scalar.rs +++ b/secp256kfun/src/scalar.rs @@ -49,6 +49,8 @@ use rand_core::{CryptoRng, RngCore}; #[derive(Clone, Eq)] pub struct Scalar(pub(crate) backend::Scalar, PhantomData<(Z, S)>); +impl Copy for Scalar {} + impl core::hash::Hash for Scalar { fn hash(&self, state: &mut H) { self.to_bytes().hash(state) @@ -314,8 +316,8 @@ impl core::ops::Neg for &Scalar { } } -impl HashInto for Scalar { - fn hash_into(&self, hash: &mut impl digest::Digest) { +impl HashInto for Scalar { + fn hash_into(self, hash: &mut impl digest::Digest) { hash.update(&self.to_bytes()) } } diff --git a/secp256kfun/src/slice.rs b/secp256kfun/src/slice.rs index 00c41fdc..3b768dc0 100644 --- a/secp256kfun/src/slice.rs +++ b/secp256kfun/src/slice.rs @@ -77,7 +77,7 @@ impl<'a, S> From> for &'a [u8] { } impl<'a, S> HashInto for Slice<'a, S> { - fn hash_into(&self, hash: &mut impl digest::Digest) { + fn hash_into(self, hash: &mut impl digest::Digest) { hash.update(self.inner) } } diff --git a/secp256kfun/src/xonly.rs b/secp256kfun/src/xonly.rs index b2888cfa..5aca7ae7 100644 --- a/secp256kfun/src/xonly.rs +++ b/secp256kfun/src/xonly.rs @@ -117,7 +117,7 @@ impl XOnly { } impl HashInto for XOnly { - fn hash_into(&self, hash: &mut impl digest::Digest) { + fn hash_into(self, hash: &mut impl digest::Digest) { hash.update(self.as_bytes()) } }