From 6db913b0b84755a6a676df8fa0036086c14584f3 Mon Sep 17 00:00:00 2001 From: nickfarrow Date: Tue, 16 Aug 2022 13:58:52 +1000 Subject: [PATCH] BIP340FrostKey and plain tweaks Following https://github.com/LLFourn/secp256kfun/pull/100 --- schnorr_fun/src/frost.rs | 202 ++++++++++++++++++++++++++------------- 1 file changed, 133 insertions(+), 69 deletions(-) diff --git a/schnorr_fun/src/frost.rs b/schnorr_fun/src/frost.rs index 60758ff6..f2ebe60a 100644 --- a/schnorr_fun/src/frost.rs +++ b/schnorr_fun/src/frost.rs @@ -353,15 +353,13 @@ impl std::error::Error for FinishKeyGenError {} )] pub struct FrostKey { /// The joint public key of the FROST multisignature. - pub joint_public_key: Point, + pub joint_public_key: Point, /// Everyone else's point polynomial evaluated at your index, used in partial signature validation. pub verification_shares: Vec, /// Number of partial signatures required to create a combined signature under this key. pub threshold: u32, /// Taproot tweak applied to this FROST key, tracks the aggregate tweak. tweak: Scalar, - /// Whether the secrets need negation in order to sign for the X-Only key. - needs_negation: bool, } impl FrostKey { @@ -370,55 +368,52 @@ impl FrostKey { /// ## Return value /// /// A point (normalised to have an even Y coordinate). - pub fn public_key(&self) -> Point { + pub fn public_key(&self) -> Point { self.joint_public_key } + /// Apply a plain tweak to the joint public key. + /// This is how you derive child public keys from a joint public key using BIP32. + /// /// Tweak the joint FROST public 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 FrostKey with the same parties but a different aggregated public key. + /// Returns a new FrostKey with the same parties but a different joint public key. /// In the erroneous case that the tweak is exactly equal to the negation of the aggregate /// secret key it returns `None`. pub fn tweak(&mut self, tweak: Scalar) -> Option { - // Also updates whether the FROST key needs negation. - // XOR of existing FROST key needs_negation and new tweaked key needs_negation. - // If both need negation, they will cancel out. - // - // Public key - // X = (b*x) * G - // where b = 1 or -1 - // For a tweak t: X' = X + t * G. - // If X' needs negation then we need secret - // -(b*x + t) = -b*x - t - // So new b = -b and t = -t. - // If X' doesn't need negation, leave b as is. - // i.e. previous needs_negation XOR new needs_negation. - let new_tweak = s!(0 + tweak).mark::(); - let (joint_public_key, tweaked_needs_negation) = g!(self.joint_public_key + new_tweak * G) - .mark::()? - .into_point_with_even_y(); - - let mut tweak = s!(self.tweak + tweak).mark::(); - tweak.conditional_negate(tweaked_needs_negation); + let joint_public_key = g!(self.joint_public_key + self.tweak * G) + .normalize() + .mark::()?; + let tweak = s!(self.tweak + tweak).mark::(); - let updated_needs_negation = self.needs_negation ^ tweaked_needs_negation; - - // Return the new FrostKey including the new tweak and updated needs_negation Some(FrostKey { joint_public_key, verification_shares: self.verification_shares.clone(), threshold: self.threshold.clone(), tweak, - needs_negation: updated_needs_negation, }) } + /// Convert the key into a `Bip340AggKey`. + /// + /// This is the BIP340 compatiple version of the key which you can put in a segwitv1 + pub fn into_bip340_key(self) -> Bip340FrostKey { + let (joint_public_key, needs_negation) = self.joint_public_key.into_point_with_even_y(); + let mut tweak = self.tweak; + tweak.conditional_negate(needs_negation); + Bip340FrostKey { + joint_public_key, + verification_shares: self.verification_shares, + threshold: self.threshold, + tweak, + needs_negation, + } + } + /// The threshold number of participants required in a signing coalition to produce a valid signature. pub fn threshold(&self) -> u32 { self.threshold @@ -430,6 +425,62 @@ impl FrostKey { } } +/// A [`FrostKey`] that has been converted into a [`Bip340FrostKey`] key. +/// +/// This is the BIP340 compatible version of the key which you can put in a segwitv1 output and create BIP340 signatures under. +/// Tweaks applied to a `Bip340FrostKey` are XOnly tweaks to the joint public key +/// [BIP340]: https://bips.xyz/340 +#[cfg_attr( + feature = "serde", + derive(serde::Deserialize, serde::Serialize), + serde(crate = "serde_crate") +)] +#[derive(Clone, Debug, PartialEq)] +pub struct Bip340FrostKey { + /// The BIP340 public key of the FROST multisignature. + pub joint_public_key: Point, + /// Everyone else's point polynomial evaluated at your index, used in partial signature validation. + pub verification_shares: Vec, + /// Number of partial signatures required to create a combined signature under this key. + pub threshold: u32, + /// Taproot tweak applied to this FROST key, tracks the aggregate tweak. + tweak: Scalar, + /// Whether the secret keys need to be negated during + needs_negation: bool, +} + +impl Bip340FrostKey { + /// Applies an "XOnly" tweak to the joint public key. + /// This is how you embed a taproot commitment into a joint public key + /// + /// Tweak the joint FROST public key with a scalar so that the resulting key is equal to the + /// existing key plus `tweak * G` as an [`EvenY`] point. The tweak mutates the public key while still allowing + /// the original set of signers to sign under the new key. + /// + /// ## Return value + /// + /// Returns a new FrostKey with the same parties but a different joint public key. + /// In the erroneous case that the tweak is exactly equal to the negation of the aggregate + /// secret key it returns `None`. + pub fn tweak(self, tweak: Scalar) -> Option { + let (new_joint_public_key, needs_negation) = g!(self.joint_public_key + tweak * G) + .normalize() + .mark::()? + .into_point_with_even_y(); + let mut new_tweak = s!(self.tweak + tweak).mark::(); + new_tweak.conditional_negate(needs_negation); + let needs_negation = self.needs_negation ^ needs_negation; + + Some(Self { + needs_negation, + tweak: new_tweak, + joint_public_key: new_joint_public_key, + verification_shares: self.verification_shares, + threshold: self.threshold, + }) + } +} + impl + Clone, NG: AddTag + NonceGen> Frost { /// Create secret shares and our proof-of-possession to be shared with other participants. /// @@ -507,12 +558,9 @@ impl + Clone, NG: AddTag> Frost { } let joint_poly = PointPoly::combine(point_polys.clone().into_iter()); - let frost_key = joint_poly.0[0]; - - let (joint_public_key, needs_negation) = frost_key + let joint_public_key = joint_poly.0[0] .mark::() - .ok_or(NewKeyGenError::ZeroFrostKey)? - .into_point_with_even_y(); + .ok_or(NewKeyGenError::ZeroFrostKey)?; let mut keygen_hash = self.keygen_id_hash.clone(); keygen_hash.update((len_first_poly as u32).to_be_bytes()); @@ -537,7 +585,6 @@ impl + Clone, NG: AddTag> Frost { joint_public_key, threshold: joint_poly.poly_len() as u32, tweak: Scalar::zero().mark::(), - needs_negation, }, }) } @@ -629,7 +676,7 @@ impl + Clone, NG: NonceGen + AddTag> Frost { /// A FROST signing session pub fn start_sign_session( &self, - frost_key: &FrostKey, + frost_key: &Bip340FrostKey, nonces: Vec<(u32, Nonce)>, message: Message, ) -> SignSession { @@ -696,7 +743,7 @@ impl + Clone, NG: NonceGen + AddTag> Frost { /// Returns a signature Scalar. pub fn sign( &self, - frost_key: &FrostKey, + frost_key: &Bip340FrostKey, session: &SignSession, my_index: u32, secret_share: &Scalar, @@ -731,7 +778,7 @@ impl + Clone, NG: NonceGen + AddTag> Frost { /// Returns `bool`, true if partial signature is valid. pub fn verify_signature_share( &self, - frost_key: &FrostKey, + frost_key: &Bip340FrostKey, session: &SignSession, index: u32, signature_share: Scalar, @@ -768,7 +815,7 @@ impl + Clone, NG: NonceGen + AddTag> Frost { /// Valid against the joint public key. pub fn combine_signature_shares( &self, - frost_key: &FrostKey, + frost_key: &Bip340FrostKey, session: &SignSession, partial_sigs: Vec>, ) -> Signature { @@ -874,21 +921,23 @@ mod test { } // finish keygen for each party - let (secret_shares, frost_keys): (Vec, Vec) = (0..n_parties).map(|i| { - let (secret_share, mut frost_key) = frost.finish_keygen( + let (secret_shares, frost_keys): (Vec, Vec) = (0..n_parties).map(|i| { + let (secret_share, frost_key) = frost.finish_keygen( KeyGen.clone(), i, recieved_shares[i as usize].clone(), proofs_of_possession.clone(), ) .unwrap(); + let mut bip_340_frost_key = frost_key.into_bip340_key(); + // apply some xonly tweaks for tweak in [tweak1, tweak2] { if let Some(tweak) = tweak { - frost_key = frost_key.tweak(tweak).unwrap(); + bip_340_frost_key = bip_340_frost_key.tweak(tweak).unwrap(); } } - (secret_share, frost_key) + (secret_share, bip_340_frost_key) }).unzip(); // use a boolean mask for which t participants are signers @@ -961,7 +1010,7 @@ mod test { let (shares3, pop3) = frost.create_shares(&KeyGen, sp3); let proofs_of_possession = vec![pop1, pop2, pop3]; - let (secret_share1, mut frost_key) = frost + let (secret_share1, frost_key) = frost .finish_keygen( KeyGen.clone(), 0, @@ -969,7 +1018,7 @@ mod test { proofs_of_possession.clone(), ) .unwrap(); - let (_secret_share2, mut jk2) = frost + let (_secret_share2, frost_key2) = frost .finish_keygen( KeyGen.clone(), 1, @@ -977,7 +1026,7 @@ mod test { proofs_of_possession.clone(), ) .unwrap(); - let (secret_share3, mut jk3) = frost + let (secret_share3, frost_key3) = frost .finish_keygen( KeyGen.clone(), 2, @@ -986,8 +1035,15 @@ mod test { ) .unwrap(); - assert_eq!(frost_key, jk2); - assert_eq!(frost_key, jk3); + assert_eq!(frost_key, frost_key2); + assert_eq!(frost_key, frost_key3); + + let mut bip340_frost_key = frost_key.into_bip340_key(); + let mut bip340_frost_key2 = frost_key2.into_bip340_key(); + let mut bip340_frost_key3 = frost_key3.into_bip340_key(); + + assert_eq!(bip340_frost_key, bip340_frost_key2); + assert_eq!(bip340_frost_key, bip340_frost_key3); let use_tweak = true; let tweak = if use_tweak { @@ -1001,11 +1057,11 @@ mod test { Scalar::zero() }; - frost_key = frost_key.tweak(tweak.clone()).expect("tweak worked"); - jk2 = jk2.tweak(tweak.clone()).expect("tweak worked"); - jk3 = jk3.tweak(tweak).expect("tweak worked"); - - dbg!(); + bip340_frost_key = bip340_frost_key.tweak(tweak.clone()).expect("tweak worked"); + bip340_frost_key2 = bip340_frost_key2 + .tweak(tweak.clone()) + .expect("tweak worked"); + bip340_frost_key3 = bip340_frost_key3.tweak(tweak).expect("tweak worked"); let tweak = if use_tweak { Scalar::from_bytes([ @@ -1018,11 +1074,13 @@ mod test { Scalar::zero() }; - frost_key = frost_key.tweak(tweak.clone()).expect("tweak worked"); - jk2 = jk2.tweak(tweak.clone()).expect("tweak worked"); - jk3 = jk3.tweak(tweak).expect("tweak worked"); + bip340_frost_key = bip340_frost_key.tweak(tweak.clone()).expect("tweak worked"); + bip340_frost_key2 = bip340_frost_key2 + .tweak(tweak.clone()) + .expect("tweak worked"); + bip340_frost_key3 = bip340_frost_key3.tweak(tweak).expect("tweak worked"); - let verification_shares_bytes: Vec<_> = frost_key + let verification_shares_bytes: Vec<_> = bip340_frost_key .verification_shares .iter() .map(|share| share.to_bytes()) @@ -1030,7 +1088,7 @@ mod test { // Create unique session IDs for these signing sessions let sid1 = [ - frost_key.joint_public_key.to_bytes().as_slice(), + bip340_frost_key.joint_public_key.to_bytes().as_slice(), verification_shares_bytes.concat().as_slice(), b"frost-end-to-end-test-1".as_slice(), b"0".as_slice(), @@ -1038,7 +1096,7 @@ mod test { .concat(); let sid2 = [ - frost_key.joint_public_key.to_bytes().as_slice(), + bip340_frost_key.joint_public_key.to_bytes().as_slice(), verification_shares_bytes.concat().as_slice(), b"frost-end-to-end-test-2".as_slice(), b"2".as_slice(), @@ -1050,25 +1108,31 @@ mod test { let nonces = vec![(0, nonce1.public()), (2, nonce3.public())]; let nonces2 = vec![(0, nonce1.public()), (2, nonce3.public())]; - let session = frost.start_sign_session(&frost_key, nonces, Message::plain("test", b"test")); + let session = + frost.start_sign_session(&bip340_frost_key, nonces, Message::plain("test", b"test")); dbg!(&session); { - let session2 = frost.start_sign_session(&jk2, nonces2, Message::plain("test", b"test")); + let session2 = frost.start_sign_session( + &bip340_frost_key2, + nonces2, + Message::plain("test", b"test"), + ); assert_eq!(session2, session); } - let sig1 = frost.sign(&frost_key, &session, 0, &secret_share1, nonce1); - let sig3 = frost.sign(&jk3, &session, 2, &secret_share3, nonce3); + let sig1 = frost.sign(&bip340_frost_key, &session, 0, &secret_share1, nonce1); + let sig3 = frost.sign(&bip340_frost_key3, &session, 2, &secret_share3, nonce3); dbg!(sig1, sig3); - assert!(frost.verify_signature_share(&frost_key, &session, 0, sig1)); - assert!(frost.verify_signature_share(&frost_key, &session, 2, sig3)); - let combined_sig = frost.combine_signature_shares(&frost_key, &session, vec![sig1, sig3]); + assert!(frost.verify_signature_share(&bip340_frost_key, &session, 0, sig1)); + assert!(frost.verify_signature_share(&bip340_frost_key, &session, 2, sig3)); + let combined_sig = + frost.combine_signature_shares(&bip340_frost_key, &session, vec![sig1, sig3]); assert!(frost.schnorr.verify( - &frost_key.joint_public_key, + &bip340_frost_key.joint_public_key, Message::::plain("test", b"test"), &combined_sig ));