From e01a5240ee3436b3d7441d90c7a2a63a41e357f1 Mon Sep 17 00:00:00 2001 From: nickfarrow Date: Fri, 18 Mar 2022 14:02:48 +1100 Subject: [PATCH 01/38] Implement FROST DKG Signed-off-by: nickfarrow --- schnorr_fun/src/frost.rs | 243 +++++++++++++++++++++++++++++++++++++++ schnorr_fun/src/lib.rs | 3 + 2 files changed, 246 insertions(+) create mode 100644 schnorr_fun/src/frost.rs diff --git a/schnorr_fun/src/frost.rs b/schnorr_fun/src/frost.rs new file mode 100644 index 00000000..b834c1aa --- /dev/null +++ b/schnorr_fun/src/frost.rs @@ -0,0 +1,243 @@ +#![allow(missing_docs, unused)] +use core::{char::from_u32_unchecked, iter}; + +use crate::{KeyPair, Message, Schnorr, Signature, Vec}; +use rand_core::{CryptoRng, RngCore}; +use secp256kfun::{ + digest::{generic_array::typenum::U32, Digest}, + g, + marker::*, + nonce::NonceGen, + rand_core, s, Point, Scalar, XOnly, G, +}; + +pub struct Dkg { + pub schnorr: S, +} + +#[derive(Clone, Debug)] +pub struct LocalPoly(Vec); + +#[derive(Clone, Debug)] +pub struct DkgMessage1 { + public_poly: PublicPoly, +} + +#[derive(Clone, Debug)] +pub struct DkgState1 { + local_poly: LocalPoly, +} + +impl LocalPoly { + pub fn eval(&self, x: u32) -> Scalar { + let x = Scalar::from(x) + .expect_nonzero("must be non-zero") + .mark::(); + let mut xpow = s!(1).mark::(); + self.0 + .iter() + .skip(1) + .fold(self.0[0].clone().mark::(), move |sum, coeff| { + xpow = s!(xpow * x).mark::(); + s!(sum + xpow * coeff) + }) + } + + fn to_public_poly(&self) -> PublicPoly { + PublicPoly(self.0.iter().map(|a| g!(a * G).normalize()).collect()) + } + + pub fn random(n_coefficients: usize, rng: &mut (impl RngCore + CryptoRng)) -> Self { + LocalPoly((0..n_coefficients).map(|_| Scalar::random(rng)).collect()) + } +} + +#[derive(Clone, Debug)] +pub struct PublicPoly(Vec>); + +impl PublicPoly { + pub fn eval(&self, x: u32) -> Point { + let x = Scalar::from(x) + .expect_nonzero("must be non-zero") + .mark::(); + let xpows = iter::successors(Some(s!(1).mark::()), |xpow| { + Some(s!(x * xpow).mark::()) + }) + .take(self.0.len()) + .collect::>(); + crate::fun::op::lincomb(&xpows, &self.0) + } + + fn combine(mut polys: impl Iterator) -> PublicPoly { + let mut combined_poly = polys + .next() + .expect("cannot combine empty list of polys") + .0 + .into_iter() + .map(|p| p.mark::<(Jacobian, Zero)>()) + .collect::>(); + for poly in polys { + for (combined_point, point) in combined_poly.iter_mut().zip(poly.0) { + *combined_point = g!({ *combined_point } + point); + } + } + PublicPoly(combined_poly.into_iter().map(|p| p.normalize()).collect()) + } +} + +#[derive(Clone, Debug)] +pub struct DkgMessage2 { + secret_share: Scalar, + proof_of_possession: Signature, +} + +#[derive(Clone, Debug)] +pub struct DkgState2 { + public_polys: Vec, + index: usize, + local_share: Scalar, + joint_key: Point, +} + +#[derive(Debug, Clone)] +pub enum FirstRoundError { + TooFewParticipants, + ZeroJointKey, +} + +#[derive(Debug, Clone)] +pub enum SecondRoundError { + InvalidPoP(usize), + InvalidShare(usize), + TooFewShares, +} + +#[derive(Clone, Debug)] +pub struct JointKey { + joint_public_key: Point, + verification_shares: Vec>, +} + +impl + Clone, NG: NonceGen> Dkg> { + pub fn start_first_round(&self, local_poly: LocalPoly) -> (DkgMessage1, DkgState1) { + let public_poly = local_poly.to_public_poly(); + (DkgMessage1 { public_poly }, DkgState1 { local_poly }) + } + + pub fn start_second_round( + &self, + mut state: DkgState1, + recieved: Vec, + index: usize, + ) -> Result<(Vec, DkgState2), FirstRoundError> { + let n_parties = recieved.len() + 1; + // TODO decide what happens if user uses own DkgMessage1 as recieved + if n_parties < state.local_poly.0.len() { + return Err(FirstRoundError::TooFewParticipants); + } + + let mut joint_key = g!({ &state.local_poly.0[0] } * G).mark::(); + for message in recieved.iter() { + joint_key = g!(joint_key + { message.public_poly.0[0] }); + } + let (joint_key, needs_negation) = joint_key + .normalize() + .mark::() + .ok_or(FirstRoundError::ZeroJointKey)? + .into_point_with_even_y(); + state.local_poly.0[0].conditional_negate(needs_negation); + + // TODO sign all commitments + let keypair = self.schnorr.new_keypair(state.local_poly.0[0].clone()); + let proof_of_possession = self.schnorr.sign(&keypair, Message::::raw(b"")); + + let mut secret_shares = (0..n_parties) + .map(|i| DkgMessage2 { + secret_share: state.local_poly.eval(i as u32), + proof_of_possession: proof_of_possession.clone(), + }) + .collect::>(); + + let local_share = secret_shares.remove(index).secret_share; + + let public_polys = recieved + .into_iter() + .map(|message| message.public_poly) + .collect(); + + Ok((secret_shares, DkgState2 { + public_polys, + index, + local_share, + joint_key, + })) + } + + pub fn finish_second_round( + &self, + state: DkgState2, + messages: Vec, + ) -> Result<(JointKey, Scalar), SecondRoundError> { + let n_parties = state.public_polys.len() + 1; + assert_eq!(state.public_polys.len(), messages.len()); + + let mut total_secret_share = state.local_share; + for (i, message) in messages.iter().enumerate() { + if g!(message.secret_share * G) != state.public_polys[i].eval(state.index as u32 + 1) { + return Err(SecondRoundError::InvalidShare(i)); + } + total_secret_share = s!(total_secret_share + message.secret_share); + } + + for (i, message) in messages.iter().enumerate() { + let (verification_key, _) = state.public_polys[i].0[0].into_point_with_even_y(); + if !self.schnorr.verify( + &verification_key, + Message::::raw(b""), + &message.proof_of_possession, + ) { + return Err(SecondRoundError::InvalidPoP(i)); + } + } + + let joint_poly = PublicPoly::combine(state.public_polys.into_iter()); + let other_party_indexes = (1..n_parties).map(|i| if i >= state.index { i + 1 } else { i }); + let verification_shares = other_party_indexes + .map(|i| joint_poly.eval(i as u32).normalize()) + .collect(); + + let joint_key = JointKey { + joint_public_key: state.joint_key, + verification_shares, + }; + + Ok((joint_key, total_secret_share)) + } +} + +struct Sign { + schnorr: S, + nonce_coeff_hash: H, +} + +impl Sign { + fn sign(joint_key: &JointKey, message: Message, secret_share: Scalar) {} +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_dkg() { + let n_coefficients = 3; + let p1 = LocalPoly::random(n_coefficients, &mut rand::thread_rng()); + let p2 = LocalPoly::random(n_coefficients, &mut rand::thread_rng()); + let p3 = LocalPoly::random(n_coefficients, &mut rand::thread_rng()); + + // Dkg::start_first_round() + + dbg!(p1); + panic!(); + } +} diff --git a/schnorr_fun/src/lib.rs b/schnorr_fun/src/lib.rs index 2efcba2a..03eace13 100755 --- a/schnorr_fun/src/lib.rs +++ b/schnorr_fun/src/lib.rs @@ -28,6 +28,9 @@ pub mod binonce; #[cfg(feature = "alloc")] pub mod musig; +#[cfg(feature = "alloc")] +pub mod frost; + mod signature; pub use signature::Signature; pub mod adaptor; From ecbeeae5e4364b1596465efc179237adab5cd945 Mon Sep 17 00:00:00 2001 From: LLFourn Date: Mon, 21 Mar 2022 10:36:00 +1100 Subject: [PATCH 02/38] frost progress Signed-off-by: nickfarrow --- schnorr_fun/src/frost.rs | 223 ++++++++++++++++++++------------------- 1 file changed, 116 insertions(+), 107 deletions(-) diff --git a/schnorr_fun/src/frost.rs b/schnorr_fun/src/frost.rs index b834c1aa..e67a2019 100644 --- a/schnorr_fun/src/frost.rs +++ b/schnorr_fun/src/frost.rs @@ -1,7 +1,7 @@ #![allow(missing_docs, unused)] use core::{char::from_u32_unchecked, iter}; -use crate::{KeyPair, Message, Schnorr, Signature, Vec}; +use crate::{KeyPair, Message, Schnorr, Signature, Vec, musig::{NonceKeyPair, SignSession, Nonce}}; use rand_core::{CryptoRng, RngCore}; use secp256kfun::{ digest::{generic_array::typenum::U32, Digest}, @@ -11,24 +11,15 @@ use secp256kfun::{ rand_core, s, Point, Scalar, XOnly, G, }; -pub struct Dkg { - pub schnorr: S, +pub struct Frost { + pub pop_schnorr: PS, + pub sign_schnorr: SS, } #[derive(Clone, Debug)] -pub struct LocalPoly(Vec); +pub struct ScalarPoly(Vec); -#[derive(Clone, Debug)] -pub struct DkgMessage1 { - public_poly: PublicPoly, -} - -#[derive(Clone, Debug)] -pub struct DkgState1 { - local_poly: LocalPoly, -} - -impl LocalPoly { +impl ScalarPoly { pub fn eval(&self, x: u32) -> Scalar { let x = Scalar::from(x) .expect_nonzero("must be non-zero") @@ -43,19 +34,27 @@ impl LocalPoly { }) } - fn to_public_poly(&self) -> PublicPoly { - PublicPoly(self.0.iter().map(|a| g!(a * G).normalize()).collect()) + fn to_point_poly(&self) -> PointPoly { + PointPoly(self.0.iter().map(|a| g!(a * G).normalize()).collect()) } pub fn random(n_coefficients: usize, rng: &mut (impl RngCore + CryptoRng)) -> Self { - LocalPoly((0..n_coefficients).map(|_| Scalar::random(rng)).collect()) + ScalarPoly((0..n_coefficients).map(|_| Scalar::random(rng)).collect()) + } + + pub fn poly_len(&self) -> usize { + self.0.len() + } + + pub fn first_coef(&self) -> &Scalar { + &self.0[0] } } #[derive(Clone, Debug)] -pub struct PublicPoly(Vec>); +pub struct PointPoly(Vec>); -impl PublicPoly { +impl PointPoly { pub fn eval(&self, x: u32) -> Point { let x = Scalar::from(x) .expect_nonzero("must be non-zero") @@ -68,7 +67,7 @@ impl PublicPoly { crate::fun::op::lincomb(&xpows, &self.0) } - fn combine(mut polys: impl Iterator) -> PublicPoly { + fn combine(mut polys: impl Iterator) -> PointPoly { let mut combined_poly = polys .next() .expect("cannot combine empty list of polys") @@ -81,27 +80,31 @@ impl PublicPoly { *combined_point = g!({ *combined_point } + point); } } - PublicPoly(combined_poly.into_iter().map(|p| p.normalize()).collect()) + PointPoly(combined_poly.into_iter().map(|p| p.normalize()).collect()) + } + + pub fn poly_len(&self) -> usize { + self.0.len() } } #[derive(Clone, Debug)] -pub struct DkgMessage2 { - secret_share: Scalar, +pub struct SecretShares { + shares: Vec>, proof_of_possession: Signature, } #[derive(Clone, Debug)] -pub struct DkgState2 { - public_polys: Vec, - index: usize, - local_share: Scalar, - joint_key: Point, +pub struct Dkg { + point_polys: Vec, + needs_negation: bool, + verification_shares: Vec>, } #[derive(Debug, Clone)] pub enum FirstRoundError { - TooFewParticipants, + PolyDifferentLength(usize), + NotEnoughParties, ZeroJointKey, } @@ -109,119 +112,125 @@ pub enum FirstRoundError { pub enum SecondRoundError { InvalidPoP(usize), InvalidShare(usize), - TooFewShares, } #[derive(Clone, Debug)] pub struct JointKey { joint_public_key: Point, + needs_negation: bool, verification_shares: Vec>, } -impl + Clone, NG: NonceGen> Dkg> { - pub fn start_first_round(&self, local_poly: LocalPoly) -> (DkgMessage1, DkgState1) { - let public_poly = local_poly.to_public_poly(); - (DkgMessage1 { public_poly }, DkgState1 { local_poly }) +impl + Clone, NG: NonceGen, SS> Frost, SS> { + pub fn create_shares(&self, dkg: &Dkg, mut scalar_poly: ScalarPoly) -> SecretShares { + let pop_key = scalar_poly.first_coef(); + let keypair = self.pop_schnorr.new_keypair(pop_key.clone()); + let proof_of_possession = self.pop_schnorr.sign(&keypair, Message::::raw(b"")); + scalar_poly.0[0].conditional_negate(dkg.needs_negation); + let mut shares = (1..=dkg.point_polys.len()) + .map(|i| scalar_poly.eval(i as u32)) + .collect(); + SecretShares { + shares, + proof_of_possession, + } } +} - pub fn start_second_round( - &self, - mut state: DkgState1, - recieved: Vec, - index: usize, - ) -> Result<(Vec, DkgState2), FirstRoundError> { - let n_parties = recieved.len() + 1; - // TODO decide what happens if user uses own DkgMessage1 as recieved - if n_parties < state.local_poly.0.len() { - return Err(FirstRoundError::TooFewParticipants); - } +impl + Clone, NG, SS> Frost, SS> { + pub fn collect_polys(&self, mut point_polys: Vec) -> Result { + { + let len_first_poly = point_polys[0].poly_len(); + if let Some((i, _)) = point_polys + .iter() + .enumerate() + .find(|(i, point_poly)| point_poly.poly_len() != len_first_poly) + { + return Err(FirstRoundError::PolyDifferentLength(i)); + } - let mut joint_key = g!({ &state.local_poly.0[0] } * G).mark::(); - for message in recieved.iter() { - joint_key = g!(joint_key + { message.public_poly.0[0] }); + if point_polys.len() < len_first_poly { + return Err(FirstRoundError::NotEnoughParties); + } } + + let mut joint_poly = PointPoly::combine(point_polys.clone().into_iter()); + let joint_key = joint_poly.0[0]; + let (joint_key, needs_negation) = joint_key - .normalize() .mark::() .ok_or(FirstRoundError::ZeroJointKey)? .into_point_with_even_y(); - state.local_poly.0[0].conditional_negate(needs_negation); - // TODO sign all commitments - let keypair = self.schnorr.new_keypair(state.local_poly.0[0].clone()); - let proof_of_possession = self.schnorr.sign(&keypair, Message::::raw(b"")); + joint_poly.0[0].conditional_negate(needs_negation); - let mut secret_shares = (0..n_parties) - .map(|i| DkgMessage2 { - secret_share: state.local_poly.eval(i as u32), - proof_of_possession: proof_of_possession.clone(), - }) - .collect::>(); - - let local_share = secret_shares.remove(index).secret_share; + for poly in &mut point_polys { + poly.0[0].conditional_negate(needs_negation); + } - let public_polys = recieved - .into_iter() - .map(|message| message.public_poly) + let verification_shares = (1..=point_polys.len()) + .map(|i| joint_poly.eval(i as u32).normalize()) .collect(); - Ok((secret_shares, DkgState2 { - public_polys, - index, - local_share, - joint_key, - })) + Ok(Dkg { + point_polys, + needs_negation, + verification_shares, + }) } - pub fn finish_second_round( + pub fn collect_shares( &self, - state: DkgState2, - messages: Vec, - ) -> Result<(JointKey, Scalar), SecondRoundError> { - let n_parties = state.public_polys.len() + 1; - assert_eq!(state.public_polys.len(), messages.len()); - - let mut total_secret_share = state.local_share; - for (i, message) in messages.iter().enumerate() { - if g!(message.secret_share * G) != state.public_polys[i].eval(state.index as u32 + 1) { + dkg: &Dkg, + my_index: usize, + secret_shares: Vec<(Scalar, Signature)>, + ) -> Result, SecondRoundError> { + assert_eq!(secret_shares.len(), dkg.verification_shares.len()); + let mut total_secret_share = s!(0); + for (i, ((secret_share, proof_of_possession), poly)) in + secret_shares.iter().zip(dkg.point_polys).enumerate() + { + let expected_public_share = poly.eval((my_index + 1) as u32); + if g!(secret_share * G) != expected_public_share { return Err(SecondRoundError::InvalidShare(i)); } - total_secret_share = s!(total_secret_share + message.secret_share); - } + let (verification_key, _) = poly.0[0].into_point_with_even_y(); - for (i, message) in messages.iter().enumerate() { - let (verification_key, _) = state.public_polys[i].0[0].into_point_with_even_y(); - if !self.schnorr.verify( + if !self.pop_schnorr.verify( &verification_key, Message::::raw(b""), - &message.proof_of_possession, + proof_of_possession, ) { return Err(SecondRoundError::InvalidPoP(i)); } + + total_secret_share = s!(total_secret_share + secret_share); } - let joint_poly = PublicPoly::combine(state.public_polys.into_iter()); - let other_party_indexes = (1..n_parties).map(|i| if i >= state.index { i + 1 } else { i }); - let verification_shares = other_party_indexes - .map(|i| joint_poly.eval(i as u32).normalize()) - .collect(); + Ok(total_secret_share) + } +} - let joint_key = JointKey { - joint_public_key: state.joint_key, - verification_shares, - }; - Ok((joint_key, total_secret_share)) +impl, SP, NG> for Frost> { + + pub fn start_sign_session(&self, dkg: &Dkg, nonces: Vec, message: Message) -> SignSession { + todo!() } -} -struct Sign { - schnorr: S, - nonce_coeff_hash: H, -} + pub fn sign(&self, dkg: &Dkg, secret_nonces: NonceKeyPair, session: &SignSession) -> Scalar { + todo!() + } + + #[must_use] + pub fn verify_signature_share(&self, dkg: &Dkg, session: &SignSession, index: usize, share: Scalar) -> bool { + todo!() + } -impl Sign { - fn sign(joint_key: &JointKey, message: Message, secret_share: Scalar) {} + + pub fn combine_signature_shares(&self, dkg: &Dkg, session: &SignSession, partial_sigs: Vec>) -> Signature { + todo!() + } } #[cfg(test)] @@ -231,9 +240,9 @@ mod test { #[test] fn test_dkg() { let n_coefficients = 3; - let p1 = LocalPoly::random(n_coefficients, &mut rand::thread_rng()); - let p2 = LocalPoly::random(n_coefficients, &mut rand::thread_rng()); - let p3 = LocalPoly::random(n_coefficients, &mut rand::thread_rng()); + let p1 = ScalarPoly::random(n_coefficients, &mut rand::thread_rng()); + let p2 = ScalarPoly::random(n_coefficients, &mut rand::thread_rng()); + let p3 = ScalarPoly::random(n_coefficients, &mut rand::thread_rng()); // Dkg::start_first_round() From e05f3f28c7e1a29d5f04aaa0ceedac2aa4b42b62 Mon Sep 17 00:00:00 2001 From: LLFourn Date: Mon, 21 Mar 2022 13:00:32 +1100 Subject: [PATCH 03/38] Write failing but compiling test Signed-off-by: nickfarrow --- schnorr_fun/src/frost.rs | 232 ++++++++++++++++++++++++++++----------- 1 file changed, 170 insertions(+), 62 deletions(-) diff --git a/schnorr_fun/src/frost.rs b/schnorr_fun/src/frost.rs index e67a2019..114a181b 100644 --- a/schnorr_fun/src/frost.rs +++ b/schnorr_fun/src/frost.rs @@ -1,9 +1,13 @@ #![allow(missing_docs, unused)] use core::{char::from_u32_unchecked, iter}; -use crate::{KeyPair, Message, Schnorr, Signature, Vec, musig::{NonceKeyPair, SignSession, Nonce}}; +use crate::{ + musig::{Nonce, NonceKeyPair}, + KeyPair, Message, Schnorr, Signature, Vec, +}; use rand_core::{CryptoRng, RngCore}; use secp256kfun::{ + derive_nonce, digest::{generic_array::typenum::U32, Digest}, g, marker::*, @@ -11,9 +15,9 @@ use secp256kfun::{ rand_core, s, Point, Scalar, XOnly, G, }; -pub struct Frost { - pub pop_schnorr: PS, - pub sign_schnorr: SS, +#[derive(Clone, Debug, Default)] +pub struct Frost { + pub schnorr: SS, } #[derive(Clone, Debug)] @@ -49,6 +53,10 @@ impl ScalarPoly { pub fn first_coef(&self) -> &Scalar { &self.0[0] } + + pub fn new(x: Vec) -> Self { + Self(x) + } } #[derive(Clone, Debug)] @@ -88,17 +96,11 @@ impl PointPoly { } } -#[derive(Clone, Debug)] -pub struct SecretShares { - shares: Vec>, - proof_of_possession: Signature, -} - #[derive(Clone, Debug)] pub struct Dkg { point_polys: Vec, needs_negation: bool, - verification_shares: Vec>, + joint_key: JointKey, } #[derive(Debug, Clone)] @@ -106,38 +108,35 @@ pub enum FirstRoundError { PolyDifferentLength(usize), NotEnoughParties, ZeroJointKey, + ZeroVerificationShare, } #[derive(Debug, Clone)] pub enum SecondRoundError { - InvalidPoP(usize), InvalidShare(usize), } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq)] pub struct JointKey { joint_public_key: Point, - needs_negation: bool, - verification_shares: Vec>, + verification_shares: Vec, + threshold: usize, } -impl + Clone, NG: NonceGen, SS> Frost, SS> { - pub fn create_shares(&self, dkg: &Dkg, mut scalar_poly: ScalarPoly) -> SecretShares { - let pop_key = scalar_poly.first_coef(); - let keypair = self.pop_schnorr.new_keypair(pop_key.clone()); - let proof_of_possession = self.pop_schnorr.sign(&keypair, Message::::raw(b"")); +impl Frost { + pub fn create_shares( + &self, + dkg: &Dkg, + mut scalar_poly: ScalarPoly, + ) -> Vec> { scalar_poly.0[0].conditional_negate(dkg.needs_negation); - let mut shares = (1..=dkg.point_polys.len()) + (1..=dkg.point_polys.len()) .map(|i| scalar_poly.eval(i as u32)) - .collect(); - SecretShares { - shares, - proof_of_possession, - } + .collect() } } -impl + Clone, NG, SS> Frost, SS> { +impl Frost { pub fn collect_polys(&self, mut point_polys: Vec) -> Result { { let len_first_poly = point_polys[0].poly_len(); @@ -157,7 +156,7 @@ impl + Clone, NG, SS> Frost, SS> { let mut joint_poly = PointPoly::combine(point_polys.clone().into_iter()); let joint_key = joint_poly.0[0]; - let (joint_key, needs_negation) = joint_key + let (joint_public_key, needs_negation) = joint_key .mark::() .ok_or(FirstRoundError::ZeroJointKey)? .into_point_with_even_y(); @@ -169,84 +168,193 @@ impl + Clone, NG, SS> Frost, SS> { } let verification_shares = (1..=point_polys.len()) - .map(|i| joint_poly.eval(i as u32).normalize()) - .collect(); + .map(|i| joint_poly.eval(i as u32).normalize().mark::()) + .collect::>>() + .ok_or(FirstRoundError::ZeroVerificationShare)?; Ok(Dkg { point_polys, needs_negation, - verification_shares, + joint_key: JointKey { + verification_shares, + joint_public_key, + threshold: joint_poly.poly_len(), + }, }) } pub fn collect_shares( &self, - dkg: &Dkg, + dkg: Dkg, my_index: usize, - secret_shares: Vec<(Scalar, Signature)>, - ) -> Result, SecondRoundError> { - assert_eq!(secret_shares.len(), dkg.verification_shares.len()); + secret_shares: Vec>, + ) -> Result<(Scalar, JointKey), SecondRoundError> { + assert_eq!(secret_shares.len(), dkg.joint_key.verification_shares.len()); let mut total_secret_share = s!(0); - for (i, ((secret_share, proof_of_possession), poly)) in - secret_shares.iter().zip(dkg.point_polys).enumerate() - { + for (i, (secret_share, poly)) in secret_shares.iter().zip(&dkg.point_polys).enumerate() { let expected_public_share = poly.eval((my_index + 1) as u32); if g!(secret_share * G) != expected_public_share { return Err(SecondRoundError::InvalidShare(i)); } let (verification_key, _) = poly.0[0].into_point_with_even_y(); - if !self.pop_schnorr.verify( - &verification_key, - Message::::raw(b""), - proof_of_possession, - ) { - return Err(SecondRoundError::InvalidPoP(i)); - } - total_secret_share = s!(total_secret_share + secret_share); } - Ok(total_secret_share) + let total_secret_share = total_secret_share.expect_nonzero( + "since verification shares are non-zero, corresponding secret shares cannot be zero", + ); + + Ok((total_secret_share, dkg.joint_key)) } } +#[derive(Clone, Debug, PartialEq)] +pub struct SignSession {} -impl, SP, NG> for Frost> { - - pub fn start_sign_session(&self, dkg: &Dkg, nonces: Vec, message: Message) -> SignSession { +impl + Clone, NG> Frost> { + pub fn start_sign_session( + &self, + joint_key: &JointKey, + nonces: &[(usize, Nonce)], + message: Message, + ) -> SignSession { + // make sure no duplicats in nonces somehow + assert_eq!(joint_key.threshold, nonces.len()); todo!() } - pub fn sign(&self, dkg: &Dkg, secret_nonces: NonceKeyPair, session: &SignSession) -> Scalar { + pub fn sign( + &self, + joint_key: &JointKey, + session: &SignSession, + my_index: usize, + secret_share: &Scalar, + secret_nonces: NonceKeyPair, + ) -> Scalar { todo!() } #[must_use] - pub fn verify_signature_share(&self, dkg: &Dkg, session: &SignSession, index: usize, share: Scalar) -> bool { + pub fn verify_signature_share( + &self, + joint_key: &JointKey, + session: &SignSession, + index: usize, + signature_share: Scalar, + ) -> bool { todo!() } - - pub fn combine_signature_shares(&self, dkg: &Dkg, session: &SignSession, partial_sigs: Vec>) -> Signature { + pub fn combine_signature_shares( + &self, + joint_key: &JointKey, + session: &SignSession, + partial_sigs: &[Scalar], + ) -> Signature { todo!() } } +impl + Clone, NG: NonceGen> Frost> { + pub fn gen_nonce( + &self, + joint_key: &JointKey, + my_index: usize, + secret_share: &Scalar, + sid: &[u8], + ) -> NonceKeyPair { + let r1 = derive_nonce!( + nonce_gen => self.schnorr.nonce_gen(), + secret => secret_share, + public => [ b"r1-frost", my_index.to_be_bytes(), joint_key.joint_public_key, &joint_key.verification_shares[..], sid] + ); + let r2 = derive_nonce!( + nonce_gen => self.schnorr.nonce_gen(), + secret => secret_share, + public => [ b"r2-frost", my_index.to_be_bytes(), joint_key.joint_public_key, &joint_key.verification_shares[..], sid] + ); + let R1 = g!(r1 * G).normalize(); + let R2 = g!(r2 * G).normalize(); + NonceKeyPair { + public: Nonce([R1, R2]), + secret: [r1, r2], + } + } +} + #[cfg(test)] mod test { use super::*; + use secp256kfun::{nonce::Deterministic, proptest::prelude::*}; + use sha2::Sha256; + #[test] - fn test_dkg() { - let n_coefficients = 3; - let p1 = ScalarPoly::random(n_coefficients, &mut rand::thread_rng()); - let p2 = ScalarPoly::random(n_coefficients, &mut rand::thread_rng()); - let p3 = ScalarPoly::random(n_coefficients, &mut rand::thread_rng()); + fn frost_test_end_to_end() { + let sp1 = ScalarPoly::new(vec![s!(3), s!(7)]); + let sp2 = ScalarPoly::new(vec![s!(11), s!(13)]); + let sp3 = ScalarPoly::new(vec![s!(17), s!(19)]); + let frost = Frost::>>::default(); + let point_polys = vec![ + sp1.to_point_poly(), + sp2.to_point_poly(), + sp3.to_point_poly(), + ]; + + let dkg = frost.collect_polys(point_polys).unwrap(); + let shares1 = frost.create_shares(&dkg, sp1); + let shares2 = frost.create_shares(&dkg, sp2); + let shares3 = frost.create_shares(&dkg, sp3); + + let (secret_share1, joint_key) = frost + .collect_shares( + dkg.clone(), + 0, + vec![shares1[0].clone(), shares2[0].clone(), shares3[0].clone()], + ) + .unwrap(); + let (secret_share2, jk2) = frost + .collect_shares( + dkg.clone(), + 1, + vec![shares1[1].clone(), shares2[1].clone(), shares3[1].clone()], + ) + .unwrap(); + let (secret_share3, jk3) = frost + .collect_shares( + dkg.clone(), + 2, + vec![shares1[2].clone(), shares2[2].clone(), shares3[2].clone()], + ) + .unwrap(); + + assert_eq!(joint_key, jk2); + assert_eq!(joint_key, jk3); + + let nonce1 = frost.gen_nonce(&joint_key, 0, &secret_share1, b"test"); + let nonce3 = frost.gen_nonce(&joint_key, 2, &secret_share3, b"test"); + let nonces = [(0, nonce1.public()), (2, nonce3.public())]; + + let session = + frost.start_sign_session(&joint_key, &nonces, Message::plain("test", b"test")); + + { + let session2 = frost.start_sign_session(&jk2, &nonces, Message::plain("test", b"test")); + assert_eq!(session2, session); + } + + let sig1 = frost.sign(&joint_key, &session, 0, &secret_share1, nonce1); + let sig3 = frost.sign(&joint_key, &session, 2, &secret_share3, nonce3); - // Dkg::start_first_round() + assert!(frost.verify_signature_share(&joint_key, &session, 0, sig1)); + assert!(frost.verify_signature_share(&joint_key, &session, 2, sig3)); + let combined_sig = frost.combine_signature_shares(&joint_key, &session, &[sig1, sig3]); - dbg!(p1); - panic!(); + assert!(frost.schnorr.verify( + &joint_key.joint_public_key, + Message::::plain("test", b"test"), + &combined_sig + )); } } From febbc41f05a1c7b483f2d9660d6478b375bb54c7 Mon Sep 17 00:00:00 2001 From: LLFourn Date: Wed, 23 Mar 2022 14:51:25 +1100 Subject: [PATCH 04/38] Various trait/derive additions Signed-off-by: nickfarrow --- schnorr_fun/src/frost.rs | 72 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 67 insertions(+), 5 deletions(-) diff --git a/schnorr_fun/src/frost.rs b/schnorr_fun/src/frost.rs index 114a181b..0adc15e2 100644 --- a/schnorr_fun/src/frost.rs +++ b/schnorr_fun/src/frost.rs @@ -16,11 +16,11 @@ use secp256kfun::{ }; #[derive(Clone, Debug, Default)] -pub struct Frost { +pub struct Frost { pub schnorr: SS, } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq)] pub struct ScalarPoly(Vec); impl ScalarPoly { @@ -38,7 +38,7 @@ impl ScalarPoly { }) } - fn to_point_poly(&self) -> PointPoly { + pub fn to_point_poly(&self) -> PointPoly { PointPoly(self.0.iter().map(|a| g!(a * G).normalize()).collect()) } @@ -59,8 +59,22 @@ impl ScalarPoly { } } -#[derive(Clone, Debug)] -pub struct PointPoly(Vec>); +#[derive(Clone, Debug, PartialEq)] +#[cfg_attr( + feature = "serde", + derive(serde::Deserialize, serde::Serialize), + serde(crate = "serde_crate") +)] +pub struct PointPoly( + #[cfg_attr( + feature = "serde", + serde(bound( + deserialize = "Point: serde::Deserialize<'de>", + serialize = "Point: serde::Serialize" + )) + )] + Vec>, +); impl PointPoly { pub fn eval(&self, x: u32) -> Point { @@ -94,6 +108,10 @@ impl PointPoly { pub fn poly_len(&self) -> usize { self.0.len() } + + pub fn points(&self) -> &[Point] { + &self.0 + } } #[derive(Clone, Debug)] @@ -103,6 +121,12 @@ pub struct Dkg { joint_key: JointKey, } +impl Dkg { + pub fn n_parties(&self) -> usize { + self.point_polys.len() + } +} + #[derive(Debug, Clone)] pub enum FirstRoundError { PolyDifferentLength(usize), @@ -111,18 +135,56 @@ pub enum FirstRoundError { ZeroVerificationShare, } +impl core::fmt::Display for FirstRoundError { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + use FirstRoundError::*; + match self { + PolyDifferentLength(i) => write!(f, "polynomial commitment from party at index {} was a different length", i), + NotEnoughParties => write!(f, "the number of parties was less than the threshold"), + ZeroJointKey => write!(f, "The joint key was zero. This means one of the parties was possibly malicious and you are not protecting against this properly"), + ZeroVerificationShare => write!(f, "One of the verification shares was malicious so we must abort the protocol"), + } + } +} + +#[cfg(feature = "std")] +impl std::error::Error for FirstRoundError {} + #[derive(Debug, Clone)] pub enum SecondRoundError { InvalidShare(usize), } +impl core::fmt::Display for SecondRoundError { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + use SecondRoundError::*; + match self { + InvalidShare(i) => write!(f, "the share provided by party at index {} was invalid", i), + } + } +} + +#[cfg(feature = "std")] +impl std::error::Error for SecondRoundError {} + #[derive(Clone, Debug, PartialEq)] +#[cfg_attr( + feature = "serde", + derive(serde::Deserialize, serde::Serialize), + serde(crate = "serde_crate") +)] pub struct JointKey { joint_public_key: Point, verification_shares: Vec, threshold: usize, } +impl JointKey { + pub fn public_key(&self) -> Point { + self.joint_public_key + } +} + impl Frost { pub fn create_shares( &self, From 34e2aff3c6f0731dc9cefcf465c364a5e1318001 Mon Sep 17 00:00:00 2001 From: nickfarrow Date: Tue, 22 Mar 2022 15:30:39 +1100 Subject: [PATCH 05/38] progress but still failing test Signed-off-by: nickfarrow --- schnorr_fun/src/frost.rs | 162 ++++++++++++++++++++++++++++++++------- 1 file changed, 133 insertions(+), 29 deletions(-) diff --git a/schnorr_fun/src/frost.rs b/schnorr_fun/src/frost.rs index 0adc15e2..c6d3dad7 100644 --- a/schnorr_fun/src/frost.rs +++ b/schnorr_fun/src/frost.rs @@ -1,5 +1,6 @@ -#![allow(missing_docs, unused)] -use core::{char::from_u32_unchecked, iter}; +#![allow(missing_docs)] +use core::iter; +use std::collections::HashMap; use crate::{ musig::{Nonce, NonceKeyPair}, @@ -10,14 +11,16 @@ use secp256kfun::{ derive_nonce, digest::{generic_array::typenum::U32, Digest}, g, + hash::HashAdd, marker::*, nonce::NonceGen, - rand_core, s, Point, Scalar, XOnly, G, + rand_core, s, Point, Scalar, G, }; #[derive(Clone, Debug, Default)] -pub struct Frost { +pub struct Frost { pub schnorr: SS, + pub nonce_coeff_hash: H, } #[derive(Clone, Debug, PartialEq)] @@ -179,13 +182,7 @@ pub struct JointKey { threshold: usize, } -impl JointKey { - pub fn public_key(&self) -> Point { - self.joint_public_key - } -} - -impl Frost { +impl Frost { pub fn create_shares( &self, dkg: &Dkg, @@ -198,7 +195,7 @@ impl Frost { } } -impl Frost { +impl Frost { pub fn collect_polys(&self, mut point_polys: Vec) -> Result { { let len_first_poly = point_polys[0].poly_len(); @@ -226,8 +223,10 @@ impl Frost { joint_poly.0[0].conditional_negate(needs_negation); for poly in &mut point_polys { - poly.0[0].conditional_negate(needs_negation); + poly.0[0] = poly.0[0].conditional_negate(needs_negation); } + // TODO ALSO NEGATE JOINT_POLY? + joint_poly = PointPoly::combine(point_polys.clone().into_iter()); let verification_shares = (1..=point_polys.len()) .map(|i| joint_poly.eval(i as u32).normalize().mark::()) @@ -272,29 +271,110 @@ impl Frost { } #[derive(Clone, Debug, PartialEq)] -pub struct SignSession {} +pub struct SignSession { + binding_coeff: Scalar, + agg_nonces: [Point; 2], + group_commitment: Point, + challenge: Scalar, + nonces: HashMap, +} + +impl + Clone, CH: Digest + Clone, NG> + Frost, H> +{ + fn lagrange_lambda(&self, my_index: usize, nonces: &HashMap) -> Scalar { + // Change to handle multiple my_indexes + // https://people.maths.ox.ac.uk/trefethen/barycentric.pdf + // Change to one inverse + let k = Scalar::from(my_index as u32); + nonces + .iter() + .map(|(j, _)| *j) + .filter(|j| Scalar::from(*j) != k) + .map(|j| Scalar::from(j as u32).expect_nonzero("index can not be zero")) + .fold(Scalar::one(), |acc, j| { + let denominator = s!(j - k) + .expect_nonzero("removed duplicate indexes") + .invert(); + s!(acc * j * denominator) + }) + } -impl + Clone, NG> Frost> { pub fn start_sign_session( &self, joint_key: &JointKey, nonces: &[(usize, Nonce)], message: Message, ) -> SignSession { - // make sure no duplicats in nonces somehow - assert_eq!(joint_key.threshold, nonces.len()); - todo!() + let nonce_map: HashMap<_, _> = nonces + .into_iter() + .map(|(i, nonce)| (*i as u32, *nonce)) + .collect(); + assert_eq!(nonces.len(), nonce_map.len()); + assert!(joint_key.threshold <= nonce_map.len()); + + // aggregate nonces for signers + let agg_nonces: Vec = nonce_map + .iter() + .fold([Point::zero().mark::(); 2], |acc, (_, nonce)| { + [ + g!({ acc[0] } + { nonce.0[0] }), + g!({ acc[1] } + { nonce.0[1] }), + ] + }) + .iter() + .map(|agg_nonce| { + agg_nonce + .normalize() + .mark::() + .expect("aggregate nonce should be non-zero") + }) + .collect(); + + let agg_nonce_points: [Point; 2] = [agg_nonces[0], agg_nonces[1]]; + let binding_coeff = Scalar::from_hash( + self.nonce_coeff_hash + .clone() + .add(agg_nonce_points) + .add(joint_key.joint_public_key) + .add(message), + ); + let group_commitment = + g!({ agg_nonce_points[0] } + binding_coeff * { agg_nonce_points[1] }) + .normalize() + .expect_nonzero("impossibly unlikely, input is a hash"); + let challenge = Scalar::from_hash( + self.schnorr + .challenge_hash() + .add(binding_coeff.clone()) + .add(joint_key.joint_public_key) + .add(message), + ); + + SignSession { + binding_coeff, + agg_nonces: agg_nonce_points, + group_commitment, + challenge, + nonces: nonce_map, + } } pub fn sign( &self, - joint_key: &JointKey, + // joint_key: &JointKey, session: &SignSession, my_index: usize, secret_share: &Scalar, secret_nonces: NonceKeyPair, ) -> Scalar { - todo!() + let lambda = self.lagrange_lambda(my_index, &session.nonces); + let d = &secret_nonces.secret[0]; + let e = &secret_nonces.secret[1]; + let rho = &session.binding_coeff; + let s = secret_share; + let c = &session.challenge; + s!(d + (e * rho) + lambda * s * c).mark::() } #[must_use] @@ -305,20 +385,38 @@ impl + Clone, NG> Frost> { index: usize, signature_share: Scalar, ) -> bool { - todo!() + let z = signature_share; + let lambda = self.lagrange_lambda(index, &session.nonces); + let c = &session.challenge; + let b = &session.binding_coeff; + let X = joint_key.verification_shares[index]; + let [ref R1, ref R2] = session.agg_nonces; + g!(R1 + (b * R2) + (c + lambda) * X - (z * G)).is_zero() } pub fn combine_signature_shares( &self, - joint_key: &JointKey, session: &SignSession, - partial_sigs: &[Scalar], + partial_sigs: Vec>, ) -> Signature { - todo!() + // TODO add tweak term + // Work with iterators or [], return sig or scalar + + let sum_s = partial_sigs + .into_iter() + .reduce(|acc, partial_sig| s!(acc + partial_sig).mark::()) + .unwrap_or(Scalar::zero().mark::()); + + Signature { + R: session.group_commitment.to_xonly(), + s: sum_s, + } } } -impl + Clone, NG: NonceGen> Frost> { +impl + Clone, CH: Digest + Clone, NG: NonceGen> + Frost, H> +{ pub fn gen_nonce( &self, joint_key: &JointKey, @@ -354,10 +452,12 @@ mod test { #[test] fn frost_test_end_to_end() { + // Create a secret polynomial for each participant let sp1 = ScalarPoly::new(vec![s!(3), s!(7)]); let sp2 = ScalarPoly::new(vec![s!(11), s!(13)]); let sp3 = ScalarPoly::new(vec![s!(17), s!(19)]); - let frost = Frost::>>::default(); + // + let frost = Frost::>, Sha256>::default(); let point_polys = vec![ sp1.to_point_poly(), sp2.to_point_poly(), @@ -401,17 +501,21 @@ mod test { let session = frost.start_sign_session(&joint_key, &nonces, Message::plain("test", b"test")); + dbg!(&session); { let session2 = frost.start_sign_session(&jk2, &nonces, Message::plain("test", b"test")); assert_eq!(session2, session); } - let sig1 = frost.sign(&joint_key, &session, 0, &secret_share1, nonce1); - let sig3 = frost.sign(&joint_key, &session, 2, &secret_share3, nonce3); + // let sig1 = frost.sign(&joint_key, &session, 0, &secret_share1, nonce1); + let sig1 = frost.sign(&session, 0, &secret_share1, nonce1); + let sig3 = frost.sign(&session, 2, &secret_share3, nonce3); + + dbg!(sig1, sig3); assert!(frost.verify_signature_share(&joint_key, &session, 0, sig1)); assert!(frost.verify_signature_share(&joint_key, &session, 2, sig3)); - let combined_sig = frost.combine_signature_shares(&joint_key, &session, &[sig1, sig3]); + let combined_sig = frost.combine_signature_shares(&session, vec![sig1, sig3]); assert!(frost.schnorr.verify( &joint_key.joint_public_key, From cd62a252873e29467b87aad33a9ae071faa66d3a Mon Sep 17 00:00:00 2001 From: nickfarrow Date: Wed, 23 Mar 2022 09:29:51 +1100 Subject: [PATCH 06/38] partial verification working Signed-off-by: nickfarrow --- schnorr_fun/src/frost.rs | 71 +++++++++++++++++++++++++++------------- 1 file changed, 48 insertions(+), 23 deletions(-) diff --git a/schnorr_fun/src/frost.rs b/schnorr_fun/src/frost.rs index c6d3dad7..bdb8130b 100644 --- a/schnorr_fun/src/frost.rs +++ b/schnorr_fun/src/frost.rs @@ -220,13 +220,10 @@ impl Frost { .ok_or(FirstRoundError::ZeroJointKey)? .into_point_with_even_y(); - joint_poly.0[0].conditional_negate(needs_negation); - for poly in &mut point_polys { poly.0[0] = poly.0[0].conditional_negate(needs_negation); } - // TODO ALSO NEGATE JOINT_POLY? - joint_poly = PointPoly::combine(point_polys.clone().into_iter()); + joint_poly.0[0] = joint_poly.0[0].conditional_negate(needs_negation); let verification_shares = (1..=point_polys.len()) .map(|i| joint_poly.eval(i as u32).normalize().mark::()) @@ -273,8 +270,8 @@ impl Frost { #[derive(Clone, Debug, PartialEq)] pub struct SignSession { binding_coeff: Scalar, - agg_nonces: [Point; 2], - group_commitment: Point, + nonces_need_negation: bool, + agg_nonce: Point, challenge: Scalar, nonces: HashMap, } @@ -286,10 +283,10 @@ impl + Clone, CH: Digest + Clone, // Change to handle multiple my_indexes // https://people.maths.ox.ac.uk/trefethen/barycentric.pdf // Change to one inverse - let k = Scalar::from(my_index as u32); + let k = Scalar::from(1 + my_index as u32); nonces .iter() - .map(|(j, _)| *j) + .map(|(j, _)| 1 + *j) .filter(|j| Scalar::from(*j) != k) .map(|j| Scalar::from(j as u32).expect_nonzero("index can not be zero")) .fold(Scalar::one(), |acc, j| { @@ -335,26 +332,30 @@ impl + Clone, CH: Digest + Clone, let binding_coeff = Scalar::from_hash( self.nonce_coeff_hash .clone() - .add(agg_nonce_points) + .add(agg_nonce_points[0]) + .add(agg_nonce_points[1]) .add(joint_key.joint_public_key) .add(message), ); - let group_commitment = + + let (combined_agg_nonce, nonces_need_negation) = g!({ agg_nonce_points[0] } + binding_coeff * { agg_nonce_points[1] }) .normalize() - .expect_nonzero("impossibly unlikely, input is a hash"); + .expect_nonzero("impossibly unlikely, input is a hash") + .into_point_with_even_y(); + let challenge = Scalar::from_hash( self.schnorr .challenge_hash() - .add(binding_coeff.clone()) + .add(combined_agg_nonce) .add(joint_key.joint_public_key) .add(message), ); SignSession { binding_coeff, - agg_nonces: agg_nonce_points, - group_commitment, + nonces_need_negation, + agg_nonce: combined_agg_nonce, challenge, nonces: nonce_map, } @@ -369,12 +370,27 @@ impl + Clone, CH: Digest + Clone, secret_nonces: NonceKeyPair, ) -> Scalar { let lambda = self.lagrange_lambda(my_index, &session.nonces); - let d = &secret_nonces.secret[0]; - let e = &secret_nonces.secret[1]; - let rho = &session.binding_coeff; - let s = secret_share; + let [mut r1, mut r2] = secret_nonces.secret; + r1.conditional_negate(session.nonces_need_negation); + r2.conditional_negate(session.nonces_need_negation); + + let b = &session.binding_coeff; + let x = secret_share; let c = &session.challenge; - s!(d + (e * rho) + lambda * s * c).mark::() + dbg!( + &my_index, + &lambda, + &c, + &b, + &x, + &r1, + &r2, + g!(x * G), + g!(r1 * G), + g!(r2 * G) + ); + dbg!(); + s!(r1 + (r2 * b) + lambda * x * c).mark::() } #[must_use] @@ -385,13 +401,22 @@ impl + Clone, CH: Digest + Clone, index: usize, signature_share: Scalar, ) -> bool { - let z = signature_share; + let s = signature_share; let lambda = self.lagrange_lambda(index, &session.nonces); let c = &session.challenge; let b = &session.binding_coeff; let X = joint_key.verification_shares[index]; - let [ref R1, ref R2] = session.agg_nonces; - g!(R1 + (b * R2) + (c + lambda) * X - (z * G)).is_zero() + let [ref R1, ref R2] = session + .nonces + .get(&(index as u32)) + .expect("verifying index that is not part of signing coalition") + .0; + + dbg!(&index, &s, &lambda, &c, &b, &X, &R1, &R2); + dbg!(); + dbg!(); + + g!(R1 + b * R2 + (c * lambda) * X - s * G).is_zero() } pub fn combine_signature_shares( @@ -408,7 +433,7 @@ impl + Clone, CH: Digest + Clone, .unwrap_or(Scalar::zero().mark::()); Signature { - R: session.group_commitment.to_xonly(), + R: session.agg_nonce.to_xonly(), s: sum_s, } } From f80ef5938e87fbf4c70099e25ea6634065b03d0e Mon Sep 17 00:00:00 2001 From: nickfarrow Date: Wed, 23 Mar 2022 13:55:34 +1100 Subject: [PATCH 07/38] verifyable combined signatures! Signed-off-by: nickfarrow --- schnorr_fun/src/frost.rs | 115 +++++++++++++++++++-------------------- 1 file changed, 55 insertions(+), 60 deletions(-) diff --git a/schnorr_fun/src/frost.rs b/schnorr_fun/src/frost.rs index bdb8130b..5c068909 100644 --- a/schnorr_fun/src/frost.rs +++ b/schnorr_fun/src/frost.rs @@ -4,7 +4,7 @@ use std::collections::HashMap; use crate::{ musig::{Nonce, NonceKeyPair}, - KeyPair, Message, Schnorr, Signature, Vec, + Message, Schnorr, Signature, Vec, }; use rand_core::{CryptoRng, RngCore}; use secp256kfun::{ @@ -19,8 +19,8 @@ use secp256kfun::{ #[derive(Clone, Debug, Default)] pub struct Frost { - pub schnorr: SS, - pub nonce_coeff_hash: H, + schnorr: SS, + nonce_coeff_hash: H, } #[derive(Clone, Debug, PartialEq)] @@ -202,7 +202,7 @@ impl Frost { if let Some((i, _)) = point_polys .iter() .enumerate() - .find(|(i, point_poly)| point_poly.poly_len() != len_first_poly) + .find(|(_, point_poly)| point_poly.poly_len() != len_first_poly) { return Err(FirstRoundError::PolyDifferentLength(i)); } @@ -254,8 +254,6 @@ impl Frost { if g!(secret_share * G) != expected_public_share { return Err(SecondRoundError::InvalidShare(i)); } - let (verification_key, _) = poly.0[0].into_point_with_even_y(); - total_secret_share = s!(total_secret_share + secret_share); } @@ -267,36 +265,33 @@ impl Frost { } } +pub fn lagrange_lambda(x_j: u32, x_ms: &[u32]) -> Scalar { + // Change to handle multiple my_indexes + // https://people.maths.ox.ac.uk/trefethen/barycentric.pdf + // Change to one inverse + let x_j = Scalar::from(x_j).expect_nonzero("target xcoord can not be zero"); + x_ms.iter() + .map(|x_m| Scalar::from(*x_m).expect_nonzero("index can not be zero")) + .fold(Scalar::one(), |acc, x_m| { + let denominator = s!(x_m - x_j) + .expect_nonzero("removed duplicate indexes") + .invert(); + s!(acc * x_m * denominator) + }) +} + #[derive(Clone, Debug, PartialEq)] pub struct SignSession { binding_coeff: Scalar, nonces_need_negation: bool, agg_nonce: Point, - challenge: Scalar, + challenge: Scalar, nonces: HashMap, } impl + Clone, CH: Digest + Clone, NG> Frost, H> { - fn lagrange_lambda(&self, my_index: usize, nonces: &HashMap) -> Scalar { - // Change to handle multiple my_indexes - // https://people.maths.ox.ac.uk/trefethen/barycentric.pdf - // Change to one inverse - let k = Scalar::from(1 + my_index as u32); - nonces - .iter() - .map(|(j, _)| 1 + *j) - .filter(|j| Scalar::from(*j) != k) - .map(|j| Scalar::from(j as u32).expect_nonzero("index can not be zero")) - .fold(Scalar::one(), |acc, j| { - let denominator = s!(j - k) - .expect_nonzero("removed duplicate indexes") - .invert(); - s!(acc * j * denominator) - }) - } - pub fn start_sign_session( &self, joint_key: &JointKey, @@ -310,8 +305,7 @@ impl + Clone, CH: Digest + Clone, assert_eq!(nonces.len(), nonce_map.len()); assert!(joint_key.threshold <= nonce_map.len()); - // aggregate nonces for signers - let agg_nonces: Vec = nonce_map + let agg_nonces_R1_R2: Vec = nonce_map .iter() .fold([Point::zero().mark::(); 2], |acc, (_, nonce)| { [ @@ -328,7 +322,7 @@ impl + Clone, CH: Digest + Clone, }) .collect(); - let agg_nonce_points: [Point; 2] = [agg_nonces[0], agg_nonces[1]]; + let agg_nonce_points: [Point; 2] = [agg_nonces_R1_R2[0], agg_nonces_R1_R2[1]]; let binding_coeff = Scalar::from_hash( self.nonce_coeff_hash .clone() @@ -337,25 +331,22 @@ impl + Clone, CH: Digest + Clone, .add(joint_key.joint_public_key) .add(message), ); - - let (combined_agg_nonce, nonces_need_negation) = + let (agg_nonce, nonces_need_negation) = g!({ agg_nonce_points[0] } + binding_coeff * { agg_nonce_points[1] }) .normalize() .expect_nonzero("impossibly unlikely, input is a hash") .into_point_with_even_y(); - let challenge = Scalar::from_hash( - self.schnorr - .challenge_hash() - .add(combined_agg_nonce) - .add(joint_key.joint_public_key) - .add(message), + let challenge = self.schnorr.challenge( + agg_nonce.to_xonly(), + joint_key.joint_public_key.to_xonly(), + message, ); SignSession { binding_coeff, nonces_need_negation, - agg_nonce: combined_agg_nonce, + agg_nonce, challenge, nonces: nonce_map, } @@ -369,7 +360,15 @@ impl + Clone, CH: Digest + Clone, secret_share: &Scalar, secret_nonces: NonceKeyPair, ) -> Scalar { - let lambda = self.lagrange_lambda(my_index, &session.nonces); + let lambda = lagrange_lambda( + my_index as u32 + 1, + &session + .nonces + .iter() + .filter(|(j, _)| **j != (my_index as u32)) + .map(|(j, _)| *j as u32 + 1) + .collect::>(), + ); let [mut r1, mut r2] = secret_nonces.secret; r1.conditional_negate(session.nonces_need_negation); r2.conditional_negate(session.nonces_need_negation); @@ -377,19 +376,6 @@ impl + Clone, CH: Digest + Clone, let b = &session.binding_coeff; let x = secret_share; let c = &session.challenge; - dbg!( - &my_index, - &lambda, - &c, - &b, - &x, - &r1, - &r2, - g!(x * G), - g!(r1 * G), - g!(r2 * G) - ); - dbg!(); s!(r1 + (r2 * b) + lambda * x * c).mark::() } @@ -402,7 +388,15 @@ impl + Clone, CH: Digest + Clone, signature_share: Scalar, ) -> bool { let s = signature_share; - let lambda = self.lagrange_lambda(index, &session.nonces); + let lambda = lagrange_lambda( + index as u32 + 1, + &session + .nonces + .iter() + .filter(|(j, _)| **j != (index as u32)) + .map(|(j, _)| *j as u32 + 1) + .collect::>(), + ); let c = &session.challenge; let b = &session.binding_coeff; let X = joint_key.verification_shares[index]; @@ -412,10 +406,6 @@ impl + Clone, CH: Digest + Clone, .expect("verifying index that is not part of signing coalition") .0; - dbg!(&index, &s, &lambda, &c, &b, &X, &R1, &R2); - dbg!(); - dbg!(); - g!(R1 + b * R2 + (c * lambda) * X - s * G).is_zero() } @@ -471,10 +461,16 @@ impl + Clone, CH: Digest + Clone, #[cfg(test)] mod test { use super::*; - - use secp256kfun::{nonce::Deterministic, proptest::prelude::*}; + // proptest::prelude::*}; + use secp256kfun::nonce::Deterministic; use sha2::Sha256; + #[test] + fn test_lagrange_lambda() { + let res = s!((1 * 4 * 5) * { s!((1 - 2) * (4 - 2) * (5 - 2)).expect_nonzero("").invert() }); + assert_eq!(res, lagrange_lambda(2, &[1, 4, 5])); + } + #[test] fn frost_test_end_to_end() { // Create a secret polynomial for each participant @@ -501,7 +497,7 @@ mod test { vec![shares1[0].clone(), shares2[0].clone(), shares3[0].clone()], ) .unwrap(); - let (secret_share2, jk2) = frost + let (_secret_share2, jk2) = frost .collect_shares( dkg.clone(), 1, @@ -532,7 +528,6 @@ mod test { assert_eq!(session2, session); } - // let sig1 = frost.sign(&joint_key, &session, 0, &secret_share1, nonce1); let sig1 = frost.sign(&session, 0, &secret_share1, nonce1); let sig3 = frost.sign(&session, 2, &secret_share3, nonce3); From 89f828823ba16552a1b84657dd11c2825b45a079 Mon Sep 17 00:00:00 2001 From: nickfarrow Date: Wed, 23 Mar 2022 14:21:21 +1100 Subject: [PATCH 08/38] frost tweaks Signed-off-by: nickfarrow --- schnorr_fun/src/frost.rs | 84 ++++++++++++++++++++++++++++++++++------ 1 file changed, 73 insertions(+), 11 deletions(-) diff --git a/schnorr_fun/src/frost.rs b/schnorr_fun/src/frost.rs index 5c068909..c7c0a161 100644 --- a/schnorr_fun/src/frost.rs +++ b/schnorr_fun/src/frost.rs @@ -180,6 +180,27 @@ pub struct JointKey { joint_public_key: Point, verification_shares: Vec, threshold: usize, + tweak: Scalar, + needs_negation: bool, +} + +impl JointKey { + pub fn tweak(&self, tweak: Scalar) -> Option { + let mut tweak = s!(self.tweak + tweak).mark::(); + let (joint_public_key, needs_negation) = g!(self.joint_public_key + tweak * G) + .mark::()? + .into_point_with_even_y(); + tweak.conditional_negate(needs_negation); + + // Store new join_public_key and new tweak, as well as needs_negation + Some(JointKey { + joint_public_key, + verification_shares: self.verification_shares.clone(), + threshold: self.threshold.clone(), + tweak, + needs_negation, + }) + } } impl Frost { @@ -237,6 +258,9 @@ impl Frost { verification_shares, joint_public_key, threshold: joint_poly.poly_len(), + tweak: Scalar::zero().mark::(), + // TODO WHAT SHOULD THIS NEEDS NEGATION BE? + needs_negation: false, }, }) } @@ -269,6 +293,7 @@ pub fn lagrange_lambda(x_j: u32, x_ms: &[u32]) -> Scalar { // Change to handle multiple my_indexes // https://people.maths.ox.ac.uk/trefethen/barycentric.pdf // Change to one inverse + // dbg!(nonces); let x_j = Scalar::from(x_j).expect_nonzero("target xcoord can not be zero"); x_ms.iter() .map(|x_m| Scalar::from(*x_m).expect_nonzero("index can not be zero")) @@ -276,6 +301,7 @@ pub fn lagrange_lambda(x_j: u32, x_ms: &[u32]) -> Scalar { let denominator = s!(x_m - x_j) .expect_nonzero("removed duplicate indexes") .invert(); + dbg!(&x_j, &x_m); s!(acc * x_m * denominator) }) } @@ -298,7 +324,7 @@ impl + Clone, CH: Digest + Clone, nonces: &[(usize, Nonce)], message: Message, ) -> SignSession { - let nonce_map: HashMap<_, _> = nonces + let mut nonce_map: HashMap<_, _> = nonces .into_iter() .map(|(i, nonce)| (*i as u32, *nonce)) .collect(); @@ -337,6 +363,10 @@ impl + Clone, CH: Digest + Clone, .expect_nonzero("impossibly unlikely, input is a hash") .into_point_with_even_y(); + for (_, nonce) in &mut nonce_map { + nonce.conditional_negate(nonces_need_negation); + } + let challenge = self.schnorr.challenge( agg_nonce.to_xonly(), joint_key.joint_public_key.to_xonly(), @@ -354,13 +384,13 @@ impl + Clone, CH: Digest + Clone, pub fn sign( &self, - // joint_key: &JointKey, + joint_key: &JointKey, session: &SignSession, my_index: usize, secret_share: &Scalar, secret_nonces: NonceKeyPair, ) -> Scalar { - let lambda = lagrange_lambda( + let mut lambda = lagrange_lambda( my_index as u32 + 1, &session .nonces @@ -369,6 +399,7 @@ impl + Clone, CH: Digest + Clone, .map(|(j, _)| *j as u32 + 1) .collect::>(), ); + lambda.conditional_negate(joint_key.needs_negation); let [mut r1, mut r2] = secret_nonces.secret; r1.conditional_negate(session.nonces_need_negation); r2.conditional_negate(session.nonces_need_negation); @@ -376,6 +407,16 @@ impl + Clone, CH: Digest + Clone, let b = &session.binding_coeff; let x = secret_share; let c = &session.challenge; + dbg!( + &my_index, + &c, + &b, + &lambda, + g!(x * G), + g!(r1 * G), + g!(r2 * G) + ); + dbg!(); s!(r1 + (r2 * b) + lambda * x * c).mark::() } @@ -388,7 +429,7 @@ impl + Clone, CH: Digest + Clone, signature_share: Scalar, ) -> bool { let s = signature_share; - let lambda = lagrange_lambda( + let mut lambda = lagrange_lambda( index as u32 + 1, &session .nonces @@ -397,6 +438,7 @@ impl + Clone, CH: Digest + Clone, .map(|(j, _)| *j as u32 + 1) .collect::>(), ); + lambda.conditional_negate(joint_key.needs_negation); let c = &session.challenge; let b = &session.binding_coeff; let X = joint_key.verification_shares[index]; @@ -406,14 +448,20 @@ impl + Clone, CH: Digest + Clone, .expect("verifying index that is not part of signing coalition") .0; + dbg!(&index, &s, &c, &b, &lambda, &X, &R1, &R2); + dbg!(); + dbg!(); + g!(R1 + b * R2 + (c * lambda) * X - s * G).is_zero() } pub fn combine_signature_shares( &self, + joint_key: &JointKey, session: &SignSession, partial_sigs: Vec>, ) -> Signature { + let ck = s!(session.challenge * joint_key.tweak); // TODO add tweak term // Work with iterators or [], return sig or scalar @@ -424,7 +472,7 @@ impl + Clone, CH: Digest + Clone, Signature { R: session.agg_nonce.to_xonly(), - s: sum_s, + s: s!(sum_s + ck).mark::(), } } } @@ -490,21 +538,21 @@ mod test { let shares2 = frost.create_shares(&dkg, sp2); let shares3 = frost.create_shares(&dkg, sp3); - let (secret_share1, joint_key) = frost + let (secret_share1, mut joint_key) = frost .collect_shares( dkg.clone(), 0, vec![shares1[0].clone(), shares2[0].clone(), shares3[0].clone()], ) .unwrap(); - let (_secret_share2, jk2) = frost + let (_secret_share2, mut jk2) = frost .collect_shares( dkg.clone(), 1, vec![shares1[1].clone(), shares2[1].clone(), shares3[1].clone()], ) .unwrap(); - let (secret_share3, jk3) = frost + let (secret_share3, mut jk3) = frost .collect_shares( dkg.clone(), 2, @@ -515,6 +563,20 @@ mod test { assert_eq!(joint_key, jk2); assert_eq!(joint_key, jk3); + let use_tweak = true; + if use_tweak { + 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 tweak = Scalar::zero(); + joint_key = joint_key.tweak(tweak.clone()).expect("tweak worked"); + jk2 = jk2.tweak(tweak.clone()).expect("tweak worked"); + jk3 = jk3.tweak(tweak).expect("tweak worked"); + } + let nonce1 = frost.gen_nonce(&joint_key, 0, &secret_share1, b"test"); let nonce3 = frost.gen_nonce(&joint_key, 2, &secret_share3, b"test"); let nonces = [(0, nonce1.public()), (2, nonce3.public())]; @@ -528,14 +590,14 @@ mod test { assert_eq!(session2, session); } - let sig1 = frost.sign(&session, 0, &secret_share1, nonce1); - let sig3 = frost.sign(&session, 2, &secret_share3, nonce3); + let sig1 = frost.sign(&joint_key, &session, 0, &secret_share1, nonce1); + let sig3 = frost.sign(&jk3, &session, 2, &secret_share3, nonce3); dbg!(sig1, sig3); assert!(frost.verify_signature_share(&joint_key, &session, 0, sig1)); assert!(frost.verify_signature_share(&joint_key, &session, 2, sig3)); - let combined_sig = frost.combine_signature_shares(&session, vec![sig1, sig3]); + let combined_sig = frost.combine_signature_shares(&joint_key, &session, vec![sig1, sig3]); assert!(frost.schnorr.verify( &joint_key.joint_public_key, From 209f272e5344c6b7974859c382a17219d3d4ca11 Mon Sep 17 00:00:00 2001 From: LLFourn Date: Thu, 24 Mar 2022 08:28:31 +1100 Subject: [PATCH 09/38] Make Frost nonce hash be unit type by default Signed-off-by: nickfarrow --- schnorr_fun/src/frost.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/schnorr_fun/src/frost.rs b/schnorr_fun/src/frost.rs index c7c0a161..abbec376 100644 --- a/schnorr_fun/src/frost.rs +++ b/schnorr_fun/src/frost.rs @@ -18,7 +18,7 @@ use secp256kfun::{ }; #[derive(Clone, Debug, Default)] -pub struct Frost { +pub struct Frost { schnorr: SS, nonce_coeff_hash: H, } From c33e5a79eb677db291318b2c4f1804c75b45b2da Mon Sep 17 00:00:00 2001 From: LLFourn Date: Thu, 24 Mar 2022 16:59:49 +1100 Subject: [PATCH 10/38] Allow generating nonces from dkg Signed-off-by: nickfarrow --- schnorr_fun/src/frost.rs | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/schnorr_fun/src/frost.rs b/schnorr_fun/src/frost.rs index abbec376..5e01feba 100644 --- a/schnorr_fun/src/frost.rs +++ b/schnorr_fun/src/frost.rs @@ -464,7 +464,6 @@ impl + Clone, CH: Digest + Clone, let ck = s!(session.challenge * joint_key.tweak); // TODO add tweak term // Work with iterators or [], return sig or scalar - let sum_s = partial_sigs .into_iter() .reduce(|acc, partial_sig| s!(acc + partial_sig).mark::()) @@ -477,16 +476,17 @@ impl + Clone, CH: Digest + Clone, } } -impl + Clone, CH: Digest + Clone, NG: NonceGen> +impl + Clone, NG: NonceGen> Frost, H> { pub fn gen_nonce( &self, - joint_key: &JointKey, + joint_key: &impl GetJointKey, my_index: usize, secret_share: &Scalar, sid: &[u8], ) -> NonceKeyPair { + let joint_key = joint_key.get_joint_key(); let r1 = derive_nonce!( nonce_gen => self.schnorr.nonce_gen(), secret => secret_share, @@ -506,6 +506,25 @@ impl + Clone, CH: Digest + Clone, } } + +/// Allows getting the joint key +// TODO seal this trait +pub trait GetJointKey { + fn get_joint_key(&self) -> &JointKey; +} + +impl GetJointKey for Dkg { + fn get_joint_key(&self) -> &JointKey { + &self.joint_key + } +} + +impl GetJointKey for JointKey { + fn get_joint_key(&self) -> &JointKey { + &self + } +} + #[cfg(test)] mod test { use super::*; From d89f07f92236692dfd2f601de8d2a60704b45d13 Mon Sep 17 00:00:00 2001 From: nickfarrow Date: Thu, 24 Mar 2022 01:42:16 +1100 Subject: [PATCH 11/38] first frost docs Signed-off-by: nickfarrow --- schnorr_fun/src/frost.rs | 172 +++++++++++++++++++++++++++++++-------- 1 file changed, 140 insertions(+), 32 deletions(-) diff --git a/schnorr_fun/src/frost.rs b/schnorr_fun/src/frost.rs index 5e01feba..394d839d 100644 --- a/schnorr_fun/src/frost.rs +++ b/schnorr_fun/src/frost.rs @@ -1,11 +1,21 @@ -#![allow(missing_docs)] -use core::iter; -use std::collections::HashMap; - +//! ## Description +//! +//! The FROST (Flexible Round-Optimize Schnorr Threshold) multisignature scheme lets you aggregate +//! multiple public keys into a single public key that requires some threshold t-of-n secret keys to +//! sign a signature under the aggregate key. +//! +//! This implementation has NOT yet been made compatible with other existing implementations +//! [secp256k1-zkp]: https://github.com/ElementsProject/secp256k1-zkp/pull/138 +//! +//! See MuSig in this repository, the [FROST paper] and [Security of Multi- and Threshold Signatures]. +//! +//! [FROST paper]: https://eprint.iacr.org/2020/852.pdf +//! [Security of Multi- and Threshold Signatures]: https://eprint.iacr.org/2021/1375.pdf use crate::{ musig::{Nonce, NonceKeyPair}, Message, Schnorr, Signature, Vec, }; +use core::iter; use rand_core::{CryptoRng, RngCore}; use secp256kfun::{ derive_nonce, @@ -16,17 +26,21 @@ use secp256kfun::{ nonce::NonceGen, rand_core, s, Point, Scalar, G, }; +use std::collections::HashMap; +/// The FROST context. #[derive(Clone, Debug, Default)] pub struct Frost { schnorr: SS, nonce_coeff_hash: H, } +/// A participant's secret polynomial with `t` random coefficients. #[derive(Clone, Debug, PartialEq)] pub struct ScalarPoly(Vec); impl ScalarPoly { + /// Evaluate the scalar polynomial at position x. pub fn eval(&self, x: u32) -> Scalar { let x = Scalar::from(x) .expect_nonzero("must be non-zero") @@ -41,27 +55,33 @@ impl ScalarPoly { }) } + /// Create a point polynomial through point multiplication of each coefficient. pub fn to_point_poly(&self) -> PointPoly { PointPoly(self.0.iter().map(|a| g!(a * G).normalize()).collect()) } + /// Create a random scalar polynomial pub fn random(n_coefficients: usize, rng: &mut (impl RngCore + CryptoRng)) -> Self { ScalarPoly((0..n_coefficients).map(|_| Scalar::random(rng)).collect()) } + /// The number of terms in the polynomial (t). pub fn poly_len(&self) -> usize { self.0.len() } + /// The secret coefficient for the polynomial. pub fn first_coef(&self) -> &Scalar { &self.0[0] } + /// Create a new scalar polynomial from a vector of scalars. pub fn new(x: Vec) -> Self { Self(x) } } +/// A participant's public commitment polynomial. #[derive(Clone, Debug, PartialEq)] #[cfg_attr( feature = "serde", @@ -80,6 +100,7 @@ pub struct PointPoly( ); impl PointPoly { + /// Evaluate the polynomial at position x. pub fn eval(&self, x: u32) -> Point { let x = Scalar::from(x) .expect_nonzero("must be non-zero") @@ -92,8 +113,10 @@ impl PointPoly { crate::fun::op::lincomb(&xpows, &self.0) } + /// Combine a vector of polynomials into a joint polynomial. fn combine(mut polys: impl Iterator) -> PointPoly { let mut combined_poly = polys + // TODO .next() .expect("cannot combine empty list of polys") .0 @@ -108,15 +131,22 @@ impl PointPoly { PointPoly(combined_poly.into_iter().map(|p| p.normalize()).collect()) } + /// The number of terms in the polynomial (t) pub fn poly_len(&self) -> usize { self.0.len() } + /// Fetch the point for the polynomial pub fn points(&self) -> &[Point] { &self.0 } } +/// A DKG (distributed key generation) session +/// +/// Created using [`Frost::collect_polys`] +/// +/// [`Frost::collect_polys`] #[derive(Clone, Debug)] pub struct Dkg { point_polys: Vec, @@ -125,16 +155,22 @@ pub struct Dkg { } impl Dkg { + /// Return the number of parties in the DKG pub fn n_parties(&self) -> usize { self.point_polys.len() } } +/// First round errors #[derive(Debug, Clone)] pub enum FirstRoundError { + /// Received polynomial is of differing length. PolyDifferentLength(usize), + /// Number of parties is less than the length of polynomials specifying the threshold. NotEnoughParties, + /// Joint key is zero. Should be impossible, or maliciously chosen. ZeroJointKey, + /// Verification share is zero. Should be impossible, or maliciously chosen. ZeroVerificationShare, } @@ -153,8 +189,10 @@ impl core::fmt::Display for FirstRoundError { #[cfg(feature = "std")] impl std::error::Error for FirstRoundError {} +/// Second round DKG errors #[derive(Debug, Clone)] pub enum SecondRoundError { + /// Secret share does not match the expected. Incorrect ordering? InvalidShare(usize), } @@ -170,6 +208,7 @@ impl core::fmt::Display for SecondRoundError { #[cfg(feature = "std")] impl std::error::Error for SecondRoundError {} +/// A joint FROST key #[derive(Clone, Debug, PartialEq)] #[cfg_attr( feature = "serde", @@ -185,6 +224,27 @@ pub struct JointKey { } impl JointKey { + /// The joint public key of the multisignature + /// + /// ## Return value + /// + /// A point (normalised to have an even Y coordinate). + pub fn public_key(&self) -> Point { + self.joint_public_key + } + + /// *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 jointkey with the same parties but a different aggregated public key. + /// In the unusual case that the twak is exactly equal to the negation of the aggregate + /// secret key it returns `None`. + /// // TODO ^ CHECK THIS pub fn tweak(&self, tweak: Scalar) -> Option { let mut tweak = s!(self.tweak + tweak).mark::(); let (joint_public_key, needs_negation) = g!(self.joint_public_key + tweak * G) @@ -192,7 +252,7 @@ impl JointKey { .into_point_with_even_y(); tweak.conditional_negate(needs_negation); - // Store new join_public_key and new tweak, as well as needs_negation + // Store new join_public_key and new tweak, as well as needs_negation. Some(JointKey { joint_public_key, verification_shares: self.verification_shares.clone(), @@ -201,9 +261,27 @@ impl JointKey { needs_negation, }) } + + /// The threshold number of participants required in a signing coalition to produce a valid signature. + pub fn threshold(&self) -> usize { + self.threshold + } + + /// The total number of signers in this multisignature. + pub fn n_signers(&self) -> usize { + self.verification_shares.len() + } } impl Frost { + /// Create a secret share for every other participant by evaluating our secret polynomial. + /// at their participant index. f(i) for 1<=i<= n. + /// + /// Each secret share f(i) needs to be securely communicated to participant i. + /// + /// ## Return value + /// + /// Returns a vector of secret shares, the share at index 0 is destined for participant 1. pub fn create_shares( &self, dkg: &Dkg, @@ -217,6 +295,15 @@ impl Frost { } impl Frost { + /// Collect all the public polynomials into a DKG session with a joint key. + /// + /// Takes a vector of point polynomials with your polynomial at index 0. + /// + /// Also prepares a vector of verification shares for later. + /// + /// ## Return value + /// + /// Returns a result of a Dkg pub fn collect_polys(&self, mut point_polys: Vec) -> Result { { let len_first_poly = point_polys[0].poly_len(); @@ -228,6 +315,7 @@ impl Frost { return Err(FirstRoundError::PolyDifferentLength(i)); } + // Number of parties is less than the length of polynomials specifying the threshold if point_polys.len() < len_first_poly { return Err(FirstRoundError::NotEnoughParties); } @@ -259,12 +347,22 @@ impl Frost { joint_public_key, threshold: joint_poly.poly_len(), tweak: Scalar::zero().mark::(), - // TODO WHAT SHOULD THIS NEEDS NEGATION BE? needs_negation: false, }, }) } + /// Collect the vector of all the secret shares into your total long-lived secret share. + /// The secret_shares include your own and a share from each of the other participants. + /// + /// Confirms the secret_share sent to us matches the expected + /// by evaluating their polynomial at our index and comparing. + /// + /// + /// + /// # Returns + /// + /// Your total secret share Scalar and the joint key pub fn collect_shares( &self, dkg: Dkg, @@ -289,11 +387,11 @@ impl Frost { } } +/// Calculate the lagrange coefficient for participant with index x_j and other signers indexes x_ms pub fn lagrange_lambda(x_j: u32, x_ms: &[u32]) -> Scalar { - // Change to handle multiple my_indexes - // https://people.maths.ox.ac.uk/trefethen/barycentric.pdf + // TODO // Change to one inverse - // dbg!(nonces); + // https://people.maths.ox.ac.uk/trefethen/barycentric.pdf let x_j = Scalar::from(x_j).expect_nonzero("target xcoord can not be zero"); x_ms.iter() .map(|x_m| Scalar::from(*x_m).expect_nonzero("index can not be zero")) @@ -306,6 +404,11 @@ pub fn lagrange_lambda(x_j: u32, x_ms: &[u32]) -> Scalar { }) } +/// A FROST signing session +/// +/// Created using [`Frost::start_sign_session`]. +/// +/// [`Frost::start_sign_session`] #[derive(Clone, Debug, PartialEq)] pub struct SignSession { binding_coeff: Scalar, @@ -318,6 +421,7 @@ pub struct SignSession { impl + Clone, CH: Digest + Clone, NG> Frost, H> { + /// Start a FROST signing session pub fn start_sign_session( &self, joint_key: &JointKey, @@ -382,6 +486,7 @@ impl + Clone, CH: Digest + Clone, } } + /// Generates a partial signature share under the joint key using a secret share. pub fn sign( &self, joint_key: &JointKey, @@ -407,20 +512,16 @@ impl + Clone, CH: Digest + Clone, let b = &session.binding_coeff; let x = secret_share; let c = &session.challenge; - dbg!( - &my_index, - &c, - &b, - &lambda, - g!(x * G), - g!(r1 * G), - g!(r2 * G) - ); - dbg!(); s!(r1 + (r2 * b) + lambda * x * c).mark::() } - #[must_use] + /// Verify a partial signature at `index`. + /// + /// Checked using verification shares that are stored in the joint key. + /// + /// ## Return Value + /// + /// Returns `bool, true if partial signature is valid. pub fn verify_signature_share( &self, joint_key: &JointKey, @@ -447,14 +548,17 @@ impl + Clone, CH: Digest + Clone, .get(&(index as u32)) .expect("verifying index that is not part of signing coalition") .0; - - dbg!(&index, &s, &c, &b, &lambda, &X, &R1, &R2); - dbg!(); - dbg!(); - g!(R1 + b * R2 + (c * lambda) * X - s * G).is_zero() } + /// Combine a vector of partial signatures into an aggregate signature. + /// + /// Includes tweak in combined signature. + /// + /// ## Return value + /// + /// Returns a combined schnorr [`schnorr_fun::signature::Signature`] for the message. + /// Valid against the joint public key. pub fn combine_signature_shares( &self, joint_key: &JointKey, @@ -462,13 +566,10 @@ impl + Clone, CH: Digest + Clone, partial_sigs: Vec>, ) -> Signature { let ck = s!(session.challenge * joint_key.tweak); - // TODO add tweak term - // Work with iterators or [], return sig or scalar let sum_s = partial_sigs .into_iter() .reduce(|acc, partial_sig| s!(acc + partial_sig).mark::()) .unwrap_or(Scalar::zero().mark::()); - Signature { R: session.agg_nonce.to_xonly(), s: s!(sum_s + ck).mark::(), @@ -476,9 +577,17 @@ impl + Clone, CH: Digest + Clone, } } -impl + Clone, NG: NonceGen> - Frost, H> -{ +impl + Clone, NG: NonceGen> Frost, H> { + /// Generate nonces for secret shares + /// + /// It is very important to carefully consider the implications of your choice of underlying + /// [`NonceGen`]. + /// + /// TODO REUSE FROM MUSIG? Macro? + /// + /// ## Return Value + /// + /// A NonceKeyPair comprised of secret scalars [r1, r2] and public nonces [R1, R2] pub fn gen_nonce( &self, joint_key: &impl GetJointKey, @@ -506,7 +615,6 @@ impl + Clone, NG: NonceGen> } } - /// Allows getting the joint key // TODO seal this trait pub trait GetJointKey { From 2fcded50cedf0584a363b50cb20fa878779486be Mon Sep 17 00:00:00 2001 From: nickfarrow Date: Mon, 4 Apr 2022 14:04:53 +1000 Subject: [PATCH 12/38] renaming functions and structs Signed-off-by: nickfarrow --- schnorr_fun/src/frost.rs | 177 ++++++++++++++++++++------------------- 1 file changed, 90 insertions(+), 87 deletions(-) diff --git a/schnorr_fun/src/frost.rs b/schnorr_fun/src/frost.rs index 394d839d..f8f848a1 100644 --- a/schnorr_fun/src/frost.rs +++ b/schnorr_fun/src/frost.rs @@ -142,20 +142,20 @@ impl PointPoly { } } -/// A DKG (distributed key generation) session +/// A KeyGen (distributed key generation) session /// -/// Created using [`Frost::collect_polys`] +/// Created using [`Frost::new_keygen`] /// -/// [`Frost::collect_polys`] +/// [`Frost::new_keygen`] #[derive(Clone, Debug)] -pub struct Dkg { +pub struct KeyGen { point_polys: Vec, needs_negation: bool, - joint_key: JointKey, + frost_key: FrostKey, } -impl Dkg { - /// Return the number of parties in the DKG +impl KeyGen { + /// Return the number of parties in the KeyGen pub fn n_parties(&self) -> usize { self.point_polys.len() } @@ -163,42 +163,42 @@ impl Dkg { /// First round errors #[derive(Debug, Clone)] -pub enum FirstRoundError { +pub enum NewKeyGenError { /// Received polynomial is of differing length. PolyDifferentLength(usize), /// Number of parties is less than the length of polynomials specifying the threshold. NotEnoughParties, /// Joint key is zero. Should be impossible, or maliciously chosen. - ZeroJointKey, + ZeroFrostKey, /// Verification share is zero. Should be impossible, or maliciously chosen. ZeroVerificationShare, } -impl core::fmt::Display for FirstRoundError { +impl core::fmt::Display for NewKeyGenError { fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { - use FirstRoundError::*; + use NewKeyGenError::*; match self { PolyDifferentLength(i) => write!(f, "polynomial commitment from party at index {} was a different length", i), NotEnoughParties => write!(f, "the number of parties was less than the threshold"), - ZeroJointKey => write!(f, "The joint key was zero. This means one of the parties was possibly malicious and you are not protecting against this properly"), + ZeroFrostKey => write!(f, "The joint key was zero. This means one of the parties was possibly malicious and you are not protecting against this properly"), ZeroVerificationShare => write!(f, "One of the verification shares was malicious so we must abort the protocol"), } } } #[cfg(feature = "std")] -impl std::error::Error for FirstRoundError {} +impl std::error::Error for NewKeyGenError {} -/// Second round DKG errors +/// Second round KeyGen errors #[derive(Debug, Clone)] -pub enum SecondRoundError { +pub enum FinishKeyGenError { /// Secret share does not match the expected. Incorrect ordering? InvalidShare(usize), } -impl core::fmt::Display for SecondRoundError { +impl core::fmt::Display for FinishKeyGenError { fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { - use SecondRoundError::*; + use FinishKeyGenError::*; match self { InvalidShare(i) => write!(f, "the share provided by party at index {} was invalid", i), } @@ -206,7 +206,7 @@ impl core::fmt::Display for SecondRoundError { } #[cfg(feature = "std")] -impl std::error::Error for SecondRoundError {} +impl std::error::Error for FinishKeyGenError {} /// A joint FROST key #[derive(Clone, Debug, PartialEq)] @@ -215,7 +215,7 @@ impl std::error::Error for SecondRoundError {} derive(serde::Deserialize, serde::Serialize), serde(crate = "serde_crate") )] -pub struct JointKey { +pub struct FrostKey { joint_public_key: Point, verification_shares: Vec, threshold: usize, @@ -223,7 +223,7 @@ pub struct JointKey { needs_negation: bool, } -impl JointKey { +impl FrostKey { /// The joint public key of the multisignature /// /// ## Return value @@ -241,7 +241,7 @@ impl JointKey { /// /// ## Return value /// - /// Returns a new jointkey with the same parties but a different aggregated public key. + /// Returns a new frostkey with the same parties but a different aggregated public key. /// In the unusual case that the twak is exactly equal to the negation of the aggregate /// secret key it returns `None`. /// // TODO ^ CHECK THIS @@ -253,7 +253,7 @@ impl JointKey { tweak.conditional_negate(needs_negation); // Store new join_public_key and new tweak, as well as needs_negation. - Some(JointKey { + Some(FrostKey { joint_public_key, verification_shares: self.verification_shares.clone(), threshold: self.threshold.clone(), @@ -284,18 +284,18 @@ impl Frost { /// Returns a vector of secret shares, the share at index 0 is destined for participant 1. pub fn create_shares( &self, - dkg: &Dkg, + KeyGen: &KeyGen, mut scalar_poly: ScalarPoly, ) -> Vec> { - scalar_poly.0[0].conditional_negate(dkg.needs_negation); - (1..=dkg.point_polys.len()) + scalar_poly.0[0].conditional_negate(KeyGen.needs_negation); + (1..=KeyGen.point_polys.len()) .map(|i| scalar_poly.eval(i as u32)) .collect() } } impl Frost { - /// Collect all the public polynomials into a DKG session with a joint key. + /// Collect all the public polynomials into a KeyGen session with a joint key. /// /// Takes a vector of point polynomials with your polynomial at index 0. /// @@ -303,8 +303,8 @@ impl Frost { /// /// ## Return value /// - /// Returns a result of a Dkg - pub fn collect_polys(&self, mut point_polys: Vec) -> Result { + /// Returns a result of a KeyGen + pub fn new_keygen(&self, mut point_polys: Vec) -> Result { { let len_first_poly = point_polys[0].poly_len(); if let Some((i, _)) = point_polys @@ -312,21 +312,21 @@ impl Frost { .enumerate() .find(|(_, point_poly)| point_poly.poly_len() != len_first_poly) { - return Err(FirstRoundError::PolyDifferentLength(i)); + return Err(NewKeyGenError::PolyDifferentLength(i)); } // Number of parties is less than the length of polynomials specifying the threshold if point_polys.len() < len_first_poly { - return Err(FirstRoundError::NotEnoughParties); + return Err(NewKeyGenError::NotEnoughParties); } } let mut joint_poly = PointPoly::combine(point_polys.clone().into_iter()); - let joint_key = joint_poly.0[0]; + let frost_key = joint_poly.0[0]; - let (joint_public_key, needs_negation) = joint_key + let (joint_public_key, needs_negation) = frost_key .mark::() - .ok_or(FirstRoundError::ZeroJointKey)? + .ok_or(NewKeyGenError::ZeroFrostKey)? .into_point_with_even_y(); for poly in &mut point_polys { @@ -337,12 +337,12 @@ impl Frost { let verification_shares = (1..=point_polys.len()) .map(|i| joint_poly.eval(i as u32).normalize().mark::()) .collect::>>() - .ok_or(FirstRoundError::ZeroVerificationShare)?; + .ok_or(NewKeyGenError::ZeroVerificationShare)?; - Ok(Dkg { + Ok(KeyGen { point_polys, needs_negation, - joint_key: JointKey { + frost_key: FrostKey { verification_shares, joint_public_key, threshold: joint_poly.poly_len(), @@ -363,18 +363,21 @@ impl Frost { /// # Returns /// /// Your total secret share Scalar and the joint key - pub fn collect_shares( + pub fn finish_keygen( &self, - dkg: Dkg, + KeyGen: KeyGen, my_index: usize, secret_shares: Vec>, - ) -> Result<(Scalar, JointKey), SecondRoundError> { - assert_eq!(secret_shares.len(), dkg.joint_key.verification_shares.len()); + ) -> Result<(Scalar, FrostKey), FinishKeyGenError> { + assert_eq!( + secret_shares.len(), + KeyGen.frost_key.verification_shares.len() + ); let mut total_secret_share = s!(0); - for (i, (secret_share, poly)) in secret_shares.iter().zip(&dkg.point_polys).enumerate() { + for (i, (secret_share, poly)) in secret_shares.iter().zip(&KeyGen.point_polys).enumerate() { let expected_public_share = poly.eval((my_index + 1) as u32); if g!(secret_share * G) != expected_public_share { - return Err(SecondRoundError::InvalidShare(i)); + return Err(FinishKeyGenError::InvalidShare(i)); } total_secret_share = s!(total_secret_share + secret_share); } @@ -383,7 +386,7 @@ impl Frost { "since verification shares are non-zero, corresponding secret shares cannot be zero", ); - Ok((total_secret_share, dkg.joint_key)) + Ok((total_secret_share, KeyGen.frost_key)) } } @@ -424,7 +427,7 @@ impl + Clone, CH: Digest + Clone, /// Start a FROST signing session pub fn start_sign_session( &self, - joint_key: &JointKey, + frost_key: &FrostKey, nonces: &[(usize, Nonce)], message: Message, ) -> SignSession { @@ -433,7 +436,7 @@ impl + Clone, CH: Digest + Clone, .map(|(i, nonce)| (*i as u32, *nonce)) .collect(); assert_eq!(nonces.len(), nonce_map.len()); - assert!(joint_key.threshold <= nonce_map.len()); + assert!(frost_key.threshold <= nonce_map.len()); let agg_nonces_R1_R2: Vec = nonce_map .iter() @@ -458,7 +461,7 @@ impl + Clone, CH: Digest + Clone, .clone() .add(agg_nonce_points[0]) .add(agg_nonce_points[1]) - .add(joint_key.joint_public_key) + .add(frost_key.joint_public_key) .add(message), ); let (agg_nonce, nonces_need_negation) = @@ -473,7 +476,7 @@ impl + Clone, CH: Digest + Clone, let challenge = self.schnorr.challenge( agg_nonce.to_xonly(), - joint_key.joint_public_key.to_xonly(), + frost_key.joint_public_key.to_xonly(), message, ); @@ -489,7 +492,7 @@ impl + Clone, CH: Digest + Clone, /// Generates a partial signature share under the joint key using a secret share. pub fn sign( &self, - joint_key: &JointKey, + frost_key: &FrostKey, session: &SignSession, my_index: usize, secret_share: &Scalar, @@ -504,7 +507,7 @@ impl + Clone, CH: Digest + Clone, .map(|(j, _)| *j as u32 + 1) .collect::>(), ); - lambda.conditional_negate(joint_key.needs_negation); + lambda.conditional_negate(frost_key.needs_negation); let [mut r1, mut r2] = secret_nonces.secret; r1.conditional_negate(session.nonces_need_negation); r2.conditional_negate(session.nonces_need_negation); @@ -524,7 +527,7 @@ impl + Clone, CH: Digest + Clone, /// Returns `bool, true if partial signature is valid. pub fn verify_signature_share( &self, - joint_key: &JointKey, + frost_key: &FrostKey, session: &SignSession, index: usize, signature_share: Scalar, @@ -539,10 +542,10 @@ impl + Clone, CH: Digest + Clone, .map(|(j, _)| *j as u32 + 1) .collect::>(), ); - lambda.conditional_negate(joint_key.needs_negation); + lambda.conditional_negate(frost_key.needs_negation); let c = &session.challenge; let b = &session.binding_coeff; - let X = joint_key.verification_shares[index]; + let X = frost_key.verification_shares[index]; let [ref R1, ref R2] = session .nonces .get(&(index as u32)) @@ -561,11 +564,11 @@ impl + Clone, CH: Digest + Clone, /// Valid against the joint public key. pub fn combine_signature_shares( &self, - joint_key: &JointKey, + frost_key: &FrostKey, session: &SignSession, partial_sigs: Vec>, ) -> Signature { - let ck = s!(session.challenge * joint_key.tweak); + let ck = s!(session.challenge * frost_key.tweak); let sum_s = partial_sigs .into_iter() .reduce(|acc, partial_sig| s!(acc + partial_sig).mark::()) @@ -590,21 +593,21 @@ impl + Clone, NG: NonceGen> Frost NonceKeyPair { - let joint_key = joint_key.get_joint_key(); + let frost_key = frost_key.get_frost_key(); let r1 = derive_nonce!( nonce_gen => self.schnorr.nonce_gen(), secret => secret_share, - public => [ b"r1-frost", my_index.to_be_bytes(), joint_key.joint_public_key, &joint_key.verification_shares[..], sid] + public => [ b"r1-frost", my_index.to_be_bytes(), frost_key.joint_public_key, &frost_key.verification_shares[..], sid] ); let r2 = derive_nonce!( nonce_gen => self.schnorr.nonce_gen(), secret => secret_share, - public => [ b"r2-frost", my_index.to_be_bytes(), joint_key.joint_public_key, &joint_key.verification_shares[..], sid] + public => [ b"r2-frost", my_index.to_be_bytes(), frost_key.joint_public_key, &frost_key.verification_shares[..], sid] ); let R1 = g!(r1 * G).normalize(); let R2 = g!(r2 * G).normalize(); @@ -617,18 +620,18 @@ impl + Clone, NG: NonceGen> Frost &JointKey; +pub trait GetFrostKey { + fn get_frost_key(&self) -> &FrostKey; } -impl GetJointKey for Dkg { - fn get_joint_key(&self) -> &JointKey { - &self.joint_key +impl GetFrostKey for KeyGen { + fn get_frost_key(&self) -> &FrostKey { + &self.frost_key } } -impl GetJointKey for JointKey { - fn get_joint_key(&self) -> &JointKey { +impl GetFrostKey for FrostKey { + fn get_frost_key(&self) -> &FrostKey { &self } } @@ -660,35 +663,35 @@ mod test { sp3.to_point_poly(), ]; - let dkg = frost.collect_polys(point_polys).unwrap(); - let shares1 = frost.create_shares(&dkg, sp1); - let shares2 = frost.create_shares(&dkg, sp2); - let shares3 = frost.create_shares(&dkg, sp3); + let KeyGen = frost.new_keygen(point_polys).unwrap(); + let shares1 = frost.create_shares(&KeyGen, sp1); + let shares2 = frost.create_shares(&KeyGen, sp2); + let shares3 = frost.create_shares(&KeyGen, sp3); - let (secret_share1, mut joint_key) = frost - .collect_shares( - dkg.clone(), + let (secret_share1, mut frost_key) = frost + .finish_keygen( + KeyGen.clone(), 0, vec![shares1[0].clone(), shares2[0].clone(), shares3[0].clone()], ) .unwrap(); let (_secret_share2, mut jk2) = frost - .collect_shares( - dkg.clone(), + .finish_keygen( + KeyGen.clone(), 1, vec![shares1[1].clone(), shares2[1].clone(), shares3[1].clone()], ) .unwrap(); let (secret_share3, mut jk3) = frost - .collect_shares( - dkg.clone(), + .finish_keygen( + KeyGen.clone(), 2, vec![shares1[2].clone(), shares2[2].clone(), shares3[2].clone()], ) .unwrap(); - assert_eq!(joint_key, jk2); - assert_eq!(joint_key, jk3); + assert_eq!(frost_key, jk2); + assert_eq!(frost_key, jk3); let use_tweak = true; if use_tweak { @@ -699,17 +702,17 @@ mod test { ]) .unwrap(); // let tweak = Scalar::zero(); - joint_key = joint_key.tweak(tweak.clone()).expect("tweak worked"); + 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"); } - let nonce1 = frost.gen_nonce(&joint_key, 0, &secret_share1, b"test"); - let nonce3 = frost.gen_nonce(&joint_key, 2, &secret_share3, b"test"); + let nonce1 = frost.gen_nonce(&frost_key, 0, &secret_share1, b"test"); + let nonce3 = frost.gen_nonce(&frost_key, 2, &secret_share3, b"test"); let nonces = [(0, nonce1.public()), (2, nonce3.public())]; let session = - frost.start_sign_session(&joint_key, &nonces, Message::plain("test", b"test")); + frost.start_sign_session(&frost_key, &nonces, Message::plain("test", b"test")); dbg!(&session); { @@ -717,17 +720,17 @@ mod test { assert_eq!(session2, session); } - let sig1 = frost.sign(&joint_key, &session, 0, &secret_share1, nonce1); + let sig1 = frost.sign(&frost_key, &session, 0, &secret_share1, nonce1); let sig3 = frost.sign(&jk3, &session, 2, &secret_share3, nonce3); dbg!(sig1, sig3); - assert!(frost.verify_signature_share(&joint_key, &session, 0, sig1)); - assert!(frost.verify_signature_share(&joint_key, &session, 2, sig3)); - let combined_sig = frost.combine_signature_shares(&joint_key, &session, vec![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.schnorr.verify( - &joint_key.joint_public_key, + &frost_key.joint_public_key, Message::::plain("test", b"test"), &combined_sig )); From aa7c5bf570d8075ecd9c0491a737fdbd3af11a78 Mon Sep 17 00:00:00 2001 From: nickfarrow Date: Mon, 4 Apr 2022 14:34:56 +1000 Subject: [PATCH 13/38] Use BTreeMap and vec of nonces Signed-off-by: nickfarrow --- schnorr_fun/src/frost.rs | 46 +++++++++++++++++++--------------------- 1 file changed, 22 insertions(+), 24 deletions(-) diff --git a/schnorr_fun/src/frost.rs b/schnorr_fun/src/frost.rs index f8f848a1..77fff384 100644 --- a/schnorr_fun/src/frost.rs +++ b/schnorr_fun/src/frost.rs @@ -26,7 +26,7 @@ use secp256kfun::{ nonce::NonceGen, rand_core, s, Point, Scalar, G, }; -use std::collections::HashMap; +use std::collections::BTreeMap; /// The FROST context. #[derive(Clone, Debug, Default)] @@ -61,7 +61,7 @@ impl ScalarPoly { } /// Create a random scalar polynomial - pub fn random(n_coefficients: usize, rng: &mut (impl RngCore + CryptoRng)) -> Self { + pub fn random(n_coefficients: u32, rng: &mut (impl RngCore + CryptoRng)) -> Self { ScalarPoly((0..n_coefficients).map(|_| Scalar::random(rng)).collect()) } @@ -218,7 +218,7 @@ impl std::error::Error for FinishKeyGenError {} pub struct FrostKey { joint_public_key: Point, verification_shares: Vec, - threshold: usize, + threshold: u32, tweak: Scalar, needs_negation: bool, } @@ -263,13 +263,13 @@ impl FrostKey { } /// The threshold number of participants required in a signing coalition to produce a valid signature. - pub fn threshold(&self) -> usize { + pub fn threshold(&self) -> u32 { self.threshold } /// The total number of signers in this multisignature. - pub fn n_signers(&self) -> usize { - self.verification_shares.len() + pub fn n_signers(&self) -> u32 { + self.verification_shares.len() as u32 } } @@ -345,7 +345,7 @@ impl Frost { frost_key: FrostKey { verification_shares, joint_public_key, - threshold: joint_poly.poly_len(), + threshold: joint_poly.poly_len() as u32, tweak: Scalar::zero().mark::(), needs_negation: false, }, @@ -366,7 +366,7 @@ impl Frost { pub fn finish_keygen( &self, KeyGen: KeyGen, - my_index: usize, + my_index: u32, secret_shares: Vec>, ) -> Result<(Scalar, FrostKey), FinishKeyGenError> { assert_eq!( @@ -418,7 +418,7 @@ pub struct SignSession { nonces_need_negation: bool, agg_nonce: Point, challenge: Scalar, - nonces: HashMap, + nonces: BTreeMap, } impl + Clone, CH: Digest + Clone, NG> @@ -428,15 +428,13 @@ impl + Clone, CH: Digest + Clone, pub fn start_sign_session( &self, frost_key: &FrostKey, - nonces: &[(usize, Nonce)], + nonces: Vec<(u32, Nonce)>, message: Message, ) -> SignSession { - let mut nonce_map: HashMap<_, _> = nonces - .into_iter() - .map(|(i, nonce)| (*i as u32, *nonce)) - .collect(); - assert_eq!(nonces.len(), nonce_map.len()); - assert!(frost_key.threshold <= nonce_map.len()); + let mut nonce_map: BTreeMap<_, _> = + nonces.into_iter().map(|(i, nonce)| (i, nonce)).collect(); + // assert_eq!(nonces.len(), nonce_map.len()); + assert!(frost_key.threshold <= nonce_map.len() as u32); let agg_nonces_R1_R2: Vec = nonce_map .iter() @@ -494,7 +492,7 @@ impl + Clone, CH: Digest + Clone, &self, frost_key: &FrostKey, session: &SignSession, - my_index: usize, + my_index: u32, secret_share: &Scalar, secret_nonces: NonceKeyPair, ) -> Scalar { @@ -529,7 +527,7 @@ impl + Clone, CH: Digest + Clone, &self, frost_key: &FrostKey, session: &SignSession, - index: usize, + index: u32, signature_share: Scalar, ) -> bool { let s = signature_share; @@ -545,7 +543,7 @@ impl + Clone, CH: Digest + Clone, lambda.conditional_negate(frost_key.needs_negation); let c = &session.challenge; let b = &session.binding_coeff; - let X = frost_key.verification_shares[index]; + let X = frost_key.verification_shares[index as usize]; let [ref R1, ref R2] = session .nonces .get(&(index as u32)) @@ -594,7 +592,7 @@ impl + Clone, NG: NonceGen> Frost NonceKeyPair { @@ -709,14 +707,14 @@ mod test { let nonce1 = frost.gen_nonce(&frost_key, 0, &secret_share1, b"test"); let nonce3 = frost.gen_nonce(&frost_key, 2, &secret_share3, b"test"); - let nonces = [(0, nonce1.public()), (2, nonce3.public())]; + 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(&frost_key, nonces, Message::plain("test", b"test")); dbg!(&session); { - let session2 = frost.start_sign_session(&jk2, &nonces, Message::plain("test", b"test")); + let session2 = frost.start_sign_session(&jk2, nonces2, Message::plain("test", b"test")); assert_eq!(session2, session); } From 7b2b2786907ad2d2dae95b13f6d585338c52a0ff Mon Sep 17 00:00:00 2001 From: nickfarrow Date: Mon, 4 Apr 2022 18:04:37 +1000 Subject: [PATCH 14/38] switch to internal schnorr hash for pop Signed-off-by: nickfarrow --- schnorr_fun/src/frost.rs | 40 +++++++++++++++++++++++++------------- schnorr_fun/src/schnorr.rs | 2 +- 2 files changed, 27 insertions(+), 15 deletions(-) diff --git a/schnorr_fun/src/frost.rs b/schnorr_fun/src/frost.rs index 77fff384..ffa647c5 100644 --- a/schnorr_fun/src/frost.rs +++ b/schnorr_fun/src/frost.rs @@ -23,16 +23,27 @@ use secp256kfun::{ g, hash::HashAdd, marker::*, - nonce::NonceGen, + nonce::{AddTag, NonceGen}, rand_core, s, Point, Scalar, G, }; use std::collections::BTreeMap; /// The FROST context. -#[derive(Clone, Debug, Default)] -pub struct Frost { - schnorr: SS, - nonce_coeff_hash: H, +// replacing nonce_coeff_hash with dkg_id_hash H +#[derive(Clone)] +pub struct Frost { + schnorr: Schnorr, + keygen_id_hash: H, +} + +impl Frost { + /// Generate a new Frost context from a Schnorr context. + pub fn new(schnorr: Schnorr) -> Self { + Self { + schnorr: schnorr.clone(), + keygen_id_hash: schnorr.challenge_hash, + } + } } /// A participant's secret polynomial with `t` random coefficients. @@ -273,7 +284,7 @@ impl FrostKey { } } -impl Frost { +impl Frost { /// Create a secret share for every other participant by evaluating our secret polynomial. /// at their participant index. f(i) for 1<=i<= n. /// @@ -294,7 +305,7 @@ impl Frost { } } -impl Frost { +impl Frost { /// Collect all the public polynomials into a KeyGen session with a joint key. /// /// Takes a vector of point polynomials with your polynomial at index 0. @@ -421,9 +432,7 @@ pub struct SignSession { nonces: BTreeMap, } -impl + Clone, CH: Digest + Clone, NG> - Frost, H> -{ +impl + Clone, NG: NonceGen + AddTag> Frost { /// Start a FROST signing session pub fn start_sign_session( &self, @@ -455,7 +464,8 @@ impl + Clone, CH: Digest + Clone, let agg_nonce_points: [Point; 2] = [agg_nonces_R1_R2[0], agg_nonces_R1_R2[1]]; let binding_coeff = Scalar::from_hash( - self.nonce_coeff_hash + self.schnorr + .challenge_hash() .clone() .add(agg_nonce_points[0]) .add(agg_nonce_points[1]) @@ -578,7 +588,7 @@ impl + Clone, CH: Digest + Clone, } } -impl + Clone, NG: NonceGen> Frost, H> { +impl + Clone, NG: NonceGen + AddTag> Frost { /// Generate nonces for secret shares /// /// It is very important to carefully consider the implications of your choice of underlying @@ -653,8 +663,10 @@ mod test { let sp1 = ScalarPoly::new(vec![s!(3), s!(7)]); let sp2 = ScalarPoly::new(vec![s!(11), s!(13)]); let sp3 = ScalarPoly::new(vec![s!(17), s!(19)]); - // - let frost = Frost::>, Sha256>::default(); + + let frost = Frost::new(Schnorr::>::new( + Deterministic::::default(), + )); let point_polys = vec![ sp1.to_point_poly(), sp2.to_point_poly(), diff --git a/schnorr_fun/src/schnorr.rs b/schnorr_fun/src/schnorr.rs index 885e00c5..85143490 100644 --- a/schnorr_fun/src/schnorr.rs +++ b/schnorr_fun/src/schnorr.rs @@ -27,7 +27,7 @@ pub struct Schnorr { /// [`NonceGen`]: crate::nonce::NonceGen nonce_gen: NG, /// The challenge hash - challenge_hash: CH, + pub challenge_hash: CH, } impl + Tagged> Schnorr { From a324a29dc3ace662b65c34bb8578ec38005ec234 Mon Sep 17 00:00:00 2001 From: nickfarrow Date: Wed, 6 Apr 2022 10:32:44 +1000 Subject: [PATCH 15/38] New gen_nonce with sid set by application Signed-off-by: nickfarrow --- schnorr_fun/src/frost.rs | 37 +++++++++++++++++++++---------------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/schnorr_fun/src/frost.rs b/schnorr_fun/src/frost.rs index ffa647c5..41b5558a 100644 --- a/schnorr_fun/src/frost.rs +++ b/schnorr_fun/src/frost.rs @@ -314,7 +314,7 @@ impl Frost { /// /// ## Return value /// - /// Returns a result of a KeyGen + /// Returns a KeyGen pub fn new_keygen(&self, mut point_polys: Vec) -> Result { { let len_first_poly = point_polys[0].poly_len(); @@ -594,28 +594,25 @@ impl + Clone, NG: NonceGen + AddTag> Frost { /// It is very important to carefully consider the implications of your choice of underlying /// [`NonceGen`]. /// - /// TODO REUSE FROM MUSIG? Macro? + /// If you are generating nonces prior to KeyGen completion, use the static first coefficient + /// for your `secret`. Otherwise you can use your secret share of the joint key. + /// + /// The application must decide upon a unique `sid` (session id) for this frost multisignature. + /// For example, the concatenation of: my_signing_index, joint_key, verfication_shares /// /// ## Return Value /// /// A NonceKeyPair comprised of secret scalars [r1, r2] and public nonces [R1, R2] - pub fn gen_nonce( - &self, - frost_key: &impl GetFrostKey, - my_index: u32, - secret_share: &Scalar, - sid: &[u8], - ) -> NonceKeyPair { - let frost_key = frost_key.get_frost_key(); + pub fn gen_nonce(&self, secret: &Scalar, sid: &[u8]) -> NonceKeyPair { let r1 = derive_nonce!( nonce_gen => self.schnorr.nonce_gen(), - secret => secret_share, - public => [ b"r1-frost", my_index.to_be_bytes(), frost_key.joint_public_key, &frost_key.verification_shares[..], sid] + secret => secret, + public => [ b"r1-frost", sid] ); let r2 = derive_nonce!( nonce_gen => self.schnorr.nonce_gen(), - secret => secret_share, - public => [ b"r2-frost", my_index.to_be_bytes(), frost_key.joint_public_key, &frost_key.verification_shares[..], sid] + secret => secret, + public => [ b"r2-frost", sid] ); let R1 = g!(r1 * G).normalize(); let R2 = g!(r2 * G).normalize(); @@ -717,8 +714,16 @@ mod test { jk3 = jk3.tweak(tweak).expect("tweak worked"); } - let nonce1 = frost.gen_nonce(&frost_key, 0, &secret_share1, b"test"); - let nonce3 = frost.gen_nonce(&frost_key, 2, &secret_share3, b"test"); + // TODO USE PROPER SID + // public => [ b"r2-frost", my_index.to_be_bytes(), frost_key.joint_public_key, &frost_key.verification_shares[..], sid] + + let sid = frost_key.joint_public_key.to_bytes(); + // for share in frost_key.verification_shares { + // // [sid, share].concat(share.to_bytes()); + // } + + let nonce1 = frost.gen_nonce(&secret_share1, &sid); + let nonce3 = frost.gen_nonce(&secret_share3, &sid); let nonces = vec![(0, nonce1.public()), (2, nonce3.public())]; let nonces2 = vec![(0, nonce1.public()), (2, nonce3.public())]; From d883fcfa6af76fa45c73c93577271cb948c5d959 Mon Sep 17 00:00:00 2001 From: nickfarrow Date: Wed, 6 Apr 2022 11:02:48 +1000 Subject: [PATCH 16/38] calculate needs_negation later Signed-off-by: nickfarrow --- schnorr_fun/src/frost.rs | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/schnorr_fun/src/frost.rs b/schnorr_fun/src/frost.rs index 41b5558a..51663fa7 100644 --- a/schnorr_fun/src/frost.rs +++ b/schnorr_fun/src/frost.rs @@ -161,7 +161,6 @@ impl PointPoly { #[derive(Clone, Debug)] pub struct KeyGen { point_polys: Vec, - needs_negation: bool, frost_key: FrostKey, } @@ -250,18 +249,24 @@ impl FrostKey { /// /// This is how you embed a taproot commitment into a key. /// + /// Also updates whether the secret first coefficient needs negation. + /// XOR of existing key needs_negation and new tweaked key needs_negation. + /// If both need negation, they will cancel out. + /// /// ## Return value /// /// Returns a new frostkey with the same parties but a different aggregated public key. /// In the unusual case that the twak is exactly equal to the negation of the aggregate /// secret key it returns `None`. /// // TODO ^ CHECK THIS - pub fn tweak(&self, tweak: Scalar) -> Option { + pub fn tweak(&mut self, tweak: Scalar) -> Option { let mut tweak = s!(self.tweak + tweak).mark::(); - let (joint_public_key, needs_negation) = g!(self.joint_public_key + tweak * G) + let (joint_public_key, tweak_needs_negation) = g!(self.joint_public_key + tweak * G) .mark::()? .into_point_with_even_y(); - tweak.conditional_negate(needs_negation); + tweak.conditional_negate(tweak_needs_negation); + + let joint_needs_negation = self.needs_negation ^ tweak_needs_negation; // Store new join_public_key and new tweak, as well as needs_negation. Some(FrostKey { @@ -269,7 +274,7 @@ impl FrostKey { verification_shares: self.verification_shares.clone(), threshold: self.threshold.clone(), tweak, - needs_negation, + needs_negation: joint_needs_negation, }) } @@ -296,9 +301,8 @@ impl Frost { pub fn create_shares( &self, KeyGen: &KeyGen, - mut scalar_poly: ScalarPoly, + scalar_poly: ScalarPoly, ) -> Vec> { - scalar_poly.0[0].conditional_negate(KeyGen.needs_negation); (1..=KeyGen.point_polys.len()) .map(|i| scalar_poly.eval(i as u32)) .collect() @@ -332,7 +336,7 @@ impl Frost { } } - let mut joint_poly = PointPoly::combine(point_polys.clone().into_iter()); + 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 @@ -340,10 +344,10 @@ impl Frost { .ok_or(NewKeyGenError::ZeroFrostKey)? .into_point_with_even_y(); - for poly in &mut point_polys { - poly.0[0] = poly.0[0].conditional_negate(needs_negation); - } - joint_poly.0[0] = joint_poly.0[0].conditional_negate(needs_negation); + // for poly in &mut point_polys { + // poly.0[0] = poly.0[0].conditional_negate(needs_negation); + // } + // joint_poly.0[0] = joint_poly.0[0].conditional_negate(needs_negation); let verification_shares = (1..=point_polys.len()) .map(|i| joint_poly.eval(i as u32).normalize().mark::()) @@ -352,13 +356,12 @@ impl Frost { Ok(KeyGen { point_polys, - needs_negation, frost_key: FrostKey { verification_shares, joint_public_key, threshold: joint_poly.poly_len() as u32, tweak: Scalar::zero().mark::(), - needs_negation: false, + needs_negation, }, }) } From 2758d68d930b75f6724f17655770af847ac10d82 Mon Sep 17 00:00:00 2001 From: nickfarrow Date: Wed, 6 Apr 2022 15:54:41 +1000 Subject: [PATCH 17/38] create proof of possession in keygen Signed-off-by: nickfarrow --- schnorr_fun/src/frost.rs | 41 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 38 insertions(+), 3 deletions(-) diff --git a/schnorr_fun/src/frost.rs b/schnorr_fun/src/frost.rs index 51663fa7..b49b8df7 100644 --- a/schnorr_fun/src/frost.rs +++ b/schnorr_fun/src/frost.rs @@ -161,6 +161,7 @@ impl PointPoly { #[derive(Clone, Debug)] pub struct KeyGen { point_polys: Vec, + proof_of_possession: (Point, Scalar), frost_key: FrostKey, } @@ -309,7 +310,29 @@ impl Frost { } } -impl Frost { +impl + Clone, NG: AddTag> Frost { + /// TODO + pub fn create_pop( + &self, + keygen_id: Point, + secret: &Scalar, + rng: &mut (impl RngCore + CryptoRng), + ) -> (Point, Scalar) { + let pop_r = Scalar::random(rng); + let pop_R = g!(pop_r * G).normalize(); + let pop_c = Scalar::from_hash( + self.keygen_id_hash + .clone() + .add(g!(*secret * G).normalize()) + .add(keygen_id) + .add(pop_R), + ); + let pop_z = s!(pop_c + pop_r); + (pop_R, pop_z) + } +} + +impl + Clone, NG: AddTag> Frost { /// Collect all the public polynomials into a KeyGen session with a joint key. /// /// Takes a vector of point polynomials with your polynomial at index 0. @@ -319,7 +342,12 @@ impl Frost { /// ## Return value /// /// Returns a KeyGen - pub fn new_keygen(&self, mut point_polys: Vec) -> Result { + pub fn new_keygen( + &self, + mut point_polys: Vec, + secret: &Scalar, + rng: &mut (impl RngCore + CryptoRng), + ) -> Result { { let len_first_poly = point_polys[0].poly_len(); if let Some((i, _)) = point_polys @@ -349,6 +377,10 @@ impl Frost { // } // joint_poly.0[0] = joint_poly.0[0].conditional_negate(needs_negation); + // TODO set keygen + let keygen_id = joint_public_key; + let proof_of_possession = self.create_pop(keygen_id, secret, rng); + let verification_shares = (1..=point_polys.len()) .map(|i| joint_poly.eval(i as u32).normalize().mark::()) .collect::>>() @@ -356,6 +388,7 @@ impl Frost { Ok(KeyGen { point_polys, + proof_of_possession, frost_key: FrostKey { verification_shares, joint_public_key, @@ -673,7 +706,9 @@ mod test { sp3.to_point_poly(), ]; - let KeyGen = frost.new_keygen(point_polys).unwrap(); + let KeyGen = frost + .new_keygen(point_polys, &sp1.0[0], &mut rand::thread_rng()) + .unwrap(); let shares1 = frost.create_shares(&KeyGen, sp1); let shares2 = frost.create_shares(&KeyGen, sp2); let shares3 = frost.create_shares(&KeyGen, sp3); From 6a3d6d8268c6aa3adc5911f35fe49712de422bae Mon Sep 17 00:00:00 2001 From: nickfarrow Date: Thu, 7 Apr 2022 12:11:46 +1000 Subject: [PATCH 18/38] verify proof of possessions Signed-off-by: nickfarrow --- schnorr_fun/src/frost.rs | 89 +++++++++++++++++++++++++++------------- 1 file changed, 61 insertions(+), 28 deletions(-) diff --git a/schnorr_fun/src/frost.rs b/schnorr_fun/src/frost.rs index b49b8df7..fa980676 100644 --- a/schnorr_fun/src/frost.rs +++ b/schnorr_fun/src/frost.rs @@ -161,7 +161,7 @@ impl PointPoly { #[derive(Clone, Debug)] pub struct KeyGen { point_polys: Vec, - proof_of_possession: (Point, Scalar), + keygen_id: Point, frost_key: FrostKey, } @@ -205,6 +205,8 @@ impl std::error::Error for NewKeyGenError {} pub enum FinishKeyGenError { /// Secret share does not match the expected. Incorrect ordering? InvalidShare(usize), + /// Proof of possession does not match the expected. Incorrect ordering? + InvalidProofOfPossession(usize), } impl core::fmt::Display for FinishKeyGenError { @@ -212,6 +214,11 @@ impl core::fmt::Display for FinishKeyGenError { use FinishKeyGenError::*; match self { InvalidShare(i) => write!(f, "the share provided by party at index {} was invalid", i), + &InvalidProofOfPossession(i) => write!( + f, + "the proof of possession provided by party at index {} was invalid", + i + ), } } } @@ -290,7 +297,8 @@ impl FrostKey { } } -impl Frost { +impl + Clone, NG: AddTag> Frost { + /// TODO POP /// Create a secret share for every other participant by evaluating our secret polynomial. /// at their participant index. f(i) for 1<=i<= n. /// @@ -303,32 +311,47 @@ impl Frost { &self, KeyGen: &KeyGen, scalar_poly: ScalarPoly, - ) -> Vec> { - (1..=KeyGen.point_polys.len()) + rng: &mut (impl RngCore + CryptoRng), + ) -> (Vec>, (Point, Scalar)) { + // Create proof of possession + let pop_r = Scalar::random(rng); + let pop_R = g!(pop_r * G).normalize(); + let pop_c = Scalar::from_hash( + self.keygen_id_hash + .clone() + // TODO CHECK SECRET REFERENCE + .add(g!({ scalar_poly.0[0].clone() } * G).normalize()) + .add(KeyGen.keygen_id) + .add(pop_R), + ); + let pop_z = s!(pop_c + pop_r); + + let shares = (1..=KeyGen.point_polys.len()) .map(|i| scalar_poly.eval(i as u32)) - .collect() + .collect(); + + (shares, (pop_R, pop_z)) } } impl + Clone, NG: AddTag> Frost { /// TODO - pub fn create_pop( + fn verify_pop( &self, keygen_id: Point, - secret: &Scalar, - rng: &mut (impl RngCore + CryptoRng), - ) -> (Point, Scalar) { - let pop_r = Scalar::random(rng); - let pop_R = g!(pop_r * G).normalize(); + point_poly: &PointPoly, + pop: (Point, Scalar), + ) -> bool { + let first_point = point_poly.0[0]; + let (pop_R, pop_z) = pop; let pop_c = Scalar::from_hash( self.keygen_id_hash .clone() - .add(g!(*secret * G).normalize()) + .add(first_point) .add(keygen_id) .add(pop_R), ); - let pop_z = s!(pop_c + pop_r); - (pop_R, pop_z) + !g!(pop_R + pop_c * first_point - pop_z * G).is_zero() } } @@ -342,12 +365,7 @@ impl + Clone, NG: AddTag> Frost { /// ## Return value /// /// Returns a KeyGen - pub fn new_keygen( - &self, - mut point_polys: Vec, - secret: &Scalar, - rng: &mut (impl RngCore + CryptoRng), - ) -> Result { + pub fn new_keygen(&self, mut point_polys: Vec) -> Result { { let len_first_poly = point_polys[0].poly_len(); if let Some((i, _)) = point_polys @@ -379,7 +397,6 @@ impl + Clone, NG: AddTag> Frost { // TODO set keygen let keygen_id = joint_public_key; - let proof_of_possession = self.create_pop(keygen_id, secret, rng); let verification_shares = (1..=point_polys.len()) .map(|i| joint_poly.eval(i as u32).normalize().mark::()) @@ -388,7 +405,7 @@ impl + Clone, NG: AddTag> Frost { Ok(KeyGen { point_polys, - proof_of_possession, + keygen_id, frost_key: FrostKey { verification_shares, joint_public_key, @@ -415,11 +432,24 @@ impl + Clone, NG: AddTag> Frost { KeyGen: KeyGen, my_index: u32, secret_shares: Vec>, + proofs_of_possession: Vec<(Point, Scalar)>, ) -> Result<(Scalar, FrostKey), FinishKeyGenError> { assert_eq!( secret_shares.len(), KeyGen.frost_key.verification_shares.len() ); + + for (i, (poly, pop)) in KeyGen + .point_polys + .iter() + .zip(proofs_of_possession) + .enumerate() + { + if !self.verify_pop(KeyGen.keygen_id, poly, pop) { + return Err(FinishKeyGenError::InvalidProofOfPossession(i)); + } + } + let mut total_secret_share = s!(0); for (i, (secret_share, poly)) in secret_shares.iter().zip(&KeyGen.point_polys).enumerate() { let expected_public_share = poly.eval((my_index + 1) as u32); @@ -692,6 +722,7 @@ mod test { #[test] fn frost_test_end_to_end() { + let mut rng = rand::thread_rng(); // Create a secret polynomial for each participant let sp1 = ScalarPoly::new(vec![s!(3), s!(7)]); let sp2 = ScalarPoly::new(vec![s!(11), s!(13)]); @@ -706,18 +737,18 @@ mod test { sp3.to_point_poly(), ]; - let KeyGen = frost - .new_keygen(point_polys, &sp1.0[0], &mut rand::thread_rng()) - .unwrap(); - let shares1 = frost.create_shares(&KeyGen, sp1); - let shares2 = frost.create_shares(&KeyGen, sp2); - let shares3 = frost.create_shares(&KeyGen, sp3); + let KeyGen = frost.new_keygen(point_polys).unwrap(); + let (shares1, pop1) = frost.create_shares(&KeyGen, sp1, &mut rng); + let (shares2, pop2) = frost.create_shares(&KeyGen, sp2, &mut rng); + let (shares3, pop3) = frost.create_shares(&KeyGen, sp3, &mut rng); + let proofs_of_possession = vec![pop1, pop2, pop3]; let (secret_share1, mut frost_key) = frost .finish_keygen( KeyGen.clone(), 0, vec![shares1[0].clone(), shares2[0].clone(), shares3[0].clone()], + proofs_of_possession.clone(), ) .unwrap(); let (_secret_share2, mut jk2) = frost @@ -725,6 +756,7 @@ mod test { KeyGen.clone(), 1, vec![shares1[1].clone(), shares2[1].clone(), shares3[1].clone()], + proofs_of_possession.clone(), ) .unwrap(); let (secret_share3, mut jk3) = frost @@ -732,6 +764,7 @@ mod test { KeyGen.clone(), 2, vec![shares1[2].clone(), shares2[2].clone(), shares3[2].clone()], + proofs_of_possession, ) .unwrap(); From a80349110e619db8db7f26001940a84a22643e14 Mon Sep 17 00:00:00 2001 From: nickfarrow Date: Thu, 7 Apr 2022 15:38:28 +1000 Subject: [PATCH 19/38] Add ScalarPoly::random_using_secret() for specifying first coeff Signed-off-by: nickfarrow --- schnorr_fun/src/frost.rs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/schnorr_fun/src/frost.rs b/schnorr_fun/src/frost.rs index fa980676..5bcdfb2f 100644 --- a/schnorr_fun/src/frost.rs +++ b/schnorr_fun/src/frost.rs @@ -76,6 +76,20 @@ impl ScalarPoly { ScalarPoly((0..n_coefficients).map(|_| Scalar::random(rng)).collect()) } + /// Create a scalar polynomial where the first coefficient is a specified secret and + /// the remaining coefficients are random. + pub fn random_using_secret( + n_coefficients: u32, + secret: Scalar, + rng: &mut (impl RngCore + CryptoRng), + ) -> Self { + let mut coeffs = vec![secret]; + for _ in 1..n_coefficients { + coeffs.push(Scalar::random(rng)) + } + ScalarPoly(coeffs) + } + /// The number of terms in the polynomial (t). pub fn poly_len(&self) -> usize { self.0.len() @@ -787,7 +801,6 @@ mod test { // TODO USE PROPER SID // public => [ b"r2-frost", my_index.to_be_bytes(), frost_key.joint_public_key, &frost_key.verification_shares[..], sid] - let sid = frost_key.joint_public_key.to_bytes(); // for share in frost_key.verification_shares { // // [sid, share].concat(share.to_bytes()); From 09992a768cace0e9c23e180cdcc1162dfd5ab068 Mon Sep 17 00:00:00 2001 From: nickfarrow Date: Fri, 8 Apr 2022 15:42:16 +1000 Subject: [PATCH 20/38] passing prop test Signed-off-by: nickfarrow --- schnorr_fun/src/frost.rs | 107 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 101 insertions(+), 6 deletions(-) diff --git a/schnorr_fun/src/frost.rs b/schnorr_fun/src/frost.rs index 5bcdfb2f..7eab4278 100644 --- a/schnorr_fun/src/frost.rs +++ b/schnorr_fun/src/frost.rs @@ -493,7 +493,6 @@ pub fn lagrange_lambda(x_j: u32, x_ms: &[u32]) -> Scalar { let denominator = s!(x_m - x_j) .expect_nonzero("removed duplicate indexes") .invert(); - dbg!(&x_j, &x_m); s!(acc * x_m * denominator) }) } @@ -724,14 +723,104 @@ impl GetFrostKey for FrostKey { #[cfg(test)] mod test { use super::*; + use rand::{prelude::IteratorRandom, Rng}; // proptest::prelude::*}; - use secp256kfun::nonce::Deterministic; + use secp256kfun::{ + nonce::Deterministic, + proptest::{arbitrary::any, proptest}, + }; use sha2::Sha256; - #[test] - fn test_lagrange_lambda() { - let res = s!((1 * 4 * 5) * { s!((1 - 2) * (4 - 2) * (5 - 2)).expect_nonzero("").invert() }); - assert_eq!(res, lagrange_lambda(2, &[1, 4, 5])); + proptest! { + #[test] + fn frost_prop_test(n_parties in 3u32..8, something in any::()) { + let mut rng = rand::thread_rng(); + let threshold = rng.gen_range(2..=n_parties); + let frost = Frost::new(Schnorr::>::new( + Deterministic::::default(), + )); + dbg!(threshold, n_parties); + + let scalar_polys: Vec = (0..n_parties).map(|_| ScalarPoly::random(threshold, &mut rng)).collect(); + let point_polys: Vec = scalar_polys.iter().map(|sp| sp.to_point_poly()).collect(); + + let KeyGen = frost.new_keygen(point_polys).unwrap(); + + let mut proofs_of_possession= vec![]; + let mut shares_vec = vec![]; + for sp in scalar_polys { + let (shares, pop) = frost.create_shares(&KeyGen, sp, &mut rng); + proofs_of_possession.push(pop); + shares_vec.push(shares); + } + + // let recieved_shares = signer_indexes.iter().zip(signer_indexes).map(|(i, j)| (i,j)).collect(); + let mut recieved_shares: Vec> = vec![]; + for party_index in 0..n_parties { + recieved_shares.push(vec![]); + for share_index in 0..n_parties { + recieved_shares[party_index as usize].push(shares_vec[share_index as usize][party_index as usize].clone()); + } + } + + let (secret_shares, frost_keys): (Vec, Vec) = (0..n_parties).map(|i| { + let (secret_share, frost) = frost.finish_keygen( + KeyGen.clone(), + i, + recieved_shares[i as usize].clone(), + proofs_of_possession.clone(), + ) + .unwrap(); + (secret_share, frost) + }).unzip(); + + + + + // Signing coalition with a threshold of parties + // let n_signers = if threshold == n_parties { + // threshold + // } else { + // rng.gen_range(threshold..=n_parties) + // }; + let n_signers = threshold; + let signer_indexes = (0..n_parties).choose_multiple(&mut rng, n_signers as usize); + + let sid = frost_keys[0].joint_public_key.to_bytes(); + + let nonces: Vec = signer_indexes.iter().map(|i| frost.gen_nonce(&secret_shares[*i as usize], &sid)).collect(); + + let mut recieved_nonces: Vec<_> = vec![]; + for (i, nonce) in signer_indexes.iter().zip(nonces.clone()) { + recieved_nonces.push((*i, nonce.public())); + } + + dbg!(recieved_nonces.clone()); + + // Create Frost signing session + let mut signatures = vec![]; + for i in 0..signer_indexes.len() { + let signer_index = signer_indexes[i] as usize; + let session = frost.start_sign_session(&frost_keys[signer_index], recieved_nonces.clone(), Message::plain("test", b"test")); + dbg!(nonces[i].clone()); + let sig = frost.sign(&frost_keys[signer_index], &session, signer_index as u32, &secret_shares[signer_index], nonces[i].clone()); + assert!(frost.verify_signature_share(&frost_keys[signer_index], &session, signer_index as u32, sig)); + signatures.push(sig); + } + + dbg!(signatures.clone()); + // TODO get this session from loop above + let session = frost.start_sign_session(&frost_keys[signer_indexes[0] as usize], recieved_nonces.clone(), Message::plain("test", b"test")); + let combined_sig = frost.combine_signature_shares(&frost_keys[signer_indexes[0] as usize], &session, signatures); + + assert!(frost.schnorr.verify( + &frost_keys[signer_indexes[0] as usize].joint_public_key, + Message::::plain("test", b"test"), + &combined_sig + )); + + + } } #[test] @@ -834,4 +923,10 @@ mod test { &combined_sig )); } + + #[test] + fn test_lagrange_lambda() { + let res = s!((1 * 4 * 5) * { s!((1 - 2) * (4 - 2) * (5 - 2)).expect_nonzero("").invert() }); + assert_eq!(res, lagrange_lambda(2, &[1, 4, 5])); + } } From aed5a95e2a565858d4abffe2fb6734ef2999adf4 Mon Sep 17 00:00:00 2001 From: nickfarrow Date: Sun, 10 Apr 2022 16:28:26 +1000 Subject: [PATCH 21/38] docs fixes and clean code Signed-off-by: nickfarrow --- schnorr_fun/src/frost.rs | 112 +++++++++++++++++++++------------------ 1 file changed, 59 insertions(+), 53 deletions(-) diff --git a/schnorr_fun/src/frost.rs b/schnorr_fun/src/frost.rs index 7eab4278..e5b96c93 100644 --- a/schnorr_fun/src/frost.rs +++ b/schnorr_fun/src/frost.rs @@ -29,7 +29,6 @@ use secp256kfun::{ use std::collections::BTreeMap; /// The FROST context. -// replacing nonce_coeff_hash with dkg_id_hash H #[derive(Clone)] pub struct Frost { schnorr: Schnorr, @@ -138,7 +137,7 @@ impl PointPoly { crate::fun::op::lincomb(&xpows, &self.0) } - /// Combine a vector of polynomials into a joint polynomial. + /// Combine a vector of point polynomials into a joint polynomial. fn combine(mut polys: impl Iterator) -> PointPoly { let mut combined_poly = polys // TODO @@ -193,7 +192,7 @@ pub enum NewKeyGenError { PolyDifferentLength(usize), /// Number of parties is less than the length of polynomials specifying the threshold. NotEnoughParties, - /// Joint key is zero. Should be impossible, or maliciously chosen. + /// Frost key is zero. Should be impossible, or maliciously chosen. ZeroFrostKey, /// Verification share is zero. Should be impossible, or maliciously chosen. ZeroVerificationShare, @@ -205,7 +204,7 @@ impl core::fmt::Display for NewKeyGenError { match self { PolyDifferentLength(i) => write!(f, "polynomial commitment from party at index {} was a different length", i), NotEnoughParties => write!(f, "the number of parties was less than the threshold"), - ZeroFrostKey => write!(f, "The joint key was zero. This means one of the parties was possibly malicious and you are not protecting against this properly"), + ZeroFrostKey => write!(f, "The joint FROST key was zero. This means one of the parties was possibly malicious and you are not protecting against this properly"), ZeroVerificationShare => write!(f, "One of the verification shares was malicious so we must abort the protocol"), } } @@ -256,7 +255,7 @@ pub struct FrostKey { } impl FrostKey { - /// The joint public key of the multisignature + /// The joint public key of the FROST multisignature /// /// ## Return value /// @@ -265,38 +264,38 @@ impl FrostKey { self.joint_public_key } - /// *Tweak* the aggregated key with a scalar so that the resulting key is equal to the + /// 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. /// - /// Also updates whether the secret first coefficient needs negation. - /// XOR of existing key needs_negation and new tweaked key needs_negation. + /// 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. /// /// ## Return value /// - /// Returns a new frostkey with the same parties but a different aggregated public key. - /// In the unusual case that the twak is exactly equal to the negation of the aggregate + /// Returns a new FrostKey with the same parties but a different aggregated public key. + /// In the erroneous case that the tweak is exactly equal to the negation of the aggregate /// secret key it returns `None`. /// // TODO ^ CHECK THIS pub fn tweak(&mut self, tweak: Scalar) -> Option { let mut tweak = s!(self.tweak + tweak).mark::(); - let (joint_public_key, tweak_needs_negation) = g!(self.joint_public_key + tweak * G) + let (joint_public_key, tweaked_needs_negation) = g!(self.joint_public_key + tweak * G) .mark::()? .into_point_with_even_y(); - tweak.conditional_negate(tweak_needs_negation); + tweak.conditional_negate(tweaked_needs_negation); - let joint_needs_negation = self.needs_negation ^ tweak_needs_negation; + let combined_needs_negation = self.needs_negation ^ tweaked_needs_negation; - // Store new join_public_key and new tweak, as well as needs_negation. + // Return the new FrostKey including the tweak and updated needs_negation Some(FrostKey { joint_public_key, verification_shares: self.verification_shares.clone(), threshold: self.threshold.clone(), tweak, - needs_negation: joint_needs_negation, + needs_negation: combined_needs_negation, }) } @@ -305,22 +304,26 @@ impl FrostKey { self.threshold } - /// The total number of signers in this multisignature. + /// The total number of signers in this FROST multisignature. pub fn n_signers(&self) -> u32 { self.verification_shares.len() as u32 } } impl + Clone, NG: AddTag> Frost { - /// TODO POP - /// Create a secret share for every other participant by evaluating our secret polynomial. - /// at their participant index. f(i) for 1<=i<= n. + /// Create secret shares and our proof-of-possession to be shared with other participants. + /// + /// Secret shares are created for every other participant by evaluating our secret polynomial + /// at their participant index. f(i) for 1<=i<=n. /// /// Each secret share f(i) needs to be securely communicated to participant i. /// + /// Also creates a proof of possession for the first coefficient of our secret scalar polynomial. + /// /// ## Return value /// - /// Returns a vector of secret shares, the share at index 0 is destined for participant 1. + /// Returns a vector of secret shares and a proof of possession, as a tupple. + /// The secret shares at index 0 is destined for participant 1. pub fn create_shares( &self, KeyGen: &KeyGen, @@ -333,7 +336,6 @@ impl + Clone, NG: AddTag> Frost { let pop_c = Scalar::from_hash( self.keygen_id_hash .clone() - // TODO CHECK SECRET REFERENCE .add(g!({ scalar_poly.0[0].clone() } * G).normalize()) .add(KeyGen.keygen_id) .add(pop_R), @@ -349,7 +351,11 @@ impl + Clone, NG: AddTag> Frost { } impl + Clone, NG: AddTag> Frost { - /// TODO + /// Verify a proof of possession against a participant's point polynomial + /// + /// ## Return value + /// + /// Returns `bool` true if the proof of possession matches this point poly, fn verify_pop( &self, keygen_id: Point, @@ -370,7 +376,7 @@ impl + Clone, NG: AddTag> Frost { } impl + Clone, NG: AddTag> Frost { - /// Collect all the public polynomials into a KeyGen session with a joint key. + /// Collect all the public polynomials into a KeyGen session with a FrostKey. /// /// Takes a vector of point polynomials with your polynomial at index 0. /// @@ -379,7 +385,7 @@ impl + Clone, NG: AddTag> Frost { /// ## Return value /// /// Returns a KeyGen - pub fn new_keygen(&self, mut point_polys: Vec) -> Result { + pub fn new_keygen(&self, point_polys: Vec) -> Result { { let len_first_poly = point_polys[0].poly_len(); if let Some((i, _)) = point_polys @@ -404,12 +410,7 @@ impl + Clone, NG: AddTag> Frost { .ok_or(NewKeyGenError::ZeroFrostKey)? .into_point_with_even_y(); - // for poly in &mut point_polys { - // poly.0[0] = poly.0[0].conditional_negate(needs_negation); - // } - // joint_poly.0[0] = joint_poly.0[0].conditional_negate(needs_negation); - - // TODO set keygen + // TODO set keygen id let keygen_id = joint_public_key; let verification_shares = (1..=point_polys.len()) @@ -430,17 +431,17 @@ impl + Clone, NG: AddTag> Frost { }) } - /// Collect the vector of all the secret shares into your total long-lived secret share. - /// The secret_shares include your own and a share from each of the other participants. - /// - /// Confirms the secret_share sent to us matches the expected - /// by evaluating their polynomial at our index and comparing. + /// Collect the vector of all recieved secret shares into your total long-lived secret share. + /// The secret_shares includes your own as well as share from each of the other participants. /// + /// The secret_shares are validated to match the expected result + /// by evaluating their polynomial at our participant index. /// + /// Each participant's proof of possession is verified against their polynomial. /// - /// # Returns + /// # Return value /// - /// Your total secret share Scalar and the joint key + /// Your total secret share Scalar and the FrostKey pub fn finish_keygen( &self, KeyGen: KeyGen, @@ -513,6 +514,10 @@ pub struct SignSession { impl + Clone, NG: NonceGen + AddTag> Frost { /// Start a FROST signing session + /// + /// ## Return value + /// + /// A FROST signing session pub fn start_sign_session( &self, frost_key: &FrostKey, @@ -576,7 +581,11 @@ impl + Clone, NG: NonceGen + AddTag> Frost { } } - /// Generates a partial signature share under the joint key using a secret share. + /// Generates a partial signature share under the FROST key using a secret share. + /// + /// ## Return value + /// + /// Returns a signature Scalar. pub fn sign( &self, frost_key: &FrostKey, @@ -607,11 +616,11 @@ impl + Clone, NG: NonceGen + AddTag> Frost { /// Verify a partial signature at `index`. /// - /// Checked using verification shares that are stored in the joint key. + /// Check partial signature against the verification shares created during keygen. /// /// ## Return Value /// - /// Returns `bool, true if partial signature is valid. + /// Returns `bool`, true if partial signature is valid. pub fn verify_signature_share( &self, frost_key: &FrostKey, @@ -674,9 +683,9 @@ impl + Clone, NG: NonceGen + AddTag> Frost { /// [`NonceGen`]. /// /// If you are generating nonces prior to KeyGen completion, use the static first coefficient - /// for your `secret`. Otherwise you can use your secret share of the joint key. + /// for your `secret`. Otherwise you can use your secret share of the joint FROST key. /// - /// The application must decide upon a unique `sid` (session id) for this frost multisignature. + /// The application must decide upon a unique `sid` (session id) for this FROST multisignature. /// For example, the concatenation of: my_signing_index, joint_key, verfication_shares /// /// ## Return Value @@ -702,9 +711,10 @@ impl + Clone, NG: NonceGen + AddTag> Frost { } } -/// Allows getting the joint key +/// Allows getting the FrostKey // TODO seal this trait pub trait GetFrostKey { + /// Get Frost key fn get_frost_key(&self) -> &FrostKey; } @@ -775,15 +785,12 @@ mod test { }).unzip(); - - - // Signing coalition with a threshold of parties - // let n_signers = if threshold == n_parties { - // threshold - // } else { - // rng.gen_range(threshold..=n_parties) - // }; - let n_signers = threshold; + // Signing coalition of t to n parties + let n_signers = if threshold == n_parties { + threshold + } else { + rng.gen_range(threshold..=n_parties) + }; let signer_indexes = (0..n_parties).choose_multiple(&mut rng, n_signers as usize); let sid = frost_keys[0].joint_public_key.to_bytes(); @@ -808,7 +815,6 @@ mod test { signatures.push(sig); } - dbg!(signatures.clone()); // TODO get this session from loop above let session = frost.start_sign_session(&frost_keys[signer_indexes[0] as usize], recieved_nonces.clone(), Message::plain("test", b"test")); let combined_sig = frost.combine_signature_shares(&frost_keys[signer_indexes[0] as usize], &session, signatures); From b18d2a9f9a54c16e7609597e5bc9c81952c21f99 Mon Sep 17 00:00:00 2001 From: nickfarrow Date: Mon, 11 Apr 2022 14:05:58 +1000 Subject: [PATCH 22/38] Use prop args for randomness, and schnorr for pop Signed-off-by: nickfarrow --- schnorr_fun/src/frost.rs | 279 +++++++++++++++++++-------------------- 1 file changed, 138 insertions(+), 141 deletions(-) diff --git a/schnorr_fun/src/frost.rs b/schnorr_fun/src/frost.rs index e5b96c93..abc39ac0 100644 --- a/schnorr_fun/src/frost.rs +++ b/schnorr_fun/src/frost.rs @@ -174,7 +174,7 @@ impl PointPoly { #[derive(Clone, Debug)] pub struct KeyGen { point_polys: Vec, - keygen_id: Point, + keygen_id: Scalar, frost_key: FrostKey, } @@ -310,7 +310,7 @@ impl FrostKey { } } -impl + Clone, NG: AddTag> Frost { +impl + Clone, NG: AddTag + NonceGen> Frost { /// Create secret shares and our proof-of-possession to be shared with other participants. /// /// Secret shares are created for every other participant by evaluating our secret polynomial @@ -322,31 +322,23 @@ impl + Clone, NG: AddTag> Frost { /// /// ## Return value /// - /// Returns a vector of secret shares and a proof of possession, as a tupple. + /// Returns a vector of secret shares and a proof of possession Signature /// The secret shares at index 0 is destined for participant 1. pub fn create_shares( &self, KeyGen: &KeyGen, scalar_poly: ScalarPoly, - rng: &mut (impl RngCore + CryptoRng), - ) -> (Vec>, (Point, Scalar)) { - // Create proof of possession - let pop_r = Scalar::random(rng); - let pop_R = g!(pop_r * G).normalize(); - let pop_c = Scalar::from_hash( - self.keygen_id_hash - .clone() - .add(g!({ scalar_poly.0[0].clone() } * G).normalize()) - .add(KeyGen.keygen_id) - .add(pop_R), - ); - let pop_z = s!(pop_c + pop_r); + ) -> (Vec>, Signature) { + let key_pair = self.schnorr.new_keypair(scalar_poly.0[0].clone()); + let pop = self + .schnorr + .sign(&key_pair, Message::::plain("frost-pop", b"")); let shares = (1..=KeyGen.point_polys.len()) .map(|i| scalar_poly.eval(i as u32)) .collect(); - (shares, (pop_R, pop_z)) + (shares, pop) } } @@ -356,22 +348,14 @@ impl + Clone, NG: AddTag> Frost { /// ## Return value /// /// Returns `bool` true if the proof of possession matches this point poly, - fn verify_pop( - &self, - keygen_id: Point, - point_poly: &PointPoly, - pop: (Point, Scalar), - ) -> bool { - let first_point = point_poly.0[0]; - let (pop_R, pop_z) = pop; - let pop_c = Scalar::from_hash( - self.keygen_id_hash - .clone() - .add(first_point) - .add(keygen_id) - .add(pop_R), - ); - !g!(pop_R + pop_c * first_point - pop_z * G).is_zero() + fn verify_pop(&self, point_poly: &PointPoly, pop: Signature) -> bool { + let (even_poly_point, _) = point_poly.0[0].into_point_with_even_y(); + + self.schnorr.verify( + &even_poly_point, + Message::::plain("frost-pop", b""), + &pop, + ) } } @@ -411,7 +395,7 @@ impl + Clone, NG: AddTag> Frost { .into_point_with_even_y(); // TODO set keygen id - let keygen_id = joint_public_key; + let keygen_id = Scalar::from_hash(self.keygen_id_hash.clone().add(joint_public_key)); let verification_shares = (1..=point_polys.len()) .map(|i| joint_poly.eval(i as u32).normalize().mark::()) @@ -447,12 +431,13 @@ impl + Clone, NG: AddTag> Frost { KeyGen: KeyGen, my_index: u32, secret_shares: Vec>, - proofs_of_possession: Vec<(Point, Scalar)>, + proofs_of_possession: Vec, ) -> Result<(Scalar, FrostKey), FinishKeyGenError> { assert_eq!( secret_shares.len(), KeyGen.frost_key.verification_shares.len() ); + assert_eq!(secret_shares.len(), proofs_of_possession.len()); for (i, (poly, pop)) in KeyGen .point_polys @@ -460,7 +445,7 @@ impl + Clone, NG: AddTag> Frost { .zip(proofs_of_possession) .enumerate() { - if !self.verify_pop(KeyGen.keygen_id, poly, pop) { + if !self.verify_pop(poly, pop) { return Err(FinishKeyGenError::InvalidProofOfPossession(i)); } } @@ -711,30 +696,11 @@ impl + Clone, NG: NonceGen + AddTag> Frost { } } -/// Allows getting the FrostKey -// TODO seal this trait -pub trait GetFrostKey { - /// Get Frost key - fn get_frost_key(&self) -> &FrostKey; -} - -impl GetFrostKey for KeyGen { - fn get_frost_key(&self) -> &FrostKey { - &self.frost_key - } -} - -impl GetFrostKey for FrostKey { - fn get_frost_key(&self) -> &FrostKey { - &self - } -} - #[cfg(test)] mod test { + use core::num::NonZeroU32; + use super::*; - use rand::{prelude::IteratorRandom, Rng}; - // proptest::prelude::*}; use secp256kfun::{ nonce::Deterministic, proptest::{arbitrary::any, proptest}, @@ -743,95 +709,124 @@ mod test { proptest! { #[test] - fn frost_prop_test(n_parties in 3u32..8, something in any::()) { - let mut rng = rand::thread_rng(); - let threshold = rng.gen_range(2..=n_parties); + fn frost_prop_test(n_parties in 3u32..8, threshold in 3u32..8, signers_mask_seed in any::(), tweak1 in any::>(), tweak2 in any::>(), use_tweak2 in any::()) { + // Two tweaks let frost = Frost::new(Schnorr::>::new( Deterministic::::default(), )); dbg!(threshold, n_parties); - - let scalar_polys: Vec = (0..n_parties).map(|_| ScalarPoly::random(threshold, &mut rng)).collect(); - let point_polys: Vec = scalar_polys.iter().map(|sp| sp.to_point_poly()).collect(); - - let KeyGen = frost.new_keygen(point_polys).unwrap(); - - let mut proofs_of_possession= vec![]; - let mut shares_vec = vec![]; - for sp in scalar_polys { - let (shares, pop) = frost.create_shares(&KeyGen, sp, &mut rng); - proofs_of_possession.push(pop); - shares_vec.push(shares); - } - - // let recieved_shares = signer_indexes.iter().zip(signer_indexes).map(|(i, j)| (i,j)).collect(); - let mut recieved_shares: Vec> = vec![]; - for party_index in 0..n_parties { - recieved_shares.push(vec![]); - for share_index in 0..n_parties { - recieved_shares[party_index as usize].push(shares_vec[share_index as usize][party_index as usize].clone()); + if n_parties < threshold { + dbg!("too few parties for this threshold"); + } else { + // create some scalar poly for each party + let mut scalar_polys = vec![]; + for i in 1..=n_parties { + let scalar_poly = (1..=threshold).map(|j| Scalar::from_non_zero_u32(NonZeroU32::new(i*j).expect("starts from 1"))).collect(); + scalar_polys.push(ScalarPoly::new(scalar_poly)); } - } + let point_polys: Vec = scalar_polys.iter().map(|sp| sp.to_point_poly()).collect(); - let (secret_shares, frost_keys): (Vec, Vec) = (0..n_parties).map(|i| { - let (secret_share, frost) = frost.finish_keygen( - KeyGen.clone(), - i, - recieved_shares[i as usize].clone(), - proofs_of_possession.clone(), - ) - .unwrap(); - (secret_share, frost) - }).unzip(); - - - // Signing coalition of t to n parties - let n_signers = if threshold == n_parties { - threshold - } else { - rng.gen_range(threshold..=n_parties) - }; - let signer_indexes = (0..n_parties).choose_multiple(&mut rng, n_signers as usize); + let KeyGen = frost.new_keygen(point_polys).unwrap(); - let sid = frost_keys[0].joint_public_key.to_bytes(); + let mut proofs_of_possession= vec![]; + let mut shares_vec = vec![]; + for sp in scalar_polys { + let (shares, pop) = frost.create_shares(&KeyGen, sp); + proofs_of_possession.push(pop); + shares_vec.push(shares); + } - let nonces: Vec = signer_indexes.iter().map(|i| frost.gen_nonce(&secret_shares[*i as usize], &sid)).collect(); + // collect the recieved shares for each party + let mut recieved_shares: Vec> = vec![]; + for party_index in 0..n_parties { + recieved_shares.push(vec![]); + for share_index in 0..n_parties { + recieved_shares[party_index as usize].push(shares_vec[share_index as usize][party_index as usize].clone()); + } + } - let mut recieved_nonces: Vec<_> = vec![]; - for (i, nonce) in signer_indexes.iter().zip(nonces.clone()) { - recieved_nonces.push((*i, nonce.public())); - } + // 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( + KeyGen.clone(), + i, + recieved_shares[i as usize].clone(), + proofs_of_possession.clone(), + ) + .unwrap(); + frost_key = frost_key.tweak(tweak1.clone()).expect("applying tweak1"); + frost_key = if use_tweak2 { + frost_key.tweak(tweak2.clone()).expect("applying tweak2") + } else { + frost_key + }; + (secret_share, frost_key) + }).unzip(); + + // create a mask of signers + // use bytes from random u32 to determine whether a party is a signer or not + let mut signers_mask = vec![true]; + while signers_mask.len() < n_parties as usize { + for v in signers_mask_seed.to_be_bytes() { + if v > 128 { + signers_mask.push(true); + } else { + signers_mask.push(false); + } + if signers_mask.len() >= n_parties as usize { + break + } + } + } + let mut signer_indexes = vec![]; + for (index, included) in signers_mask.iter().enumerate() { + if *included { + signer_indexes.push(index); + } + } + dbg!(&signer_indexes); + + if signer_indexes.len() < threshold as usize { + dbg!("less signers than threshold.. skipping"); + } else { + let sid = frost_keys[0].joint_public_key.to_bytes(); + let nonces: Vec = signer_indexes.iter().map(|i| frost.gen_nonce(&secret_shares[*i as usize], &sid)).collect(); + // dbg!(&nonces); + + let mut recieved_nonces: Vec<_> = vec![]; + for (i, nonce) in signer_indexes.iter().zip(nonces.clone()) { + recieved_nonces.push((*i as u32, nonce.public())); + } + + // Create Frost signing session + let mut signatures = vec![]; + for i in 0..signer_indexes.len() { + let signer_index = signer_indexes[i] as usize; + let session = frost.start_sign_session(&frost_keys[signer_index], recieved_nonces.clone(), Message::plain("test", b"test")); + dbg!(nonces[i].clone()); + let sig = frost.sign(&frost_keys[signer_index], &session, signer_index as u32, &secret_shares[signer_index], nonces[i].clone()); + assert!(frost.verify_signature_share(&frost_keys[signer_index], &session, signer_index as u32, sig)); + signatures.push(sig); + } + + // TODO get this session from loop above + // assert same and use one + let session = frost.start_sign_session(&frost_keys[signer_indexes[0] as usize], recieved_nonces.clone(), Message::plain("test", b"test")); + let combined_sig = frost.combine_signature_shares(&frost_keys[signer_indexes[0] as usize], &session, signatures); + + assert!(frost.schnorr.verify( + &frost_keys[signer_indexes[0] as usize].joint_public_key, + Message::::plain("test", b"test"), + &combined_sig + )); - dbg!(recieved_nonces.clone()); - - // Create Frost signing session - let mut signatures = vec![]; - for i in 0..signer_indexes.len() { - let signer_index = signer_indexes[i] as usize; - let session = frost.start_sign_session(&frost_keys[signer_index], recieved_nonces.clone(), Message::plain("test", b"test")); - dbg!(nonces[i].clone()); - let sig = frost.sign(&frost_keys[signer_index], &session, signer_index as u32, &secret_shares[signer_index], nonces[i].clone()); - assert!(frost.verify_signature_share(&frost_keys[signer_index], &session, signer_index as u32, sig)); - signatures.push(sig); + } } - - // TODO get this session from loop above - let session = frost.start_sign_session(&frost_keys[signer_indexes[0] as usize], recieved_nonces.clone(), Message::plain("test", b"test")); - let combined_sig = frost.combine_signature_shares(&frost_keys[signer_indexes[0] as usize], &session, signatures); - - assert!(frost.schnorr.verify( - &frost_keys[signer_indexes[0] as usize].joint_public_key, - Message::::plain("test", b"test"), - &combined_sig - )); - - } } #[test] fn frost_test_end_to_end() { - let mut rng = rand::thread_rng(); // Create a secret polynomial for each participant let sp1 = ScalarPoly::new(vec![s!(3), s!(7)]); let sp2 = ScalarPoly::new(vec![s!(11), s!(13)]); @@ -847,9 +842,9 @@ mod test { ]; let KeyGen = frost.new_keygen(point_polys).unwrap(); - let (shares1, pop1) = frost.create_shares(&KeyGen, sp1, &mut rng); - let (shares2, pop2) = frost.create_shares(&KeyGen, sp2, &mut rng); - let (shares3, pop3) = frost.create_shares(&KeyGen, sp3, &mut rng); + let (shares1, pop1) = frost.create_shares(&KeyGen, sp1); + let (shares2, pop2) = frost.create_shares(&KeyGen, sp2); + let (shares3, pop3) = frost.create_shares(&KeyGen, sp3); let proofs_of_possession = vec![pop1, pop2, pop3]; let (secret_share1, mut frost_key) = frost @@ -881,18 +876,20 @@ mod test { assert_eq!(frost_key, jk3); let use_tweak = true; - if use_tweak { - let tweak = Scalar::from_bytes([ + let tweak = if use_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 tweak = 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"); - } + .unwrap() + } else { + 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"); // TODO USE PROPER SID // public => [ b"r2-frost", my_index.to_be_bytes(), frost_key.joint_public_key, &frost_key.verification_shares[..], sid] From 3b9faf7281054bda62f4eb6ae445b3879b102695 Mon Sep 17 00:00:00 2001 From: nickfarrow Date: Mon, 11 Apr 2022 18:54:05 +1000 Subject: [PATCH 23/38] clean TODOs, 2x tweak not yet working Signed-off-by: nickfarrow --- schnorr_fun/src/frost.rs | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/schnorr_fun/src/frost.rs b/schnorr_fun/src/frost.rs index abc39ac0..2c5a28f0 100644 --- a/schnorr_fun/src/frost.rs +++ b/schnorr_fun/src/frost.rs @@ -140,7 +140,6 @@ impl PointPoly { /// Combine a vector of point polynomials into a joint polynomial. fn combine(mut polys: impl Iterator) -> PointPoly { let mut combined_poly = polys - // TODO .next() .expect("cannot combine empty list of polys") .0 @@ -279,7 +278,6 @@ impl FrostKey { /// Returns a new FrostKey with the same parties but a different aggregated public key. /// In the erroneous case that the tweak is exactly equal to the negation of the aggregate /// secret key it returns `None`. - /// // TODO ^ CHECK THIS pub fn tweak(&mut self, tweak: Scalar) -> Option { let mut tweak = s!(self.tweak + tweak).mark::(); let (joint_public_key, tweaked_needs_negation) = g!(self.joint_public_key + tweak * G) @@ -469,9 +467,7 @@ impl + Clone, NG: AddTag> Frost { /// Calculate the lagrange coefficient for participant with index x_j and other signers indexes x_ms pub fn lagrange_lambda(x_j: u32, x_ms: &[u32]) -> Scalar { - // TODO - // Change to one inverse - // https://people.maths.ox.ac.uk/trefethen/barycentric.pdf + // TODO change to a single inverse https://people.maths.ox.ac.uk/trefethen/barycentric.pdf (?) let x_j = Scalar::from(x_j).expect_nonzero("target xcoord can not be zero"); x_ms.iter() .map(|x_m| Scalar::from(*x_m).expect_nonzero("index can not be zero")) @@ -799,20 +795,17 @@ mod test { } // Create Frost signing session + let signing_session = frost.start_sign_session(&frost_keys[signer_indexes[0]], recieved_nonces.clone(), Message::plain("test", b"test")); + let mut signatures = vec![]; for i in 0..signer_indexes.len() { let signer_index = signer_indexes[i] as usize; let session = frost.start_sign_session(&frost_keys[signer_index], recieved_nonces.clone(), Message::plain("test", b"test")); - dbg!(nonces[i].clone()); let sig = frost.sign(&frost_keys[signer_index], &session, signer_index as u32, &secret_shares[signer_index], nonces[i].clone()); assert!(frost.verify_signature_share(&frost_keys[signer_index], &session, signer_index as u32, sig)); signatures.push(sig); } - - // TODO get this session from loop above - // assert same and use one - let session = frost.start_sign_session(&frost_keys[signer_indexes[0] as usize], recieved_nonces.clone(), Message::plain("test", b"test")); - let combined_sig = frost.combine_signature_shares(&frost_keys[signer_indexes[0] as usize], &session, signatures); + let combined_sig = frost.combine_signature_shares(&frost_keys[signer_indexes[0] as usize], &signing_session, signatures); assert!(frost.schnorr.verify( &frost_keys[signer_indexes[0] as usize].joint_public_key, From b1c48f4b298d7f58750b9c813c0dbdfc60dad21e Mon Sep 17 00:00:00 2001 From: nickfarrow Date: Tue, 12 Apr 2022 14:55:16 +1000 Subject: [PATCH 24/38] fix multiple tweaks negation by removing extra addition of self.tweak Signed-off-by: nickfarrow --- schnorr_fun/src/frost.rs | 41 ++++++++++++++++++++++++++++++++++------ 1 file changed, 35 insertions(+), 6 deletions(-) diff --git a/schnorr_fun/src/frost.rs b/schnorr_fun/src/frost.rs index 2c5a28f0..0bca2bb9 100644 --- a/schnorr_fun/src/frost.rs +++ b/schnorr_fun/src/frost.rs @@ -263,7 +263,7 @@ impl FrostKey { self.joint_public_key } - /// Tweak the aggregated key with a scalar so that the resulting key is equal to the + /// 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. /// @@ -273,27 +273,39 @@ impl FrostKey { /// 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. + /// /// ## Return value /// /// Returns a new FrostKey with the same parties but a different aggregated 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 { - let mut tweak = s!(self.tweak + tweak).mark::(); - let (joint_public_key, tweaked_needs_negation) = g!(self.joint_public_key + tweak * G) + 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 combined_needs_negation = self.needs_negation ^ tweaked_needs_negation; + let updated_needs_negation = self.needs_negation ^ tweaked_needs_negation; - // Return the new FrostKey including the tweak and updated 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: combined_needs_negation, + needs_negation: updated_needs_negation, }) } @@ -884,6 +896,23 @@ mod test { jk2 = jk2.tweak(tweak.clone()).expect("tweak worked"); jk3 = jk3.tweak(tweak).expect("tweak worked"); + dbg!(); + + let tweak = if use_tweak { + Scalar::from_bytes([ + 0xE8, 0xF7, 0x92, 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() + } else { + 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"); + // TODO USE PROPER SID // public => [ b"r2-frost", my_index.to_be_bytes(), frost_key.joint_public_key, &frost_key.verification_shares[..], sid] let sid = frost_key.joint_public_key.to_bytes(); From 51fe7352c002550536148c59a4e937742fb4d0b1 Mon Sep 17 00:00:00 2001 From: nickfarrow Date: Wed, 13 Apr 2022 14:24:26 +1000 Subject: [PATCH 25/38] prop test restructure Signed-off-by: nickfarrow --- schnorr_fun/src/frost.rs | 184 +++++++++++++++++++-------------------- 1 file changed, 92 insertions(+), 92 deletions(-) diff --git a/schnorr_fun/src/frost.rs b/schnorr_fun/src/frost.rs index 0bca2bb9..0cf37991 100644 --- a/schnorr_fun/src/frost.rs +++ b/schnorr_fun/src/frost.rs @@ -711,121 +711,121 @@ mod test { use super::*; use secp256kfun::{ nonce::Deterministic, - proptest::{arbitrary::any, proptest}, + proptest::{ + arbitrary::any, + proptest, + strategy::{Just, Strategy}, + }, }; use sha2::Sha256; proptest! { #[test] - fn frost_prop_test(n_parties in 3u32..8, threshold in 3u32..8, signers_mask_seed in any::(), tweak1 in any::>(), tweak2 in any::>(), use_tweak2 in any::()) { - // Two tweaks + fn frost_prop_test((n_parties, threshold) in (3u32..8).prop_flat_map(|n| (Just(n), 3u32..=n)), signers_mask_seed in any::(), tweak1 in any::>(), tweak2 in any::>(), use_tweak2 in any::()) { let frost = Frost::new(Schnorr::>::new( Deterministic::::default(), )); dbg!(threshold, n_parties); - if n_parties < threshold { - dbg!("too few parties for this threshold"); - } else { - // create some scalar poly for each party - let mut scalar_polys = vec![]; - for i in 1..=n_parties { - let scalar_poly = (1..=threshold).map(|j| Scalar::from_non_zero_u32(NonZeroU32::new(i*j).expect("starts from 1"))).collect(); - scalar_polys.push(ScalarPoly::new(scalar_poly)); - } - let point_polys: Vec = scalar_polys.iter().map(|sp| sp.to_point_poly()).collect(); + assert!(threshold <= n_parties); - let KeyGen = frost.new_keygen(point_polys).unwrap(); + // create some scalar poly for each party + let mut scalar_polys = vec![]; + for i in 1..=n_parties { + let scalar_poly = (1..=threshold).map(|j| Scalar::from_non_zero_u32(NonZeroU32::new(i*j).expect("starts from 1"))).collect(); + scalar_polys.push(ScalarPoly::new(scalar_poly)); + } + let point_polys: Vec = scalar_polys.iter().map(|sp| sp.to_point_poly()).collect(); - let mut proofs_of_possession= vec![]; - let mut shares_vec = vec![]; - for sp in scalar_polys { - let (shares, pop) = frost.create_shares(&KeyGen, sp); - proofs_of_possession.push(pop); - shares_vec.push(shares); - } + let KeyGen = frost.new_keygen(point_polys).unwrap(); - // collect the recieved shares for each party - let mut recieved_shares: Vec> = vec![]; - for party_index in 0..n_parties { - recieved_shares.push(vec![]); - for share_index in 0..n_parties { - recieved_shares[party_index as usize].push(shares_vec[share_index as usize][party_index as usize].clone()); - } + let mut proofs_of_possession= vec![]; + let mut shares_vec = vec![]; + for sp in scalar_polys { + let (shares, pop) = frost.create_shares(&KeyGen, sp); + proofs_of_possession.push(pop); + shares_vec.push(shares); + } + + // collect the recieved shares for each party + let mut recieved_shares: Vec> = vec![]; + for party_index in 0..n_parties { + recieved_shares.push(vec![]); + for share_index in 0..n_parties { + recieved_shares[party_index as usize].push(shares_vec[share_index as usize][party_index as usize].clone()); } + } - // 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( - KeyGen.clone(), - i, - recieved_shares[i as usize].clone(), - proofs_of_possession.clone(), - ) - .unwrap(); - frost_key = frost_key.tweak(tweak1.clone()).expect("applying tweak1"); - frost_key = if use_tweak2 { - frost_key.tweak(tweak2.clone()).expect("applying tweak2") + // 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( + KeyGen.clone(), + i, + recieved_shares[i as usize].clone(), + proofs_of_possession.clone(), + ) + .unwrap(); + frost_key = frost_key.tweak(tweak1.clone()).expect("applying tweak1"); + frost_key = if use_tweak2 { + frost_key.tweak(tweak2.clone()).expect("applying tweak2") + } else { + frost_key + }; + (secret_share, frost_key) + }).unzip(); + + // create a mask of signers + // use bytes from random u32 to determine whether a party is a signer or not + let mut signers_mask = vec![true]; + while signers_mask.len() < n_parties as usize { + for v in signers_mask_seed.to_be_bytes() { + if v > 128 { + signers_mask.push(true); } else { - frost_key - }; - (secret_share, frost_key) - }).unzip(); - - // create a mask of signers - // use bytes from random u32 to determine whether a party is a signer or not - let mut signers_mask = vec![true]; - while signers_mask.len() < n_parties as usize { - for v in signers_mask_seed.to_be_bytes() { - if v > 128 { - signers_mask.push(true); - } else { - signers_mask.push(false); - } - if signers_mask.len() >= n_parties as usize { - break - } + signers_mask.push(false); } - } - let mut signer_indexes = vec![]; - for (index, included) in signers_mask.iter().enumerate() { - if *included { - signer_indexes.push(index); + if signers_mask.len() >= n_parties as usize { + break } } - dbg!(&signer_indexes); - - if signer_indexes.len() < threshold as usize { - dbg!("less signers than threshold.. skipping"); - } else { - let sid = frost_keys[0].joint_public_key.to_bytes(); - let nonces: Vec = signer_indexes.iter().map(|i| frost.gen_nonce(&secret_shares[*i as usize], &sid)).collect(); - // dbg!(&nonces); - - let mut recieved_nonces: Vec<_> = vec![]; - for (i, nonce) in signer_indexes.iter().zip(nonces.clone()) { - recieved_nonces.push((*i as u32, nonce.public())); - } + } + let mut signer_indexes = vec![]; + for (index, included) in signers_mask.iter().enumerate() { + if *included { + signer_indexes.push(index); + } + } + dbg!(&signer_indexes); - // Create Frost signing session - let signing_session = frost.start_sign_session(&frost_keys[signer_indexes[0]], recieved_nonces.clone(), Message::plain("test", b"test")); + if signer_indexes.len() < threshold as usize { + dbg!("pseudorandomly chose less signers than threshold.. skipping"); + } else { + let sid = frost_keys[0].joint_public_key.to_bytes(); + let nonces: Vec = signer_indexes.iter().map(|i| frost.gen_nonce(&secret_shares[*i as usize], &sid)).collect(); + // dbg!(&nonces); - let mut signatures = vec![]; - for i in 0..signer_indexes.len() { - let signer_index = signer_indexes[i] as usize; - let session = frost.start_sign_session(&frost_keys[signer_index], recieved_nonces.clone(), Message::plain("test", b"test")); - let sig = frost.sign(&frost_keys[signer_index], &session, signer_index as u32, &secret_shares[signer_index], nonces[i].clone()); - assert!(frost.verify_signature_share(&frost_keys[signer_index], &session, signer_index as u32, sig)); - signatures.push(sig); - } - let combined_sig = frost.combine_signature_shares(&frost_keys[signer_indexes[0] as usize], &signing_session, signatures); + let mut recieved_nonces: Vec<_> = vec![]; + for (i, nonce) in signer_indexes.iter().zip(nonces.clone()) { + recieved_nonces.push((*i as u32, nonce.public())); + } - assert!(frost.schnorr.verify( - &frost_keys[signer_indexes[0] as usize].joint_public_key, - Message::::plain("test", b"test"), - &combined_sig - )); + // Create Frost signing session + let signing_session = frost.start_sign_session(&frost_keys[signer_indexes[0]], recieved_nonces.clone(), Message::plain("test", b"test")); + let mut signatures = vec![]; + for i in 0..signer_indexes.len() { + let signer_index = signer_indexes[i] as usize; + let session = frost.start_sign_session(&frost_keys[signer_index], recieved_nonces.clone(), Message::plain("test", b"test")); + let sig = frost.sign(&frost_keys[signer_index], &session, signer_index as u32, &secret_shares[signer_index], nonces[i].clone()); + assert!(frost.verify_signature_share(&frost_keys[signer_index], &session, signer_index as u32, sig)); + signatures.push(sig); } + let combined_sig = frost.combine_signature_shares(&frost_keys[signer_indexes[0] as usize], &signing_session, signatures); + + assert!(frost.schnorr.verify( + &frost_keys[signer_indexes[0] as usize].joint_public_key, + Message::::plain("test", b"test"), + &combined_sig + )); } } } From 1e8844652af5e327b532bd56cc69bb16b80020f3 Mon Sep 17 00:00:00 2001 From: nickfarrow Date: Wed, 13 Apr 2022 16:19:13 +1000 Subject: [PATCH 26/38] use proper sid for generating nonces Signed-off-by: nickfarrow --- schnorr_fun/src/frost.rs | 67 ++++++++++++++++++++++++++++++---------- 1 file changed, 50 insertions(+), 17 deletions(-) diff --git a/schnorr_fun/src/frost.rs b/schnorr_fun/src/frost.rs index 0cf37991..417052cd 100644 --- a/schnorr_fun/src/frost.rs +++ b/schnorr_fun/src/frost.rs @@ -404,8 +404,13 @@ impl + Clone, NG: AddTag> Frost { .ok_or(NewKeyGenError::ZeroFrostKey)? .into_point_with_even_y(); - // TODO set keygen id - let keygen_id = Scalar::from_hash(self.keygen_id_hash.clone().add(joint_public_key)); + let mut keygen_hash = self.keygen_id_hash.clone(); + for poly in point_polys.clone() { + for point in poly.0.iter() { + keygen_hash = keygen_hash.add(point); + } + } + let keygen_id = Scalar::from_hash(keygen_hash); let verification_shares = (1..=point_polys.len()) .map(|i| joint_poly.eval(i as u32).normalize().mark::()) @@ -672,13 +677,14 @@ impl + Clone, NG: NonceGen + AddTag> Frost { impl + Clone, NG: NonceGen + AddTag> Frost { /// Generate nonces for secret shares /// - /// It is very important to carefully consider the implications of your choice of underlying - /// [`NonceGen`]. + /// It is very important that you use a unique `sid` for this signing session and to also carefully + /// consider the implications of your choice of underlying [`NonceGen`]. /// - /// If you are generating nonces prior to KeyGen completion, use the static first coefficient - /// for your `secret`. Otherwise you can use your secret share of the joint FROST key. + /// When choosing a `secret` to use, if you are generating nonces prior to KeyGen completion, + /// use the static first coefficient of your polynomial. + /// Otherwise you can use your secret share of the joint FROST key. /// - /// The application must decide upon a unique `sid` (session id) for this FROST multisignature. + /// The application must decide upon a unique `sid` for this FROST multisignature. /// For example, the concatenation of: my_signing_index, joint_key, verfication_shares /// /// ## Return Value @@ -799,8 +805,19 @@ mod test { if signer_indexes.len() < threshold as usize { dbg!("pseudorandomly chose less signers than threshold.. skipping"); } else { - let sid = frost_keys[0].joint_public_key.to_bytes(); - let nonces: Vec = signer_indexes.iter().map(|i| frost.gen_nonce(&secret_shares[*i as usize], &sid)).collect(); + let verification_shares_bytes: Vec<_> = frost_keys[signer_indexes[0]] + .verification_shares + .iter() + .map(|share| share.to_bytes()) + .collect(); + + let sid = [ + frost_keys[signer_indexes[0]].joint_public_key.to_bytes().as_slice(), + verification_shares_bytes.concat().as_slice(), + b"frost-prop-test".as_slice(), + ] + .concat(); + let nonces: Vec = signer_indexes.iter().map(|i| frost.gen_nonce(&secret_shares[*i as usize], &[sid.as_slice(), [*i as u8].as_slice()].concat())).collect(); // dbg!(&nonces); let mut recieved_nonces: Vec<_> = vec![]; @@ -913,15 +930,31 @@ mod test { jk2 = jk2.tweak(tweak.clone()).expect("tweak worked"); jk3 = jk3.tweak(tweak).expect("tweak worked"); - // TODO USE PROPER SID - // public => [ b"r2-frost", my_index.to_be_bytes(), frost_key.joint_public_key, &frost_key.verification_shares[..], sid] - let sid = frost_key.joint_public_key.to_bytes(); - // for share in frost_key.verification_shares { - // // [sid, share].concat(share.to_bytes()); - // } + let verification_shares_bytes: Vec<_> = frost_key + .verification_shares + .iter() + .map(|share| share.to_bytes()) + .collect(); - let nonce1 = frost.gen_nonce(&secret_share1, &sid); - let nonce3 = frost.gen_nonce(&secret_share3, &sid); + // Create unique session IDs for these signing sessions + let sid1 = [ + 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(), + ] + .concat(); + + let sid2 = [ + 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(), + ] + .concat(); + + let nonce1 = frost.gen_nonce(&secret_share1, &sid1); + let nonce3 = frost.gen_nonce(&secret_share3, &sid2); let nonces = vec![(0, nonce1.public()), (2, nonce3.public())]; let nonces2 = vec![(0, nonce1.public()), (2, nonce3.public())]; From aaa53529690c39c8baa1a42f1a122b19b53fc7fa Mon Sep 17 00:00:00 2001 From: nickfarrow Date: Wed, 13 Apr 2022 16:51:41 +1000 Subject: [PATCH 27/38] use keygen_id in pop Signed-off-by: nickfarrow --- schnorr_fun/src/frost.rs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/schnorr_fun/src/frost.rs b/schnorr_fun/src/frost.rs index 417052cd..0a40853b 100644 --- a/schnorr_fun/src/frost.rs +++ b/schnorr_fun/src/frost.rs @@ -340,9 +340,10 @@ impl + Clone, NG: AddTag + NonceGen> Frost { scalar_poly: ScalarPoly, ) -> (Vec>, Signature) { let key_pair = self.schnorr.new_keypair(scalar_poly.0[0].clone()); - let pop = self - .schnorr - .sign(&key_pair, Message::::plain("frost-pop", b"")); + let pop = self.schnorr.sign( + &key_pair, + Message::::plain("frost-pop", &KeyGen.keygen_id.to_bytes()), + ); let shares = (1..=KeyGen.point_polys.len()) .map(|i| scalar_poly.eval(i as u32)) @@ -358,12 +359,12 @@ impl + Clone, NG: AddTag> Frost { /// ## Return value /// /// Returns `bool` true if the proof of possession matches this point poly, - fn verify_pop(&self, point_poly: &PointPoly, pop: Signature) -> bool { + fn verify_pop(&self, KeyGen: &KeyGen, point_poly: &PointPoly, pop: Signature) -> bool { let (even_poly_point, _) = point_poly.0[0].into_point_with_even_y(); self.schnorr.verify( &even_poly_point, - Message::::plain("frost-pop", b""), + Message::::plain("frost-pop", &KeyGen.keygen_id.to_bytes()), &pop, ) } @@ -460,7 +461,7 @@ impl + Clone, NG: AddTag> Frost { .zip(proofs_of_possession) .enumerate() { - if !self.verify_pop(poly, pop) { + if !self.verify_pop(&KeyGen, poly, pop) { return Err(FinishKeyGenError::InvalidProofOfPossession(i)); } } From 9f6049bd37b950a1527b5c87ee4e3474dcd16d46 Mon Sep 17 00:00:00 2001 From: nickfarrow Date: Wed, 20 Apr 2022 15:26:33 +1000 Subject: [PATCH 28/38] Fix keygen_id Signed-off-by: nickfarrow --- schnorr_fun/src/frost.rs | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/schnorr_fun/src/frost.rs b/schnorr_fun/src/frost.rs index 0a40853b..a1ff3e57 100644 --- a/schnorr_fun/src/frost.rs +++ b/schnorr_fun/src/frost.rs @@ -21,7 +21,7 @@ use secp256kfun::{ derive_nonce, digest::{generic_array::typenum::U32, Digest}, g, - hash::HashAdd, + hash::{HashAdd, Tagged}, marker::*, nonce::{AddTag, NonceGen}, rand_core, s, Point, Scalar, G, @@ -35,12 +35,12 @@ pub struct Frost { keygen_id_hash: H, } -impl Frost { +impl Frost { /// Generate a new Frost context from a Schnorr context. pub fn new(schnorr: Schnorr) -> Self { Self { schnorr: schnorr.clone(), - keygen_id_hash: schnorr.challenge_hash, + keygen_id_hash: H::default().tagged(b"frost/keygenid"), } } } @@ -173,7 +173,7 @@ impl PointPoly { #[derive(Clone, Debug)] pub struct KeyGen { point_polys: Vec, - keygen_id: Scalar, + keygen_id: [u8; 32], frost_key: FrostKey, } @@ -342,7 +342,7 @@ impl + Clone, NG: AddTag + NonceGen> Frost { let key_pair = self.schnorr.new_keypair(scalar_poly.0[0].clone()); let pop = self.schnorr.sign( &key_pair, - Message::::plain("frost-pop", &KeyGen.keygen_id.to_bytes()), + Message::::plain("frost-pop", &KeyGen.keygen_id), ); let shares = (1..=KeyGen.point_polys.len()) @@ -364,7 +364,7 @@ impl + Clone, NG: AddTag> Frost { self.schnorr.verify( &even_poly_point, - Message::::plain("frost-pop", &KeyGen.keygen_id.to_bytes()), + Message::::plain("frost-pop", &KeyGen.keygen_id), &pop, ) } @@ -381,8 +381,8 @@ impl + Clone, NG: AddTag> Frost { /// /// Returns a KeyGen pub fn new_keygen(&self, point_polys: Vec) -> Result { + let len_first_poly = point_polys[0].poly_len(); { - let len_first_poly = point_polys[0].poly_len(); if let Some((i, _)) = point_polys .iter() .enumerate() @@ -406,12 +406,14 @@ impl + Clone, NG: AddTag> Frost { .into_point_with_even_y(); let mut keygen_hash = self.keygen_id_hash.clone(); - for poly in point_polys.clone() { + keygen_hash.update((len_first_poly as u32).to_be_bytes()); + keygen_hash.update((point_polys.len() as u32).to_be_bytes()); + for poly in &point_polys { for point in poly.0.iter() { - keygen_hash = keygen_hash.add(point); + keygen_hash.update(point.to_bytes()); } } - let keygen_id = Scalar::from_hash(keygen_hash); + let keygen_id = keygen_hash.finalize().into(); let verification_shares = (1..=point_polys.len()) .map(|i| joint_poly.eval(i as u32).normalize().mark::()) From aac0e579a319bee54cfb58d458907eae597e66f9 Mon Sep 17 00:00:00 2001 From: nickfarrow Date: Mon, 9 May 2022 15:42:49 +1000 Subject: [PATCH 29/38] use option type for proptest tweaks --- schnorr_fun/src/frost.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/schnorr_fun/src/frost.rs b/schnorr_fun/src/frost.rs index a1ff3e57..18aad8ba 100644 --- a/schnorr_fun/src/frost.rs +++ b/schnorr_fun/src/frost.rs @@ -722,7 +722,7 @@ mod test { nonce::Deterministic, proptest::{ arbitrary::any, - proptest, + option, proptest, strategy::{Just, Strategy}, }, }; @@ -730,7 +730,7 @@ mod test { proptest! { #[test] - fn frost_prop_test((n_parties, threshold) in (3u32..8).prop_flat_map(|n| (Just(n), 3u32..=n)), signers_mask_seed in any::(), tweak1 in any::>(), tweak2 in any::>(), use_tweak2 in any::()) { + fn frost_prop_test((n_parties, threshold) in (3u32..8).prop_flat_map(|n| (Just(n), 3u32..=n)), signers_mask_seed in any::(), tweak1 in option::of(any::>()), tweak2 in option::of(any::>())) { let frost = Frost::new(Schnorr::>::new( Deterministic::::default(), )); @@ -773,12 +773,12 @@ mod test { proofs_of_possession.clone(), ) .unwrap(); - frost_key = frost_key.tweak(tweak1.clone()).expect("applying tweak1"); - frost_key = if use_tweak2 { - frost_key.tweak(tweak2.clone()).expect("applying tweak2") - } else { - frost_key - }; + + for tweak in [tweak1, tweak2] { + if let Some(tweak) = tweak { + frost_key = frost_key.tweak(tweak).unwrap(); + } + } (secret_share, frost_key) }).unzip(); From c479a468d2ac9ef48a0bc46ab0d19ba85187525c Mon Sep 17 00:00:00 2001 From: nickfarrow Date: Mon, 9 May 2022 15:58:44 +1000 Subject: [PATCH 30/38] Synopsis and make docs test pass --- schnorr_fun/README.md | 2 + schnorr_fun/src/frost.rs | 128 ++++++++++++++++++++++++++++++++++----- 2 files changed, 116 insertions(+), 14 deletions(-) diff --git a/schnorr_fun/README.md b/schnorr_fun/README.md index aaa1f7cd..b083ffb5 100644 --- a/schnorr_fun/README.md +++ b/schnorr_fun/README.md @@ -54,6 +54,7 @@ assert!(schnorr.verify(&verification_key, message, &signature)); - Adaptor signatures - compatibility with `rust-secp256k1`'s `schnorrsig` module with `libsecp_compat` feature. - [MuSig2] implementation compatible with [this PR](https://github.com/jonasnick/bips/pull/37) of the spec. +- [FROST] implementation - Feature flags - `serde`: for serde implementations for signatures - `libsecp_compat`: for `From` implementations between `rust-secp256k1`'s Schnorr signatures. @@ -64,3 +65,4 @@ assert!(schnorr.verify(&verification_key, message, &signature)); [secp256kfun]: https://docs.rs/secp256kfun [secp256k1-zkp]: https://github.com/ElementsProject/secp256k1-zkp/pull/131 [MuSig2]: https://eprint.iacr.org/2020/1261.pdf +[FROST]: https://eprint.iacr.org/2020/852.pdf diff --git a/schnorr_fun/src/frost.rs b/schnorr_fun/src/frost.rs index 18aad8ba..ba646c01 100644 --- a/schnorr_fun/src/frost.rs +++ b/schnorr_fun/src/frost.rs @@ -1,16 +1,110 @@ -//! ## Description +//! ## FROST multisignature scheme //! -//! The FROST (Flexible Round-Optimize Schnorr Threshold) multisignature scheme lets you aggregate -//! multiple public keys into a single public key that requires some threshold t-of-n secret keys to -//! sign a signature under the aggregate key. +//! The FROST (Flexible Round-Optimize Schnorr Threshold) multisignature scheme allows you aggregate +//! multiple public keys into a single public key. To sign a message under this public key, a threshold t-of-n secret keys +//! must use a common set of nonces to each produce a signature share. These signature shares are then combined +//! to form a signature that is valid under the aggregate key. //! -//! This implementation has NOT yet been made compatible with other existing implementations -//! [secp256k1-zkp]: https://github.com/ElementsProject/secp256k1-zkp/pull/138 +//! This implementation has **not yet** been made compatible with other existing FROST implementations +//! (notably [secp256k1-zkp]). //! -//! See MuSig in this repository, the [FROST paper] and [Security of Multi- and Threshold Signatures]. +//! For reference see the [FROST paper], the MuSig implementation in this repository, and also [Security of Multi- and Threshold Signatures]. //! -//! [FROST paper]: https://eprint.iacr.org/2020/852.pdf -//! [Security of Multi- and Threshold Signatures]: https://eprint.iacr.org/2021/1375.pdf +//! [secp256k1-zkp]: +//! [FROST paper]: +//! [Security of Multi- and Threshold Signatures]: +//! +//! ## Synopsis +//! +//! ``` +//! use schnorr_fun::{frost::{Frost, ScalarPoly}, Schnorr, Message, nonce::Deterministic, fun::marker::Public}; +//! use sha2::Sha256; +//! // use SHA256 with deterministic nonce generation +//! let frost = Frost::new(Schnorr::>::new( +//! Deterministic::::default(), +//! )); +//! // create a random secret scalar poly with two coefficients, +//! // corresponding to FROST multisig with a threshold of two +//! let scalar_poly = ScalarPoly::random(2, &mut rand::thread_rng()); +//! # let scalar_poly2 = ScalarPoly::random(2, &mut rand::thread_rng()); +//! # let scalar_poly3 = ScalarPoly::random(2, &mut rand::thread_rng()); +//! // share our public point poly, and recieve the point polys from other participants +//! # let point_poly2 = scalar_poly2.to_point_poly(); +//! # let point_poly3 = scalar_poly3.to_point_poly(); +//! let point_polys = vec![scalar_poly.to_point_poly(), point_poly2, point_poly3]; +//! // create secret shares and proof of possession using our secret scalar poly +//! let keygen = frost.new_keygen(point_polys).unwrap(); +//! let (shares, pop) = frost.create_shares(&keygen, scalar_poly); +//! # let (shares2, pop2) = frost.create_shares(&keygen, scalar_poly2); +//! # let (shares3, pop3) = frost.create_shares(&keygen, scalar_poly3); +//! // send shares at index i and all proofs-of-possession to each other participant i, +//! // and recieve our shares from each other participant as well as their proofs-of-possession. +//! let recieved_shares = vec![shares[0].clone(), shares2[0].clone(), shares3[0].clone()]; +//! # let recieved_shares3 = vec![shares[2].clone(), shares2[2].clone(), shares3[2].clone()]; +//! let proofs_of_possession = vec![pop, pop2, pop3]; +//! // finish keygen by calculating our secret share of the joint FROST key +//! let (secret_share, frost_key) = frost +//! .finish_keygen( +//! keygen.clone(), +//! 0, +//! recieved_shares, +//! proofs_of_possession.clone(), +//! ) +//! .unwrap(); +//! # let (secret_share3, _frost_key3) = frost +//! # .finish_keygen( +//! # keygen.clone(), +//! # 2, +//! # recieved_shares3, +//! # proofs_of_possession.clone(), +//! # ) +//! # .unwrap(); +//! // for signing we must have a unique session ID to derive nonces. +//! // we should include all the information that is publicly available. +//! let verification_shares_bytes: Vec<_> = frost_key +//! .verification_shares +//! .iter() +//! .map(|share| share.to_bytes()) +//! .collect(); +//! // create a unique session ID for this signing session +//! let sid = [ +//! frost_key.joint_public_key.to_bytes().as_slice(), +//! verification_shares_bytes.concat().as_slice(), +//! b"frost-very-unique-id".as_slice(), +//! b"0".as_slice(), +//! ] +//! .concat(); +//! # let sid3 = [ +//! # frost_key.joint_public_key.to_bytes().as_slice(), +//! # verification_shares_bytes.concat().as_slice(), +//! # b"frost-very-unique-id".as_slice(), +//! # b"2".as_slice(), +//! # ] +//! # .concat(); +//! // generate nonces for this signing session +//! let nonce = frost.gen_nonce(&secret_share, &sid); +//! # let nonce3 = frost.gen_nonce(&secret_share3, &sid3); +//! // share your public nonce with the other signing participant(s) +//! # let recieved_nonce3 = nonce3.public(); +//! // recieve public nonces from other participants with their index +//! let nonces = vec![(0, nonce.public()), (2, recieved_nonce3)]; +//! # let nonces3 = vec![(0, nonce.public()), (2, recieved_nonce3)]; +//! // start a sign session with these nonces for this message +//! let session = frost.start_sign_session(&frost_key, nonces, Message::plain("test", b"test")); +//! # let session3 = frost.start_sign_session(&frost_key, nonces3, Message::plain("test", b"test")); +//! // create a partial signature +//! let sig = frost.sign(&frost_key, &session, 0, &secret_share, nonce); +//! # let sig3 = frost.sign(&frost_key, &session3, 2, &secret_share3, nonce3); +//! // recieve partial signature(s) from other participant(s) and verify +//! assert!(frost.verify_signature_share(&frost_key, &session, 2, sig3)); +//! // combine signature +//! let combined_sig = frost.combine_signature_shares(&frost_key, &session, vec![sig, sig3]); +//! assert!(frost.schnorr.verify( +//! &frost_key.joint_public_key, +//! Message::::plain("test", b"test"), +//! &combined_sig +//! )); +//! ``` use crate::{ musig::{Nonce, NonceKeyPair}, Message, Schnorr, Signature, Vec, @@ -31,7 +125,8 @@ use std::collections::BTreeMap; /// The FROST context. #[derive(Clone)] pub struct Frost { - schnorr: Schnorr, + /// The instance of the Schnorr signature scheme + pub schnorr: Schnorr, keygen_id_hash: H, } @@ -246,10 +341,15 @@ impl std::error::Error for FinishKeyGenError {} serde(crate = "serde_crate") )] pub struct FrostKey { - joint_public_key: Point, - verification_shares: Vec, - threshold: u32, + /// The joint public key of the FROST multisignature + pub joint_public_key: Point, + /// Everyone else's point poly 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, } @@ -657,7 +757,7 @@ impl + Clone, NG: NonceGen + AddTag> Frost { /// /// ## Return value /// - /// Returns a combined schnorr [`schnorr_fun::signature::Signature`] for the message. + /// Returns a combined schnorr [`Signature`] for the message. /// Valid against the joint public key. pub fn combine_signature_shares( &self, From abf6e0dc50c4a9340124de09da8056c029bb7f3c Mon Sep 17 00:00:00 2001 From: nickfarrow Date: Mon, 16 May 2022 14:49:22 +1000 Subject: [PATCH 31/38] clearer synopsis, docs, comments --- schnorr_fun/src/frost.rs | 119 +++++++++++++++++++++------------------ 1 file changed, 63 insertions(+), 56 deletions(-) diff --git a/schnorr_fun/src/frost.rs b/schnorr_fun/src/frost.rs index ba646c01..46f26e8f 100644 --- a/schnorr_fun/src/frost.rs +++ b/schnorr_fun/src/frost.rs @@ -1,6 +1,6 @@ //! ## FROST multisignature scheme //! -//! The FROST (Flexible Round-Optimize Schnorr Threshold) multisignature scheme allows you aggregate +//! The FROST (Flexible Round-Optimized Schnorr Threshold) multisignature scheme allows you aggregate //! multiple public keys into a single public key. To sign a message under this public key, a threshold t-of-n secret keys //! must use a common set of nonces to each produce a signature share. These signature shares are then combined //! to form a signature that is valid under the aggregate key. @@ -23,8 +23,8 @@ //! let frost = Frost::new(Schnorr::>::new( //! Deterministic::::default(), //! )); -//! // create a random secret scalar poly with two coefficients, -//! // corresponding to FROST multisig with a threshold of two +//! // to create a FROST multisig with a threshold of two, each participant creates +//! // a random secret scalar polynomial with two coefficients. //! let scalar_poly = ScalarPoly::random(2, &mut rand::thread_rng()); //! # let scalar_poly2 = ScalarPoly::random(2, &mut rand::thread_rng()); //! # let scalar_poly3 = ScalarPoly::random(2, &mut rand::thread_rng()); @@ -32,17 +32,18 @@ //! # let point_poly2 = scalar_poly2.to_point_poly(); //! # let point_poly3 = scalar_poly3.to_point_poly(); //! let point_polys = vec![scalar_poly.to_point_poly(), point_poly2, point_poly3]; -//! // create secret shares and proof of possession using our secret scalar poly +//! // create secret shares and proofs-of-possession using our secret scalar polynomial //! let keygen = frost.new_keygen(point_polys).unwrap(); //! let (shares, pop) = frost.create_shares(&keygen, scalar_poly); //! # let (shares2, pop2) = frost.create_shares(&keygen, scalar_poly2); //! # let (shares3, pop3) = frost.create_shares(&keygen, scalar_poly3); -//! // send shares at index i and all proofs-of-possession to each other participant i, +//! // send the shares at index i and all proofs-of-possession to each other participant i, //! // and recieve our shares from each other participant as well as their proofs-of-possession. //! let recieved_shares = vec![shares[0].clone(), shares2[0].clone(), shares3[0].clone()]; //! # let recieved_shares3 = vec![shares[2].clone(), shares2[2].clone(), shares3[2].clone()]; //! let proofs_of_possession = vec![pop, pop2, pop3]; -//! // finish keygen by calculating our secret share of the joint FROST key +//! // finish keygen by verifying the shares we recieved as well as proofs-of-possession +//! // and calulate our secret share of the joint FROST key //! let (secret_share, frost_key) = frost //! .finish_keygen( //! keygen.clone(), @@ -59,8 +60,8 @@ //! # proofs_of_possession.clone(), //! # ) //! # .unwrap(); -//! // for signing we must have a unique session ID to derive nonces. -//! // we should include all the information that is publicly available. +//! // for signing we must have a unique session ID to derive nonces such that nonces +//! // are never reused. For gen_nonce we use all information that is publicly available. //! let verification_shares_bytes: Vec<_> = frost_key //! .verification_shares //! .iter() @@ -92,12 +93,12 @@ //! // start a sign session with these nonces for this message //! let session = frost.start_sign_session(&frost_key, nonces, Message::plain("test", b"test")); //! # let session3 = frost.start_sign_session(&frost_key, nonces3, Message::plain("test", b"test")); -//! // create a partial signature +//! // create a partial signature using our secret share and secret nonce //! let sig = frost.sign(&frost_key, &session, 0, &secret_share, nonce); //! # let sig3 = frost.sign(&frost_key, &session3, 2, &secret_share3, nonce3); //! // recieve partial signature(s) from other participant(s) and verify //! assert!(frost.verify_signature_share(&frost_key, &session, 2, sig3)); -//! // combine signature +//! // combine signature shares into a single signature that is valid under the joint key //! let combined_sig = frost.combine_signature_shares(&frost_key, &session, vec![sig, sig3]); //! assert!(frost.schnorr.verify( //! &frost_key.joint_public_key, @@ -105,10 +106,8 @@ //! &combined_sig //! )); //! ``` -use crate::{ - musig::{Nonce, NonceKeyPair}, - Message, Schnorr, Signature, Vec, -}; +pub use crate::binonce::{Nonce, NonceKeyPair}; +use crate::{Message, Schnorr, Signature, Vec}; use core::iter; use rand_core::{CryptoRng, RngCore}; use secp256kfun::{ @@ -123,10 +122,13 @@ use secp256kfun::{ use std::collections::BTreeMap; /// The FROST context. +/// H: hash for challenges and creating a keygen_id +/// NG: hash for nonce generation #[derive(Clone)] pub struct Frost { - /// The instance of the Schnorr signature scheme + /// The instance of the Schnorr signature scheme. pub schnorr: Schnorr, + /// The hash used to generate the keygen_id. keygen_id_hash: H, } @@ -172,6 +174,8 @@ impl ScalarPoly { /// Create a scalar polynomial where the first coefficient is a specified secret and /// the remaining coefficients are random. + /// + /// This will be used for creating secret polynomials with known & reproducable secrets. pub fn random_using_secret( n_coefficients: u32, secret: Scalar, @@ -219,7 +223,7 @@ pub struct PointPoly( ); impl PointPoly { - /// Evaluate the polynomial at position x. + /// Evaluate the point polynomial at position x. pub fn eval(&self, x: u32) -> Point { let x = Scalar::from(x) .expect_nonzero("must be non-zero") @@ -234,6 +238,7 @@ impl PointPoly { /// Combine a vector of point polynomials into a joint polynomial. fn combine(mut polys: impl Iterator) -> PointPoly { + // take the first point polynomial and collect its coefficients let mut combined_poly = polys .next() .expect("cannot combine empty list of polys") @@ -241,6 +246,7 @@ impl PointPoly { .into_iter() .map(|p| p.mark::<(Jacobian, Zero)>()) .collect::>(); + // add the coefficients of the remaining polys for poly in polys { for (combined_point, point) in combined_poly.iter_mut().zip(poly.0) { *combined_point = g!({ *combined_point } + point); @@ -279,16 +285,16 @@ impl KeyGen { } } -/// First round errors +/// First round keygen errors #[derive(Debug, Clone)] pub enum NewKeyGenError { /// Received polynomial is of differing length. PolyDifferentLength(usize), /// Number of parties is less than the length of polynomials specifying the threshold. NotEnoughParties, - /// Frost key is zero. Should be impossible, or maliciously chosen. + /// Frost key is zero. This should be impossible, likely has been maliciously chosen. ZeroFrostKey, - /// Verification share is zero. Should be impossible, or maliciously chosen. + /// Verification share is zero. This should be impossible, likely has been maliciously chosen. ZeroVerificationShare, } @@ -298,8 +304,8 @@ impl core::fmt::Display for NewKeyGenError { match self { PolyDifferentLength(i) => write!(f, "polynomial commitment from party at index {} was a different length", i), NotEnoughParties => write!(f, "the number of parties was less than the threshold"), - ZeroFrostKey => write!(f, "The joint FROST key was zero. This means one of the parties was possibly malicious and you are not protecting against this properly"), - ZeroVerificationShare => write!(f, "One of the verification shares was malicious so we must abort the protocol"), + ZeroFrostKey => write!(f, "The joint FROST key was zero. This should be impossible, one party is acting maliciously."), + ZeroVerificationShare => write!(f, "Zero verification share. This should be impossible, one party is acting maliciously."), } } } @@ -320,10 +326,15 @@ impl core::fmt::Display for FinishKeyGenError { fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { use FinishKeyGenError::*; match self { - InvalidShare(i) => write!(f, "the share provided by party at index {} was invalid", i), + InvalidShare(i) => write!( + f, + "the secret share at index {} does not match the expected evaluation \ + of their point polynomial at our index. Check that the order and our index is correct", + i + ), &InvalidProofOfPossession(i) => write!( f, - "the proof of possession provided by party at index {} was invalid", + "the proof of possession provided by party at index {} was invalid, check ordering.", i ), } @@ -341,15 +352,15 @@ impl std::error::Error for FinishKeyGenError {} serde(crate = "serde_crate") )] pub struct FrostKey { - /// The joint public key of the FROST multisignature + /// The joint public key of the FROST multisignature. pub joint_public_key: Point, - /// Everyone else's point poly evaluated at your index, used in partial signature validation. + /// 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 + /// Whether the secrets need negation in order to sign for the X-Only key. needs_negation: bool, } @@ -369,26 +380,25 @@ impl FrostKey { /// /// This is how you embed a taproot commitment into a key. /// - /// 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. - /// /// ## Return value /// /// Returns a new FrostKey with the same parties but a different aggregated 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::()? @@ -426,9 +436,8 @@ impl + Clone, NG: AddTag + NonceGen> Frost { /// Secret shares are created for every other participant by evaluating our secret polynomial /// at their participant index. f(i) for 1<=i<=n. /// - /// Each secret share f(i) needs to be securely communicated to participant i. - /// - /// Also creates a proof of possession for the first coefficient of our secret scalar polynomial. + /// Each secret share f(i) needs to be securely communicated to participant i. Additionally + /// we share a proof of possession for the first coefficient in our secret scalar polynomial. /// /// ## Return value /// @@ -454,11 +463,11 @@ impl + Clone, NG: AddTag + NonceGen> Frost { } impl + Clone, NG: AddTag> Frost { - /// Verify a proof of possession against a participant's point polynomial + /// Verify a proof of possession against a participant's committed point polynomial /// /// ## Return value /// - /// Returns `bool` true if the proof of possession matches this point poly, + /// Returns `bool` true if the proof of possession matches the point polynomial fn verify_pop(&self, KeyGen: &KeyGen, point_poly: &PointPoly, pop: Signature) -> bool { let (even_poly_point, _) = point_poly.0[0].into_point_with_even_y(); @@ -473,13 +482,13 @@ impl + Clone, NG: AddTag> Frost { impl + Clone, NG: AddTag> Frost { /// Collect all the public polynomials into a KeyGen session with a FrostKey. /// - /// Takes a vector of point polynomials with your polynomial at index 0. + /// Takes a vector of point polynomials to use for this FrostKey /// /// Also prepares a vector of verification shares for later. /// /// ## Return value /// - /// Returns a KeyGen + /// Returns a KeyGen containing a FrostKey pub fn new_keygen(&self, point_polys: Vec) -> Result { let len_first_poly = point_polys[0].poly_len(); { @@ -578,7 +587,7 @@ impl + Clone, NG: AddTag> Frost { } let total_secret_share = total_secret_share.expect_nonzero( - "since verification shares are non-zero, corresponding secret shares cannot be zero", + "since verification shares are non-zero, the total secret share cannot be zero", ); Ok((total_secret_share, KeyGen.frost_key)) @@ -586,8 +595,7 @@ impl + Clone, NG: AddTag> Frost { } /// Calculate the lagrange coefficient for participant with index x_j and other signers indexes x_ms -pub fn lagrange_lambda(x_j: u32, x_ms: &[u32]) -> Scalar { - // TODO change to a single inverse https://people.maths.ox.ac.uk/trefethen/barycentric.pdf (?) +fn lagrange_lambda(x_j: u32, x_ms: &[u32]) -> Scalar { let x_j = Scalar::from(x_j).expect_nonzero("target xcoord can not be zero"); x_ms.iter() .map(|x_m| Scalar::from(*x_m).expect_nonzero("index can not be zero")) @@ -627,10 +635,8 @@ impl + Clone, NG: NonceGen + AddTag> Frost { ) -> SignSession { let mut nonce_map: BTreeMap<_, _> = nonces.into_iter().map(|(i, nonce)| (i, nonce)).collect(); - // assert_eq!(nonces.len(), nonce_map.len()); - assert!(frost_key.threshold <= nonce_map.len() as u32); - let agg_nonces_R1_R2: Vec = nonce_map + let agg_nonce_points: [Point; 2] = nonce_map .iter() .fold([Point::zero().mark::(); 2], |acc, (_, nonce)| { [ @@ -645,9 +651,10 @@ impl + Clone, NG: NonceGen + AddTag> Frost { .mark::() .expect("aggregate nonce should be non-zero") }) - .collect(); + .collect::>() + .try_into() + .expect("there are only R1 and R2, collecting cant fail"); - let agg_nonce_points: [Point; 2] = [agg_nonces_R1_R2[0], agg_nonces_R1_R2[1]]; let binding_coeff = Scalar::from_hash( self.schnorr .challenge_hash() @@ -837,7 +844,7 @@ mod test { dbg!(threshold, n_parties); assert!(threshold <= n_parties); - // create some scalar poly for each party + // create some scalar polynomial for each party let mut scalar_polys = vec![]; for i in 1..=n_parties { let scalar_poly = (1..=threshold).map(|j| Scalar::from_non_zero_u32(NonZeroU32::new(i*j).expect("starts from 1"))).collect(); From c788e727232e37a46b31683c7e313ff47a6df9d8 Mon Sep 17 00:00:00 2001 From: nickfarrow Date: Mon, 30 May 2022 18:29:00 +1000 Subject: [PATCH 32/38] Deterministically shuffled signer mask with TestRng --- schnorr_fun/src/frost.rs | 104 ++++++++++++++++----------------------- 1 file changed, 43 insertions(+), 61 deletions(-) diff --git a/schnorr_fun/src/frost.rs b/schnorr_fun/src/frost.rs index 46f26e8f..60758ff6 100644 --- a/schnorr_fun/src/frost.rs +++ b/schnorr_fun/src/frost.rs @@ -825,19 +825,21 @@ mod test { use core::num::NonZeroU32; use super::*; + use rand::seq::SliceRandom; use secp256kfun::{ nonce::Deterministic, proptest::{ arbitrary::any, option, proptest, strategy::{Just, Strategy}, + test_runner::{RngAlgorithm, TestRng}, }, }; use sha2::Sha256; proptest! { #[test] - fn frost_prop_test((n_parties, threshold) in (3u32..8).prop_flat_map(|n| (Just(n), 3u32..=n)), signers_mask_seed in any::(), tweak1 in option::of(any::>()), tweak2 in option::of(any::>())) { + fn frost_prop_test((n_parties, threshold) in (3u32..8).prop_flat_map(|n| (Just(n), 3u32..=n)), tweak1 in option::of(any::>()), tweak2 in option::of(any::>())) { let frost = Frost::new(Schnorr::>::new( Deterministic::::default(), )); @@ -889,71 +891,51 @@ mod test { (secret_share, frost_key) }).unzip(); - // create a mask of signers - // use bytes from random u32 to determine whether a party is a signer or not - let mut signers_mask = vec![true]; - while signers_mask.len() < n_parties as usize { - for v in signers_mask_seed.to_be_bytes() { - if v > 128 { - signers_mask.push(true); - } else { - signers_mask.push(false); - } - if signers_mask.len() >= n_parties as usize { - break - } - } - } - let mut signer_indexes = vec![]; - for (index, included) in signers_mask.iter().enumerate() { - if *included { - signer_indexes.push(index); - } - } - dbg!(&signer_indexes); - - if signer_indexes.len() < threshold as usize { - dbg!("pseudorandomly chose less signers than threshold.. skipping"); - } else { - let verification_shares_bytes: Vec<_> = frost_keys[signer_indexes[0]] - .verification_shares - .iter() - .map(|share| share.to_bytes()) - .collect(); - - let sid = [ - frost_keys[signer_indexes[0]].joint_public_key.to_bytes().as_slice(), - verification_shares_bytes.concat().as_slice(), - b"frost-prop-test".as_slice(), - ] - .concat(); - let nonces: Vec = signer_indexes.iter().map(|i| frost.gen_nonce(&secret_shares[*i as usize], &[sid.as_slice(), [*i as u8].as_slice()].concat())).collect(); - // dbg!(&nonces); + // use a boolean mask for which t participants are signers + let mut signer_mask = vec![true; threshold as usize]; + signer_mask.append(&mut vec![false; (n_parties - threshold) as usize]); + // shuffle the mask for random signers (roughly shuffled and deterministic based on signers_mask_seed) + signer_mask.shuffle(&mut TestRng::deterministic_rng(RngAlgorithm::ChaCha)); - let mut recieved_nonces: Vec<_> = vec![]; - for (i, nonce) in signer_indexes.iter().zip(nonces.clone()) { - recieved_nonces.push((*i as u32, nonce.public())); - } + let signer_indexes: Vec<_> = signer_mask.iter().enumerate().filter(|(_, is_signer)| **is_signer).map(|(i,_)| i).collect(); - // Create Frost signing session - let signing_session = frost.start_sign_session(&frost_keys[signer_indexes[0]], recieved_nonces.clone(), Message::plain("test", b"test")); + let verification_shares_bytes: Vec<_> = frost_keys[signer_indexes[0]] + .verification_shares + .iter() + .map(|share| share.to_bytes()) + .collect(); + + let sid = [ + frost_keys[signer_indexes[0]].joint_public_key.to_bytes().as_slice(), + verification_shares_bytes.concat().as_slice(), + b"frost-prop-test".as_slice(), + ] + .concat(); + let nonces: Vec = signer_indexes.iter().map(|i| frost.gen_nonce(&secret_shares[*i as usize], &[sid.as_slice(), [*i as u8].as_slice()].concat())).collect(); + + let mut recieved_nonces: Vec<_> = vec![]; + for (i, nonce) in signer_indexes.iter().zip(nonces.clone()) { + recieved_nonces.push((*i as u32, nonce.public())); + } - let mut signatures = vec![]; - for i in 0..signer_indexes.len() { - let signer_index = signer_indexes[i] as usize; - let session = frost.start_sign_session(&frost_keys[signer_index], recieved_nonces.clone(), Message::plain("test", b"test")); - let sig = frost.sign(&frost_keys[signer_index], &session, signer_index as u32, &secret_shares[signer_index], nonces[i].clone()); - assert!(frost.verify_signature_share(&frost_keys[signer_index], &session, signer_index as u32, sig)); - signatures.push(sig); - } - let combined_sig = frost.combine_signature_shares(&frost_keys[signer_indexes[0] as usize], &signing_session, signatures); + // Create Frost signing session + let signing_session = frost.start_sign_session(&frost_keys[signer_indexes[0]], recieved_nonces.clone(), Message::plain("test", b"test")); - assert!(frost.schnorr.verify( - &frost_keys[signer_indexes[0] as usize].joint_public_key, - Message::::plain("test", b"test"), - &combined_sig - )); + let mut signatures = vec![]; + for i in 0..signer_indexes.len() { + let signer_index = signer_indexes[i] as usize; + let session = frost.start_sign_session(&frost_keys[signer_index], recieved_nonces.clone(), Message::plain("test", b"test")); + let sig = frost.sign(&frost_keys[signer_index], &session, signer_index as u32, &secret_shares[signer_index], nonces[i].clone()); + assert!(frost.verify_signature_share(&frost_keys[signer_index], &session, signer_index as u32, sig)); + signatures.push(sig); } + let combined_sig = frost.combine_signature_shares(&frost_keys[signer_indexes[0] as usize], &signing_session, signatures); + + assert!(frost.schnorr.verify( + &frost_keys[signer_indexes[0] as usize].joint_public_key, + Message::::plain("test", b"test"), + &combined_sig + )); } } From 7803ed8ac75fd3459a644e7c11fcee4fcd9e725c Mon Sep 17 00:00:00 2001 From: nickfarrow Date: Mon, 9 May 2022 15:58:44 +1000 Subject: [PATCH 33/38] Synopsis and make docs test pass From 64dce458fc281200d578278035e6e02c073f4d62 Mon Sep 17 00:00:00 2001 From: nickfarrow Date: Tue, 16 Aug 2022 13:58:52 +1000 Subject: [PATCH 34/38] XOnlyFrostKey and plain tweaks Following https://github.com/LLFourn/secp256kfun/pull/100 --- schnorr_fun/src/frost.rs | 220 ++++++++++++++++++++++++++------------- 1 file changed, 149 insertions(+), 71 deletions(-) diff --git a/schnorr_fun/src/frost.rs b/schnorr_fun/src/frost.rs index 60758ff6..4c2fc03c 100644 --- a/schnorr_fun/src/frost.rs +++ b/schnorr_fun/src/frost.rs @@ -45,7 +45,7 @@ //! // finish keygen by verifying the shares we recieved as well as proofs-of-possession //! // and calulate our secret share of the joint FROST key //! let (secret_share, frost_key) = frost -//! .finish_keygen( +//! .finish_keygen_to_xonly( //! keygen.clone(), //! 0, //! recieved_shares, @@ -53,7 +53,7 @@ //! ) //! .unwrap(); //! # let (secret_share3, _frost_key3) = frost -//! # .finish_keygen( +//! # .finish_keygen_to_xonly( //! # keygen.clone(), //! # 2, //! # recieved_shares3, @@ -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 updated_needs_negation = self.needs_negation ^ tweaked_needs_negation; + let joint_public_key = g!(self.joint_public_key + tweak * G) + .normalize() + .mark::()?; + let tweak = s!(self.tweak + tweak).mark::(); - // 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_xonly_key(self) -> XOnlyFrostKey { + 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); + XOnlyFrostKey { + 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 [`XOnlyFrostKey`] 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 `XOnlyFrostKey` are XOnly tweaks to the joint public key. +/// BIP340: +#[cfg_attr( + feature = "serde", + derive(serde::Deserialize, serde::Serialize), + serde(crate = "serde_crate") +)] +#[derive(Clone, Debug, PartialEq)] +pub struct XOnlyFrostKey { + /// 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 XOnlyFrostKey { + /// 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, }, }) } @@ -592,6 +639,23 @@ impl + Clone, NG: AddTag> Frost { Ok((total_secret_share, KeyGen.frost_key)) } + + /// Calls [`Frost::finish_keygen`] but immediately provides the the key as an [`XOnlyFrostKey`]. + /// + /// # Return value + /// + /// Your total secret share Scalar and the XOnlyFrostKey + pub fn finish_keygen_to_xonly( + &self, + KeyGen: KeyGen, + my_index: u32, + secret_shares: Vec>, + proofs_of_possession: Vec, + ) -> Result<(Scalar, XOnlyFrostKey), FinishKeyGenError> { + let (secret_share, frost_key) = + self.finish_keygen(KeyGen, my_index, secret_shares, proofs_of_possession)?; + Ok((secret_share, frost_key.into_xonly_key())) + } } /// Calculate the lagrange coefficient for participant with index x_j and other signers indexes x_ms @@ -629,7 +693,7 @@ impl + Clone, NG: NonceGen + AddTag> Frost { /// A FROST signing session pub fn start_sign_session( &self, - frost_key: &FrostKey, + frost_key: &XOnlyFrostKey, nonces: Vec<(u32, Nonce)>, message: Message, ) -> SignSession { @@ -696,7 +760,7 @@ impl + Clone, NG: NonceGen + AddTag> Frost { /// Returns a signature Scalar. pub fn sign( &self, - frost_key: &FrostKey, + frost_key: &XOnlyFrostKey, session: &SignSession, my_index: u32, secret_share: &Scalar, @@ -731,7 +795,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: &XOnlyFrostKey, session: &SignSession, index: u32, signature_share: Scalar, @@ -768,7 +832,7 @@ impl + Clone, NG: NonceGen + AddTag> Frost { /// Valid against the joint public key. pub fn combine_signature_shares( &self, - frost_key: &FrostKey, + frost_key: &XOnlyFrostKey, session: &SignSession, partial_sigs: Vec>, ) -> Signature { @@ -874,21 +938,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 xonly_frost_key = frost_key.into_xonly_key(); + // apply some xonly tweaks for tweak in [tweak1, tweak2] { if let Some(tweak) = tweak { - frost_key = frost_key.tweak(tweak).unwrap(); + xonly_frost_key = xonly_frost_key.tweak(tweak).unwrap(); } } - (secret_share, frost_key) + (secret_share, xonly_frost_key) }).unzip(); // use a boolean mask for which t participants are signers @@ -961,7 +1027,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 +1035,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 +1043,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 +1052,16 @@ 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); + + // Currently we are not doing any non-xonly tweak tests + let mut xonly_frost_key = frost_key.into_xonly_key(); + let mut xonly_frost_key2 = frost_key2.into_xonly_key(); + let mut xonly_frost_key3 = frost_key3.into_xonly_key(); + + assert_eq!(xonly_frost_key, xonly_frost_key2); + assert_eq!(xonly_frost_key, xonly_frost_key3); let use_tweak = true; let tweak = if use_tweak { @@ -1001,11 +1075,9 @@ 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!(); + xonly_frost_key = xonly_frost_key.tweak(tweak.clone()).expect("tweak worked"); + xonly_frost_key2 = xonly_frost_key2.tweak(tweak.clone()).expect("tweak worked"); + xonly_frost_key3 = xonly_frost_key3.tweak(tweak).expect("tweak worked"); let tweak = if use_tweak { Scalar::from_bytes([ @@ -1018,11 +1090,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"); + xonly_frost_key = xonly_frost_key.tweak(tweak.clone()).expect("tweak worked"); + xonly_frost_key2 = xonly_frost_key2.tweak(tweak.clone()).expect("tweak worked"); + xonly_frost_key3 = xonly_frost_key3.tweak(tweak).expect("tweak worked"); - let verification_shares_bytes: Vec<_> = frost_key + let verification_shares_bytes: Vec<_> = xonly_frost_key .verification_shares .iter() .map(|share| share.to_bytes()) @@ -1030,7 +1102,7 @@ mod test { // Create unique session IDs for these signing sessions let sid1 = [ - frost_key.joint_public_key.to_bytes().as_slice(), + xonly_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 +1110,7 @@ mod test { .concat(); let sid2 = [ - frost_key.joint_public_key.to_bytes().as_slice(), + xonly_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 +1122,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(&xonly_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( + &xonly_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(&xonly_frost_key, &session, 0, &secret_share1, nonce1); + let sig3 = frost.sign(&xonly_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(&xonly_frost_key, &session, 0, sig1)); + assert!(frost.verify_signature_share(&xonly_frost_key, &session, 2, sig3)); + let combined_sig = + frost.combine_signature_shares(&xonly_frost_key, &session, vec![sig1, sig3]); assert!(frost.schnorr.verify( - &frost_key.joint_public_key, + &xonly_frost_key.joint_public_key, Message::::plain("test", b"test"), &combined_sig )); From 7731feac29840a60813225397eb2b44282e700cf Mon Sep 17 00:00:00 2001 From: nickfarrow Date: Wed, 17 Aug 2022 17:42:02 +1000 Subject: [PATCH 35/38] use NonceKeyPair::generate --- schnorr_fun/src/frost.rs | 80 +++++++++++++++++++++------------------- 1 file changed, 43 insertions(+), 37 deletions(-) diff --git a/schnorr_fun/src/frost.rs b/schnorr_fun/src/frost.rs index 4c2fc03c..06bcd721 100644 --- a/schnorr_fun/src/frost.rs +++ b/schnorr_fun/src/frost.rs @@ -69,7 +69,6 @@ //! .collect(); //! // create a unique session ID for this signing session //! let sid = [ -//! frost_key.joint_public_key.to_bytes().as_slice(), //! verification_shares_bytes.concat().as_slice(), //! b"frost-very-unique-id".as_slice(), //! b"0".as_slice(), @@ -83,8 +82,8 @@ //! # ] //! # .concat(); //! // generate nonces for this signing session -//! let nonce = frost.gen_nonce(&secret_share, &sid); -//! # let nonce3 = frost.gen_nonce(&secret_share3, &sid3); +//! let nonce = frost.gen_nonce(&secret_share, &sid, Some(frost_key.joint_public_key), None); +//! # let nonce3 = frost.gen_nonce(&secret_share3, &sid3, Some(frost_key.joint_public_key), None); //! // share your public nonce with the other signing participant(s) //! # let recieved_nonce3 = nonce3.public(); //! // recieve public nonces from other participants with their index @@ -111,7 +110,6 @@ use crate::{Message, Schnorr, Signature, Vec}; use core::iter; use rand_core::{CryptoRng, RngCore}; use secp256kfun::{ - derive_nonce, digest::{generic_array::typenum::U32, Digest}, g, hash::{HashAdd, Tagged}, @@ -851,36 +849,35 @@ impl + Clone, NG: NonceGen + AddTag> Frost { impl + Clone, NG: NonceGen + AddTag> Frost { /// Generate nonces for secret shares /// - /// It is very important that you use a unique `sid` for this signing session and to also carefully - /// consider the implications of your choice of underlying [`NonceGen`]. + /// This method should be used carefully. + /// This calls [`NonceKeyPair::generate`] internally with the `MuSig` instance's `NonceGen`. + /// See documentation for that for more usage info. /// /// When choosing a `secret` to use, if you are generating nonces prior to KeyGen completion, /// use the static first coefficient of your polynomial. /// Otherwise you can use your secret share of the joint FROST key. /// /// The application must decide upon a unique `sid` for this FROST multisignature. - /// For example, the concatenation of: my_signing_index, joint_key, verfication_shares + /// For example, the concatenation of: my_signing_index, verfication_shares, purpose /// /// ## Return Value /// /// A NonceKeyPair comprised of secret scalars [r1, r2] and public nonces [R1, R2] - pub fn gen_nonce(&self, secret: &Scalar, sid: &[u8]) -> NonceKeyPair { - let r1 = derive_nonce!( - nonce_gen => self.schnorr.nonce_gen(), - secret => secret, - public => [ b"r1-frost", sid] - ); - let r2 = derive_nonce!( - nonce_gen => self.schnorr.nonce_gen(), - secret => secret, - public => [ b"r2-frost", sid] - ); - let R1 = g!(r1 * G).normalize(); - let R2 = g!(r2 * G).normalize(); - NonceKeyPair { - public: Nonce([R1, R2]), - secret: [r1, r2], - } + /// [`NonceKeyPair::generate`]: crate::binonce::NonceKeyPair::generate + pub fn gen_nonce( + &self, + secret: &Scalar, + session_id: &[u8], + public_key: Option>, + message: Option>, + ) -> NonceKeyPair { + NonceKeyPair::generate( + self.schnorr.nonce_gen(), + secret, + session_id, + public_key, + message, + ) } } @@ -977,7 +974,13 @@ mod test { b"frost-prop-test".as_slice(), ] .concat(); - let nonces: Vec = signer_indexes.iter().map(|i| frost.gen_nonce(&secret_shares[*i as usize], &[sid.as_slice(), [*i as u8].as_slice()].concat())).collect(); + let nonces: Vec = signer_indexes.iter().map(|i| + frost.gen_nonce( + &secret_shares[*i as usize], + &[sid.as_slice(), [*i as u8].as_slice()].concat(), + Some(frost_keys[signer_indexes[0]].joint_public_key), + None) + ).collect(); let mut recieved_nonces: Vec<_> = vec![]; for (i, nonce) in signer_indexes.iter().zip(nonces.clone()) { @@ -1102,7 +1105,6 @@ mod test { // Create unique session IDs for these signing sessions let sid1 = [ - xonly_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(), @@ -1110,28 +1112,32 @@ mod test { .concat(); let sid2 = [ - xonly_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(), ] .concat(); - let nonce1 = frost.gen_nonce(&secret_share1, &sid1); - let nonce3 = frost.gen_nonce(&secret_share3, &sid2); + let message = Message::plain("test", b"test"); + let nonce1 = frost.gen_nonce( + &secret_share1, + &sid1, + Some(xonly_frost_key.joint_public_key), + Some(message), + ); + let nonce3 = frost.gen_nonce( + &secret_share3, + &sid2, + Some(xonly_frost_key.joint_public_key), + Some(message), + ); let nonces = vec![(0, nonce1.public()), (2, nonce3.public())]; let nonces2 = vec![(0, nonce1.public()), (2, nonce3.public())]; - let session = - frost.start_sign_session(&xonly_frost_key, nonces, Message::plain("test", b"test")); - + let session = frost.start_sign_session(&xonly_frost_key, nonces, message); dbg!(&session); { - let session2 = frost.start_sign_session( - &xonly_frost_key2, - nonces2, - Message::plain("test", b"test"), - ); + let session2 = frost.start_sign_session(&xonly_frost_key2, nonces2, message); assert_eq!(session2, session); } From 4386463710a1302140e93a2de7a250d2699e581b Mon Sep 17 00:00:00 2001 From: nickfarrow Date: Wed, 17 Aug 2022 18:04:48 +1000 Subject: [PATCH 36/38] test plain tweaks and formatting --- schnorr_fun/src/frost.rs | 112 ++++++++++++++++++++++++++------------- 1 file changed, 74 insertions(+), 38 deletions(-) diff --git a/schnorr_fun/src/frost.rs b/schnorr_fun/src/frost.rs index 06bcd721..a34922c3 100644 --- a/schnorr_fun/src/frost.rs +++ b/schnorr_fun/src/frost.rs @@ -1,9 +1,9 @@ //! ## FROST multisignature scheme //! //! The FROST (Flexible Round-Optimized Schnorr Threshold) multisignature scheme allows you aggregate -//! multiple public keys into a single public key. To sign a message under this public key, a threshold t-of-n secret keys +//! multiple public keys into a single joint public key. To sign a message under this public key, a threshold t-of-n secret keys //! must use a common set of nonces to each produce a signature share. These signature shares are then combined -//! to form a signature that is valid under the aggregate key. +//! to form a signature that is valid under the joint public key. //! //! This implementation has **not yet** been made compatible with other existing FROST implementations //! (notably [secp256k1-zkp]). @@ -42,8 +42,8 @@ //! let recieved_shares = vec![shares[0].clone(), shares2[0].clone(), shares3[0].clone()]; //! # let recieved_shares3 = vec![shares[2].clone(), shares2[2].clone(), shares3[2].clone()]; //! let proofs_of_possession = vec![pop, pop2, pop3]; -//! // finish keygen by verifying the shares we recieved as well as proofs-of-possession -//! // and calulate our secret share of the joint FROST key +//! // finish keygen by verifying the shares we recieved as well as proofs-of-possession, +//! // and calulate our secret share of the joint FROST key. //! let (secret_share, frost_key) = frost //! .finish_keygen_to_xonly( //! keygen.clone(), @@ -75,7 +75,6 @@ //! ] //! .concat(); //! # let sid3 = [ -//! # frost_key.joint_public_key.to_bytes().as_slice(), //! # verification_shares_bytes.concat().as_slice(), //! # b"frost-very-unique-id".as_slice(), //! # b"2".as_slice(), @@ -89,7 +88,7 @@ //! // recieve public nonces from other participants with their index //! let nonces = vec![(0, nonce.public()), (2, recieved_nonce3)]; //! # let nonces3 = vec![(0, nonce.public()), (2, recieved_nonce3)]; -//! // start a sign session with these nonces for this message +//! // start a sign session with these nonces for a message //! let session = frost.start_sign_session(&frost_key, nonces, Message::plain("test", b"test")); //! # let session3 = frost.start_sign_session(&frost_key, nonces3, Message::plain("test", b"test")); //! // create a partial signature using our secret share and secret nonce @@ -423,7 +422,7 @@ impl FrostKey { } } -/// A [`FrostKey`] that has been converted into a [`XOnlyFrostKey`] key. +/// A [`FrostKey`] that has been converted into an [`XOnlyFrostKey`] 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 `XOnlyFrostKey` are XOnly tweaks to the joint public key. @@ -900,7 +899,11 @@ mod test { proptest! { #[test] - fn frost_prop_test((n_parties, threshold) in (3u32..8).prop_flat_map(|n| (Just(n), 3u32..=n)), tweak1 in option::of(any::>()), tweak2 in option::of(any::>())) { + fn frost_prop_test( + (n_parties, threshold) in (3u32..8).prop_flat_map(|n| (Just(n), 3u32..=n)), + tweak1 in option::of(any::>()), + tweak2 in option::of(any::>()) + ) { let frost = Frost::new(Schnorr::>::new( Deterministic::::default(), )); @@ -910,7 +913,10 @@ mod test { // create some scalar polynomial for each party let mut scalar_polys = vec![]; for i in 1..=n_parties { - let scalar_poly = (1..=threshold).map(|j| Scalar::from_non_zero_u32(NonZeroU32::new(i*j).expect("starts from 1"))).collect(); + let scalar_poly = (1..=threshold).map(|j| + Scalar::from_non_zero_u32(NonZeroU32::new(i*j) + .expect("starts from 1"))) + .collect(); scalar_polys.push(ScalarPoly::new(scalar_poly)); } let point_polys: Vec = scalar_polys.iter().map(|sp| sp.to_point_poly()).collect(); @@ -930,26 +936,29 @@ mod test { for party_index in 0..n_parties { recieved_shares.push(vec![]); for share_index in 0..n_parties { - recieved_shares[party_index as usize].push(shares_vec[share_index as usize][party_index as usize].clone()); + recieved_shares[party_index as usize] + .push(shares_vec[share_index as usize][party_index as usize].clone()); } } // finish keygen for each party let (secret_shares, frost_keys): (Vec, Vec) = (0..n_parties).map(|i| { - let (secret_share, frost_key) = frost.finish_keygen( + let (secret_share, mut frost_key) = frost.finish_keygen( KeyGen.clone(), i, recieved_shares[i as usize].clone(), proofs_of_possession.clone(), ) .unwrap(); - let mut xonly_frost_key = frost_key.into_xonly_key(); + // apply some plain tweak + if let Some(tweak) = tweak1 { + frost_key = frost_key.tweak(tweak).unwrap(); + } - // apply some xonly tweaks - for tweak in [tweak1, tweak2] { - if let Some(tweak) = tweak { - xonly_frost_key = xonly_frost_key.tweak(tweak).unwrap(); - } + let mut xonly_frost_key = frost_key.into_xonly_key(); + // apply some xonly tweak + if let Some(tweak) = tweak2 { + xonly_frost_key = xonly_frost_key.tweak(tweak).unwrap(); } (secret_share, xonly_frost_key) }).unzip(); @@ -957,10 +966,15 @@ mod test { // use a boolean mask for which t participants are signers let mut signer_mask = vec![true; threshold as usize]; signer_mask.append(&mut vec![false; (n_parties - threshold) as usize]); - // shuffle the mask for random signers (roughly shuffled and deterministic based on signers_mask_seed) + // shuffle the mask for random signers signer_mask.shuffle(&mut TestRng::deterministic_rng(RngAlgorithm::ChaCha)); - let signer_indexes: Vec<_> = signer_mask.iter().enumerate().filter(|(_, is_signer)| **is_signer).map(|(i,_)| i).collect(); + let signer_indexes: Vec<_> = signer_mask + .iter() + .enumerate() + .filter(|(_, is_signer)| **is_signer) + .map(|(i,_)| i) + .collect(); let verification_shares_bytes: Vec<_> = frost_keys[signer_indexes[0]] .verification_shares @@ -988,17 +1002,38 @@ mod test { } // Create Frost signing session - let signing_session = frost.start_sign_session(&frost_keys[signer_indexes[0]], recieved_nonces.clone(), Message::plain("test", b"test")); + let signing_session = frost.start_sign_session( + &frost_keys[signer_indexes[0]], + recieved_nonces.clone(), + Message::plain("test", b"test") + ); let mut signatures = vec![]; for i in 0..signer_indexes.len() { let signer_index = signer_indexes[i] as usize; - let session = frost.start_sign_session(&frost_keys[signer_index], recieved_nonces.clone(), Message::plain("test", b"test")); - let sig = frost.sign(&frost_keys[signer_index], &session, signer_index as u32, &secret_shares[signer_index], nonces[i].clone()); - assert!(frost.verify_signature_share(&frost_keys[signer_index], &session, signer_index as u32, sig)); + let session = frost.start_sign_session( + &frost_keys[signer_index], + recieved_nonces.clone(), + Message::plain("test", b"test") + ); + let sig = frost.sign( + &frost_keys[signer_index], + &session, signer_index as u32, + &secret_shares[signer_index], + nonces[i].clone() + ); + assert!(frost.verify_signature_share( + &frost_keys[signer_index], + &session, + signer_index as u32, + sig) + ); signatures.push(sig); } - let combined_sig = frost.combine_signature_shares(&frost_keys[signer_indexes[0] as usize], &signing_session, signatures); + let combined_sig = frost.combine_signature_shares( + &frost_keys[signer_indexes[0] as usize], + &signing_session, + signatures); assert!(frost.schnorr.verify( &frost_keys[signer_indexes[0] as usize].joint_public_key, @@ -1030,7 +1065,7 @@ mod test { let (shares3, pop3) = frost.create_shares(&KeyGen, sp3); let proofs_of_possession = vec![pop1, pop2, pop3]; - let (secret_share1, frost_key) = frost + let (secret_share1, mut frost_key) = frost .finish_keygen( KeyGen.clone(), 0, @@ -1038,7 +1073,7 @@ mod test { proofs_of_possession.clone(), ) .unwrap(); - let (_secret_share2, frost_key2) = frost + let (_secret_share2, mut frost_key2) = frost .finish_keygen( KeyGen.clone(), 1, @@ -1046,7 +1081,7 @@ mod test { proofs_of_possession.clone(), ) .unwrap(); - let (secret_share3, frost_key3) = frost + let (secret_share3, mut frost_key3) = frost .finish_keygen( KeyGen.clone(), 2, @@ -1058,14 +1093,7 @@ mod test { assert_eq!(frost_key, frost_key2); assert_eq!(frost_key, frost_key3); - // Currently we are not doing any non-xonly tweak tests - let mut xonly_frost_key = frost_key.into_xonly_key(); - let mut xonly_frost_key2 = frost_key2.into_xonly_key(); - let mut xonly_frost_key3 = frost_key3.into_xonly_key(); - - assert_eq!(xonly_frost_key, xonly_frost_key2); - assert_eq!(xonly_frost_key, xonly_frost_key3); - + // plain tweak let use_tweak = true; let tweak = if use_tweak { Scalar::from_bytes([ @@ -1078,10 +1106,18 @@ mod test { Scalar::zero() }; - xonly_frost_key = xonly_frost_key.tweak(tweak.clone()).expect("tweak worked"); - xonly_frost_key2 = xonly_frost_key2.tweak(tweak.clone()).expect("tweak worked"); - xonly_frost_key3 = xonly_frost_key3.tweak(tweak).expect("tweak worked"); + frost_key = frost_key.tweak(tweak.clone()).expect("tweak worked"); + frost_key2 = frost_key2.tweak(tweak.clone()).expect("tweak worked"); + frost_key3 = frost_key3.tweak(tweak).expect("tweak worked"); + + let mut xonly_frost_key = frost_key.into_xonly_key(); + let mut xonly_frost_key2 = frost_key2.into_xonly_key(); + let mut xonly_frost_key3 = frost_key3.into_xonly_key(); + + assert_eq!(xonly_frost_key, xonly_frost_key2); + assert_eq!(xonly_frost_key, xonly_frost_key3); + // xonly tweak let tweak = if use_tweak { Scalar::from_bytes([ 0xE8, 0xF7, 0x92, 0xFF, 0x92, 0x25, 0xA2, 0xAF, 0x01, 0x02, 0xAF, 0xFF, 0x4A, 0x9A, From 3bee20b340254689352d2b2b63b807b9546684d5 Mon Sep 17 00:00:00 2001 From: nickfarrow Date: Fri, 19 Aug 2022 01:06:52 +1000 Subject: [PATCH 37/38] pr feedback * docs updates * use message::raw * rename joint_public_key public_key * remove unecessary AddTag typing * add Frost.binding_hash * handle zero aggregate nonce like musig --- schnorr_fun/src/frost.rs | 287 +++++++++++++++++++++++---------------- 1 file changed, 169 insertions(+), 118 deletions(-) diff --git a/schnorr_fun/src/frost.rs b/schnorr_fun/src/frost.rs index a34922c3..82767607 100644 --- a/schnorr_fun/src/frost.rs +++ b/schnorr_fun/src/frost.rs @@ -1,13 +1,10 @@ //! ## FROST multisignature scheme //! //! The FROST (Flexible Round-Optimized Schnorr Threshold) multisignature scheme allows you aggregate -//! multiple public keys into a single joint public key. To sign a message under this public key, a threshold t-of-n secret keys -//! must use a common set of nonces to each produce a signature share. These signature shares are then combined -//! to form a signature that is valid under the joint public key. -//! -//! This implementation has **not yet** been made compatible with other existing FROST implementations -//! (notably [secp256k1-zkp]). +//! multiple public keys into a single FROST public key. To sign a message under this public key, a threshold t-of-n parties each +//! produce a signature share. These signature shares are then combined to form a single signature that is valid under the FROST public key. //! +//! This implementation has **not yet** been made compatible with other existing FROST implementations (notably [secp256k1-zkp]). //! For reference see the [FROST paper], the MuSig implementation in this repository, and also [Security of Multi- and Threshold Signatures]. //! //! [secp256k1-zkp]: @@ -18,16 +15,17 @@ //! //! ``` //! use schnorr_fun::{frost::{Frost, ScalarPoly}, Schnorr, Message, nonce::Deterministic, fun::marker::Public}; +//! use schnorr_fun::fun::Scalar; //! use sha2::Sha256; //! // use SHA256 with deterministic nonce generation //! let frost = Frost::new(Schnorr::>::new( //! Deterministic::::default(), //! )); -//! // to create a FROST multisig with a threshold of two, each participant creates -//! // a random secret scalar polynomial with two coefficients. -//! let scalar_poly = ScalarPoly::random(2, &mut rand::thread_rng()); -//! # let scalar_poly2 = ScalarPoly::random(2, &mut rand::thread_rng()); -//! # let scalar_poly3 = ScalarPoly::random(2, &mut rand::thread_rng()); +//! // to create a FROST multisig with a threshold of two, each participant uses a secret to generate a random +//! // secret scalar polynomial with two coefficients. +//! let scalar_poly = frost.new_scalar_poly(Scalar::random(&mut rand::thread_rng()), 2, b"frost-unique-id"); +//! # let scalar_poly2 = frost.new_scalar_poly(Scalar::random(&mut rand::thread_rng()), 2, b"frost-unique-id"); +//! # let scalar_poly3 = frost.new_scalar_poly(Scalar::random(&mut rand::thread_rng()), 2, b"frost-unique-id"); //! // share our public point poly, and recieve the point polys from other participants //! # let point_poly2 = scalar_poly2.to_point_poly(); //! # let point_poly3 = scalar_poly3.to_point_poly(); @@ -43,7 +41,7 @@ //! # let recieved_shares3 = vec![shares[2].clone(), shares2[2].clone(), shares3[2].clone()]; //! let proofs_of_possession = vec![pop, pop2, pop3]; //! // finish keygen by verifying the shares we recieved as well as proofs-of-possession, -//! // and calulate our secret share of the joint FROST key. +//! // and calulate our secret share of the FROST key. //! let (secret_share, frost_key) = frost //! .finish_keygen_to_xonly( //! keygen.clone(), @@ -60,14 +58,12 @@ //! # proofs_of_possession.clone(), //! # ) //! # .unwrap(); -//! // for signing we must have a unique session ID to derive nonces such that nonces -//! // are never reused. For gen_nonce we use all information that is publicly available. +//! // signing parties must use a common set of nonces when creating signature shares //! let verification_shares_bytes: Vec<_> = frost_key -//! .verification_shares -//! .iter() +//! .verification_shares() //! .map(|share| share.to_bytes()) //! .collect(); -//! // create a unique session ID for this signing session +//! // create a unique session id //! let sid = [ //! verification_shares_bytes.concat().as_slice(), //! b"frost-very-unique-id".as_slice(), @@ -81,26 +77,27 @@ //! # ] //! # .concat(); //! // generate nonces for this signing session -//! let nonce = frost.gen_nonce(&secret_share, &sid, Some(frost_key.joint_public_key), None); -//! # let nonce3 = frost.gen_nonce(&secret_share3, &sid3, Some(frost_key.joint_public_key), None); +//! let nonce = frost.gen_nonce(&secret_share, &sid, Some(frost_key.public_key()), None); +//! # let nonce3 = frost.gen_nonce(&secret_share3, &sid3, Some(frost_key.public_key()), None); //! // share your public nonce with the other signing participant(s) //! # let recieved_nonce3 = nonce3.public(); //! // recieve public nonces from other participants with their index //! let nonces = vec![(0, nonce.public()), (2, recieved_nonce3)]; //! # let nonces3 = vec![(0, nonce.public()), (2, recieved_nonce3)]; +//! let message = Message::plain("my-app", b"chancellor on brink of second bailout for banks"); //! // start a sign session with these nonces for a message -//! let session = frost.start_sign_session(&frost_key, nonces, Message::plain("test", b"test")); -//! # let session3 = frost.start_sign_session(&frost_key, nonces3, Message::plain("test", b"test")); +//! let session = frost.start_sign_session(&frost_key, nonces, message); +//! # let session3 = frost.start_sign_session(&frost_key, nonces3, message); //! // create a partial signature using our secret share and secret nonce //! let sig = frost.sign(&frost_key, &session, 0, &secret_share, nonce); //! # let sig3 = frost.sign(&frost_key, &session3, 2, &secret_share3, nonce3); //! // recieve partial signature(s) from other participant(s) and verify //! assert!(frost.verify_signature_share(&frost_key, &session, 2, sig3)); -//! // combine signature shares into a single signature that is valid under the joint key +//! // combine signature shares into a single signature that is valid under the FROST key //! let combined_sig = frost.combine_signature_shares(&frost_key, &session, vec![sig, sig3]); //! assert!(frost.schnorr.verify( -//! &frost_key.joint_public_key, -//! Message::::plain("test", b"test"), +//! &frost_key.public_key(), +//! message, //! &combined_sig //! )); //! ``` @@ -109,6 +106,7 @@ use crate::{Message, Schnorr, Signature, Vec}; use core::iter; use rand_core::{CryptoRng, RngCore}; use secp256kfun::{ + derive_nonce, digest::{generic_array::typenum::U32, Digest}, g, hash::{HashAdd, Tagged}, @@ -122,9 +120,11 @@ use std::collections::BTreeMap; /// H: hash for challenges and creating a keygen_id /// NG: hash for nonce generation #[derive(Clone)] -pub struct Frost { +pub struct Frost { /// The instance of the Schnorr signature scheme. pub schnorr: Schnorr, + /// The hash used in the binding coefficient of the frost key. + binding_hash: H, /// The hash used to generate the keygen_id. keygen_id_hash: H, } @@ -134,6 +134,7 @@ impl Frost { pub fn new(schnorr: Schnorr) -> Self { Self { schnorr: schnorr.clone(), + binding_hash: H::default().tagged(b"frost/binding"), keygen_id_hash: H::default().tagged(b"frost/keygenid"), } } @@ -171,8 +172,6 @@ impl ScalarPoly { /// Create a scalar polynomial where the first coefficient is a specified secret and /// the remaining coefficients are random. - /// - /// This will be used for creating secret polynomials with known & reproducable secrets. pub fn random_using_secret( n_coefficients: u32, secret: Scalar, @@ -216,7 +215,7 @@ pub struct PointPoly( serialize = "Point: serde::Serialize" )) )] - Vec>, + pub Vec>, ); impl PointPoly { @@ -289,9 +288,9 @@ pub enum NewKeyGenError { PolyDifferentLength(usize), /// Number of parties is less than the length of polynomials specifying the threshold. NotEnoughParties, - /// Frost key is zero. This should be impossible, likely has been maliciously chosen. + /// Frost key is zero. Computationally unreachable, likely has been maliciously chosen. ZeroFrostKey, - /// Verification share is zero. This should be impossible, likely has been maliciously chosen. + /// Verification share is zero. Computationally unreachable, likely has been maliciously chosen. ZeroVerificationShare, } @@ -301,8 +300,8 @@ impl core::fmt::Display for NewKeyGenError { match self { PolyDifferentLength(i) => write!(f, "polynomial commitment from party at index {} was a different length", i), NotEnoughParties => write!(f, "the number of parties was less than the threshold"), - ZeroFrostKey => write!(f, "The joint FROST key was zero. This should be impossible, one party is acting maliciously."), - ZeroVerificationShare => write!(f, "Zero verification share. This should be impossible, one party is acting maliciously."), + ZeroFrostKey => write!(f, "The frost public key was zero. Computationally unreachable, one party is acting maliciously."), + ZeroVerificationShare => write!(f, "Zero verification share. Computationally unreachable, one party is acting maliciously."), } } } @@ -349,46 +348,55 @@ impl std::error::Error for FinishKeyGenError {} serde(crate = "serde_crate") )] pub struct FrostKey { - /// The joint public key of the FROST multisignature. - pub joint_public_key: Point, + /// The joint public key of the frost multisignature. + public_key: Point, /// Everyone else's point polynomial evaluated at your index, used in partial signature validation. - pub verification_shares: Vec, + 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. + threshold: u32, + /// The tweak applied to this frost key, tracks the aggregate tweak. tweak: Scalar, } impl FrostKey { - /// The joint public key of the FROST multisignature + /// The joint public key of the frost multisignature /// /// ## Return value /// - /// A point (normalised to have an even Y coordinate). + /// A point pub fn public_key(&self) -> Point { - self.joint_public_key + self.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. + /// An iterator over the verification shares of each party in the key. /// - /// Tweak the joint FROST public key with a scalar so that the resulting key is equal to the + /// ## Return value + /// + /// An iterator over verification share points + pub fn verification_shares(&self) -> impl Iterator + '_ { + self.verification_shares.iter().map(|point| *point) + } + + /// Apply a plain tweak to the frost public key. + /// This is how you derive child public keys from a frost public key using BIP32. + /// + /// Tweak the 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. /// /// ## Return value /// - /// Returns a new FrostKey with the same parties but a different joint public key. + /// Returns a new [`FrostKey`] with the same parties but a different frost 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 { - let joint_public_key = g!(self.joint_public_key + tweak * G) + let public_key = g!(self.public_key + tweak * G) .normalize() .mark::()?; let tweak = s!(self.tweak + tweak).mark::(); Some(FrostKey { - joint_public_key, + public_key, verification_shares: self.verification_shares.clone(), threshold: self.threshold.clone(), tweak, @@ -399,11 +407,11 @@ impl FrostKey { /// /// This is the BIP340 compatiple version of the key which you can put in a segwitv1 pub fn into_xonly_key(self) -> XOnlyFrostKey { - let (joint_public_key, needs_negation) = self.joint_public_key.into_point_with_even_y(); + let (public_key, needs_negation) = self.public_key.into_point_with_even_y(); let mut tweak = self.tweak; tweak.conditional_negate(needs_negation); XOnlyFrostKey { - joint_public_key, + public_key, verification_shares: self.verification_shares, threshold: self.threshold, tweak, @@ -416,7 +424,7 @@ impl FrostKey { self.threshold } - /// The total number of signers in this FROST multisignature. + /// The total number of signers in this frost multisignature. pub fn n_signers(&self) -> u32 { self.verification_shares.len() as u32 } @@ -425,7 +433,7 @@ impl FrostKey { /// A [`FrostKey`] that has been converted into an [`XOnlyFrostKey`] 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 `XOnlyFrostKey` are XOnly tweaks to the joint public key. +/// Tweaks applied to a `XOnlyFrostKey` are XOnly tweaks to the frost public key. /// BIP340: #[cfg_attr( feature = "serde", @@ -434,33 +442,51 @@ impl FrostKey { )] #[derive(Clone, Debug, PartialEq)] pub struct XOnlyFrostKey { - /// The BIP340 public key of the FROST multisignature. - pub joint_public_key: Point, + /// The BIP340 public key of the frost multisignature. + public_key: Point, /// Everyone else's point polynomial evaluated at your index, used in partial signature validation. - pub verification_shares: Vec, + 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. + threshold: u32, + /// The 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 XOnlyFrostKey { - /// Applies an "XOnly" tweak to the joint public key. - /// This is how you embed a taproot commitment into a joint public key + /// The joint public key of the frost multisignature + /// + /// ## Return value + /// + /// A point (normalised to have an even Y coordinate). + pub fn public_key(&self) -> Point { + self.public_key + } + + /// An iterator over the verification shares of each party in the key. + /// + /// ## Return value + /// + /// An iterator over verification share points + pub fn verification_shares(&self) -> impl Iterator + '_ { + self.verification_shares.iter().map(|point| *point) + } + + /// Applies an "XOnly" tweak to the FROST public key. + /// This is how you embed a taproot commitment into a frost public key /// - /// Tweak the joint FROST public key with a scalar so that the resulting key is equal to the + /// Tweak the 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. + /// Returns a new [`FrostKey`] with the same parties but a different frost 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) + let (new_public_key, needs_negation) = g!(self.public_key + tweak * G) .normalize() .mark::()? .into_point_with_even_y(); @@ -471,14 +497,37 @@ impl XOnlyFrostKey { Some(Self { needs_negation, tweak: new_tweak, - joint_public_key: new_joint_public_key, + public_key: new_public_key, verification_shares: self.verification_shares, threshold: self.threshold, }) } } -impl + Clone, NG: AddTag + NonceGen> Frost { +impl + Clone, NG: NonceGen> Frost { + /// Create a scalar polynomial where the first coefficient is a specified secret + /// and the remaining coefficients are generated using the internal nonce_gen. + /// + /// Depending on your choice of [`NonceGen`], this can be deterministic based + /// on the secret and session_id. Be careful not to reuse scalar polys! + /// + /// ## Return value + /// + /// Returns a scalar polynomial + pub fn new_scalar_poly(&self, secret: Scalar, threshold: u32, session_id: &[u8]) -> ScalarPoly { + let mut coeffs = vec![secret.clone()]; + for _ in 1..threshold { + let schnorr = self.schnorr.nonce_gen(); + let nonce = derive_nonce!( + nonce_gen => schnorr, + secret => secret, + public => [session_id] + ); + coeffs.push(nonce) + } + ScalarPoly(coeffs) + } + /// Create secret shares and our proof-of-possession to be shared with other participants. /// /// Secret shares are created for every other participant by evaluating our secret polynomial @@ -497,10 +546,9 @@ impl + Clone, NG: AddTag + NonceGen> Frost { scalar_poly: ScalarPoly, ) -> (Vec>, Signature) { let key_pair = self.schnorr.new_keypair(scalar_poly.0[0].clone()); - let pop = self.schnorr.sign( - &key_pair, - Message::::plain("frost-pop", &KeyGen.keygen_id), - ); + let pop = self + .schnorr + .sign(&key_pair, Message::::raw(&KeyGen.keygen_id)); let shares = (1..=KeyGen.point_polys.len()) .map(|i| scalar_poly.eval(i as u32)) @@ -510,7 +558,7 @@ impl + Clone, NG: AddTag + NonceGen> Frost { } } -impl + Clone, NG: AddTag> Frost { +impl + Clone, NG> Frost { /// Verify a proof of possession against a participant's committed point polynomial /// /// ## Return value @@ -521,22 +569,19 @@ impl + Clone, NG: AddTag> Frost { self.schnorr.verify( &even_poly_point, - Message::::plain("frost-pop", &KeyGen.keygen_id), + Message::::raw(&KeyGen.keygen_id), &pop, ) } -} -impl + Clone, NG: AddTag> Frost { - /// Collect all the public polynomials into a KeyGen session with a FrostKey. - /// - /// Takes a vector of point polynomials to use for this FrostKey + /// Collect all the public polynomials into a [`KeyGen`] session with a [`FrostKey`]. /// + /// Takes a vector of point polynomials to use for this [`FrostKey`]. /// Also prepares a vector of verification shares for later. /// /// ## Return value /// - /// Returns a KeyGen containing a FrostKey + /// Returns a [`KeyGen`] containing a [`FrostKey`] pub fn new_keygen(&self, point_polys: Vec) -> Result { let len_first_poly = point_polys[0].poly_len(); { @@ -555,7 +600,7 @@ impl + Clone, NG: AddTag> Frost { } let joint_poly = PointPoly::combine(point_polys.clone().into_iter()); - let joint_public_key = joint_poly.0[0] + let public_key = joint_poly.0[0] .mark::() .ok_or(NewKeyGenError::ZeroFrostKey)?; @@ -579,7 +624,7 @@ impl + Clone, NG: AddTag> Frost { keygen_id, frost_key: FrostKey { verification_shares, - joint_public_key, + public_key, threshold: joint_poly.poly_len() as u32, tweak: Scalar::zero().mark::(), }, @@ -596,7 +641,7 @@ impl + Clone, NG: AddTag> Frost { /// /// # Return value /// - /// Your total secret share Scalar and the FrostKey + /// Your total secret share Scalar and the [`FrostKey`] pub fn finish_keygen( &self, KeyGen: KeyGen, @@ -682,8 +727,11 @@ pub struct SignSession { nonces: BTreeMap, } -impl + Clone, NG: NonceGen + AddTag> Frost { - /// Start a FROST signing session +impl + Clone, NG> Frost { + /// Start a FROST signing session. + /// + /// Signing participants must use a common set of nonces to each produce a + /// signature share. See [`Frost::gen_nonce`] and [`NonceKeyPair::generate`]. /// /// ## Return value /// @@ -697,38 +745,41 @@ impl + Clone, NG: NonceGen + AddTag> Frost { let mut nonce_map: BTreeMap<_, _> = nonces.into_iter().map(|(i, nonce)| (i, nonce)).collect(); - let agg_nonce_points: [Point; 2] = nonce_map - .iter() - .fold([Point::zero().mark::(); 2], |acc, (_, nonce)| { - [ - g!({ acc[0] } + { nonce.0[0] }), - g!({ acc[1] } + { nonce.0[1] }), - ] - }) - .iter() - .map(|agg_nonce| { - agg_nonce - .normalize() - .mark::() - .expect("aggregate nonce should be non-zero") - }) - .collect::>() - .try_into() - .expect("there are only R1 and R2, collecting cant fail"); + let agg_nonce_jac: [Point; 2] = + nonce_map + .iter() + .fold([Point::zero().mark::(); 2], |acc, (_, nonce)| { + [ + g!({ acc[0] } + { nonce.0[0] }), + g!({ acc[1] } + { nonce.0[1] }), + ] + }); + let agg_nonce_points = [ + agg_nonce_jac[0] + .normalize() + .mark::() + .unwrap_or_else(|| { + // Like in musig spec, if the final nonce is zero we set to the generator + G.clone().mark::() + }), + agg_nonce_jac[1] + .normalize() + .mark::() + .unwrap_or_else(|| G.clone().mark::()), + ]; let binding_coeff = Scalar::from_hash( - self.schnorr - .challenge_hash() + self.binding_hash .clone() .add(agg_nonce_points[0]) .add(agg_nonce_points[1]) - .add(frost_key.joint_public_key) + .add(frost_key.public_key()) .add(message), ); let (agg_nonce, nonces_need_negation) = g!({ agg_nonce_points[0] } + binding_coeff * { agg_nonce_points[1] }) .normalize() - .expect_nonzero("impossibly unlikely, input is a hash") + .expect_nonzero("computationally unreachable, input is a hash") .into_point_with_even_y(); for (_, nonce) in &mut nonce_map { @@ -737,7 +788,7 @@ impl + Clone, NG: NonceGen + AddTag> Frost { let challenge = self.schnorr.challenge( agg_nonce.to_xonly(), - frost_key.joint_public_key.to_xonly(), + frost_key.public_key().to_xonly(), message, ); @@ -750,7 +801,7 @@ impl + Clone, NG: NonceGen + AddTag> Frost { } } - /// Generates a partial signature share under the FROST key using a secret share. + /// Generates a partial signature share under the frost key using a secret share. /// /// ## Return value /// @@ -761,7 +812,7 @@ impl + Clone, NG: NonceGen + AddTag> Frost { session: &SignSession, my_index: u32, secret_share: &Scalar, - secret_nonces: NonceKeyPair, + secret_nonce: NonceKeyPair, ) -> Scalar { let mut lambda = lagrange_lambda( my_index as u32 + 1, @@ -773,7 +824,7 @@ impl + Clone, NG: NonceGen + AddTag> Frost { .collect::>(), ); lambda.conditional_negate(frost_key.needs_negation); - let [mut r1, mut r2] = secret_nonces.secret; + let [mut r1, mut r2] = secret_nonce.secret; r1.conditional_negate(session.nonces_need_negation); r2.conditional_negate(session.nonces_need_negation); @@ -810,7 +861,7 @@ impl + Clone, NG: NonceGen + AddTag> Frost { lambda.conditional_negate(frost_key.needs_negation); let c = &session.challenge; let b = &session.binding_coeff; - let X = frost_key.verification_shares[index as usize]; + let X = frost_key.verification_shares().nth(index as usize).unwrap(); let [ref R1, ref R2] = session .nonces .get(&(index as u32)) @@ -826,7 +877,7 @@ impl + Clone, NG: NonceGen + AddTag> Frost { /// ## Return value /// /// Returns a combined schnorr [`Signature`] for the message. - /// Valid against the joint public key. + /// Valid against the frost public key. pub fn combine_signature_shares( &self, frost_key: &XOnlyFrostKey, @@ -845,18 +896,18 @@ impl + Clone, NG: NonceGen + AddTag> Frost { } } -impl + Clone, NG: NonceGen + AddTag> Frost { +impl + Clone, NG: NonceGen> Frost { /// Generate nonces for secret shares /// /// This method should be used carefully. - /// This calls [`NonceKeyPair::generate`] internally with the `MuSig` instance's `NonceGen`. + /// This calls [`NonceKeyPair::generate`] internally with the frost instance's [`NonceGen`]. /// See documentation for that for more usage info. /// - /// When choosing a `secret` to use, if you are generating nonces prior to KeyGen completion, + /// When choosing a `secret` to use, if you are generating nonces prior to [`KeyGen`] completion, /// use the static first coefficient of your polynomial. - /// Otherwise you can use your secret share of the joint FROST key. + /// Otherwise you can use your secret share of the frost key. /// - /// The application must decide upon a unique `sid` for this FROST multisignature. + /// The application must decide upon a unique `sid` for this frost multisignature. /// For example, the concatenation of: my_signing_index, verfication_shares, purpose /// /// ## Return Value @@ -983,7 +1034,7 @@ mod test { .collect(); let sid = [ - frost_keys[signer_indexes[0]].joint_public_key.to_bytes().as_slice(), + frost_keys[signer_indexes[0]].public_key().to_bytes().as_slice(), verification_shares_bytes.concat().as_slice(), b"frost-prop-test".as_slice(), ] @@ -992,7 +1043,7 @@ mod test { frost.gen_nonce( &secret_shares[*i as usize], &[sid.as_slice(), [*i as u8].as_slice()].concat(), - Some(frost_keys[signer_indexes[0]].joint_public_key), + Some(frost_keys[signer_indexes[0]].public_key()), None) ).collect(); @@ -1036,7 +1087,7 @@ mod test { signatures); assert!(frost.schnorr.verify( - &frost_keys[signer_indexes[0] as usize].joint_public_key, + &frost_keys[signer_indexes[0] as usize].public_key(), Message::::plain("test", b"test"), &combined_sig )); @@ -1158,13 +1209,13 @@ mod test { let nonce1 = frost.gen_nonce( &secret_share1, &sid1, - Some(xonly_frost_key.joint_public_key), + Some(xonly_frost_key.public_key()), Some(message), ); let nonce3 = frost.gen_nonce( &secret_share3, &sid2, - Some(xonly_frost_key.joint_public_key), + Some(xonly_frost_key.public_key()), Some(message), ); let nonces = vec![(0, nonce1.public()), (2, nonce3.public())]; @@ -1188,7 +1239,7 @@ mod test { frost.combine_signature_shares(&xonly_frost_key, &session, vec![sig1, sig3]); assert!(frost.schnorr.verify( - &xonly_frost_key.joint_public_key, + &xonly_frost_key.public_key(), Message::::plain("test", b"test"), &combined_sig )); From 81b425f24f1201244b1e4e914df5dca48924aa74 Mon Sep 17 00:00:00 2001 From: LLFourn Date: Tue, 6 Sep 2022 16:41:08 +0800 Subject: [PATCH 38/38] lloyd fixups --- schnorr_fun/src/schnorr.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/schnorr_fun/src/schnorr.rs b/schnorr_fun/src/schnorr.rs index 85143490..885e00c5 100644 --- a/schnorr_fun/src/schnorr.rs +++ b/schnorr_fun/src/schnorr.rs @@ -27,7 +27,7 @@ pub struct Schnorr { /// [`NonceGen`]: crate::nonce::NonceGen nonce_gen: NG, /// The challenge hash - pub challenge_hash: CH, + challenge_hash: CH, } impl + Tagged> Schnorr {