diff --git a/Cargo.toml b/Cargo.toml index a7f90949..8305e0a1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "wsts" -version = "9.2.0" +version = "10.0.0" edition = "2021" authors = ["Joey Yandle "] license = "Apache-2.0" diff --git a/src/common.rs b/src/common.rs index 0b6834a3..248dc316 100644 --- a/src/common.rs +++ b/src/common.rs @@ -1,6 +1,6 @@ use core::{ fmt::{Debug, Display, Formatter, Result as FmtResult}, - ops::Add, + ops::{Add, AddAssign, Index, Mul, MulAssign}, }; use hashbrown::HashMap; use num_traits::{One, Zero}; @@ -22,6 +22,122 @@ use crate::{ /// A merkle root is a 256 bit hash pub type MerkleRoot = [u8; 32]; +/// A trait that allows us to create random instances of implementors +pub trait Random { + /// Create a new instance with random data + fn fill(rng: &mut RNG) -> Self; +} + +impl Random for Point { + fn fill(rng: &mut RNG) -> Self { + Point::from(Scalar::random(rng)) + } +} + +impl Random for Scalar { + fn fill(rng: &mut RNG) -> Self { + Scalar::random(rng) + } +} + +/// A Polynomial where the parameters are not necessarily the same type as the args +#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)] +pub struct Polynomial { + /// parameters for the polynomial + pub params: Vec, + _x: std::marker::PhantomData, +} + +impl Polynomial +where + Param: Clone + Zero + Random + Add + AddAssign<>::Output>, + Arg: Clone + One + Mul + MulAssign, +{ + /// construct new random polynomial of the specified degree + pub fn random(n: u32, rng: &mut RNG) -> Self { + let params = (0..n + 1).map(|_| Param::fill(rng)).collect::>(); + Self { + params, + _x: std::marker::PhantomData, + } + } + + /// construct new polynomial from passed params + pub fn new(params: Vec) -> Self { + Self { + params, + _x: std::marker::PhantomData, + } + } + /// evaluate the polynomial with the passed arg + pub fn eval(&self, x: Arg) -> Param { + let mut pow = Arg::one(); + let mut ret = Param::zero(); + for i in 0..self.params.len() { + ret += pow.clone() * self.params[i].clone(); + pow *= x.clone(); + } + ret + } + + /// length of the params + pub fn len(&self) -> usize { + self.params.len() + } + + /// is the length of the polynomial zero + pub fn is_empty(&self) -> bool { + self.params.is_empty() + } +} + +impl Index for Polynomial { + type Output = Param; + fn index(&self, i: usize) -> &Param { + &self.params[i] + } +} + +impl Mul for &Polynomial +where + Param: Clone + + Zero + + Random + + Add + + AddAssign<>::Output> + + Mul, + Arg: Clone + One + Mul + Mul + MulAssign, + Operand: Clone, + OpResult: Clone + Zero + Random + Add + AddAssign<>::Output>, + Vec: FromIterator<>::Output>, +{ + type Output = Polynomial; + fn mul(self, x: Operand) -> Self::Output { + let params: Vec = self.params.iter().map(|p| p.clone() * x.clone()).collect(); + Polynomial::new(params) + } +} + +impl Mul for Polynomial +where + Param: Clone + + Zero + + Random + + Add + + AddAssign<>::Output> + + Mul, + Arg: Clone + One + Mul + Mul + MulAssign, + Operand: Clone, + OpResult: Clone + Zero + Random + Add + AddAssign<>::Output>, + Vec: FromIterator<>::Output>, +{ + type Output = Polynomial; + fn mul(self, x: Operand) -> Self::Output { + let params: Vec = self.params.iter().map(|p| p.clone() * x.clone()).collect(); + Polynomial::new(params) + } +} + #[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] /// A commitment to a polynonial, with a Schnorr proof of ownership bound to the ID pub struct PolyCommitment { @@ -161,19 +277,20 @@ impl Signature { #[allow(non_snake_case)] /// A Chaum-Pedersen proof that (G, A=a*G, B=b*G, K=(a*b)*G) is a DH tuple +/// It consists of two Schnorr proofs. The first shows knowledge of `a`, and the second is just the first multiplied by `b` #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] pub struct TupleProof { /// R = r*G for a random scalar r pub R: Point, - /// rB = r*B + /// rB = r*B = b*R pub rB: Point, - /// z = r + a*s where s = H(G,A,B,K,R) as per Fiat-Shamir + /// z = r + c*a where c = H(G,A,B,K,R) as per Fiat-Shamir pub z: Scalar, } impl TupleProof { #[allow(non_snake_case)] - /// Construct a Chaum-Pedersen proof that (G, A, B, K) is a DH tuple + /// Construct a Chaum-Pedersen proof that (A, B, K) is a DH tuple pub fn new( a: &Scalar, A: &Point, @@ -183,21 +300,21 @@ impl TupleProof { ) -> Self { let r = Scalar::random(rng); let R = r * G; - let s = Self::challenge(A, B, K, &R); + let c = Self::challenge(A, B, K, &R); Self { R, rB: r * B, - z: r + a * s, + z: r + c * a, } } #[allow(non_snake_case)] /// Verify the proof using the transcript and public parameters pub fn verify(&self, A: &Point, B: &Point, K: &Point) -> bool { - let s = Self::challenge(A, B, K, &self.R); + let c = Self::challenge(A, B, K, &self.R); - (self.z * G == self.R + s * A) && (self.z * B == self.rB + s * K) + (self.z * G == self.R + c * A) && (self.z * B == self.rB + c * K) } #[allow(non_snake_case)] @@ -321,13 +438,65 @@ pub mod test_helpers { #[cfg(test)] pub mod test { + use num_traits::Zero; use rand_core::OsRng; use crate::{ common::TupleProof, - curve::{point::Point, scalar::Scalar}, + compute, + curve::{ + point::{Point, G}, + scalar::Scalar, + }, }; + #[test] + #[allow(non_snake_case)] + fn polynomial() { + let mut rng = OsRng; + let n = 16u32; + + let poly = super::Polynomial::::random(n - 1, &mut rng); + let x = Scalar::from(8); + let y = poly.eval(x); + let mut z = Scalar::zero(); + let mut pow = Scalar::from(1); + for i in 0..poly.params.len() { + z += pow * poly.params[i]; + pow *= x; + } + assert_eq!(y, z); + + let public_params = poly.params.iter().map(|p| p * G).collect::>(); + let public_poly: super::Polynomial = + super::Polynomial::new(public_params.clone()); + let a = poly.eval(x); + let b = public_poly.eval(x); + assert_eq!(a * G, b); + + let mul_poly = poly * G; + let b = mul_poly.eval(x); + assert_eq!(a * G, b); + + let b = compute::poly(&x, &public_params); + assert_eq!(a * G, b.unwrap()); + + let poly = super::Polynomial::random(n - 1, &mut rng); + let params = poly.params.clone(); + let y = poly.eval(x); + let mut z = Point::zero(); + let mut pow = Scalar::from(1); + for i in 0..poly.params.len() { + z += pow * poly.params[i]; + pow *= x; + } + assert_eq!(y, z); + + let a = poly.eval(x); + let b = compute::poly(&x, ¶ms); + assert_eq!(a, b.unwrap()); + } + #[test] #[allow(non_snake_case)] fn tuple_proof() { diff --git a/src/compute.rs b/src/compute.rs index de4595a2..b11632fa 100644 --- a/src/compute.rs +++ b/src/compute.rs @@ -169,3 +169,30 @@ pub fn merkle_root(data: &[u8]) -> [u8; 32] { hasher.finalize().into() } + +#[cfg(test)] +pub mod test { + use rand_core::OsRng; + + use crate::{ + common::Polynomial, + compute, + curve::{point::G, scalar::Scalar}, + }; + + #[test] + #[allow(non_snake_case)] + fn poly() { + let mut rng = OsRng; + let n = 16u32; + + let private_poly = Polynomial::::random(n - 1, &mut rng); + let public_poly = &private_poly * G; + + let x = Scalar::from(8); + let a = private_poly.eval(x); + let b = compute::poly(&x, &public_poly.params); + + assert_eq!(a * G, b.unwrap()); + } +} diff --git a/src/net.rs b/src/net.rs index 794c3f7b..abc87953 100644 --- a/src/net.rs +++ b/src/net.rs @@ -125,12 +125,16 @@ impl Signable for Message { pub struct DkgBegin { /// DKG round ID pub dkg_id: u64, + /// Keep the constant factor so DKG will produce the same key + pub keep_constant: bool, } impl Signable for DkgBegin { fn hash(&self, hasher: &mut Sha256) { + let keep_constant = if self.keep_constant { [1u8] } else { [0u8] }; hasher.update("DKG_BEGIN".as_bytes()); hasher.update(self.dkg_id.to_be_bytes()); + hasher.update(keep_constant); } } @@ -629,7 +633,10 @@ mod test { #[test] fn dkg_begin_verify_msg() { let test_config = TestConfig::default(); - let dkg_begin = DkgBegin { dkg_id: 0 }; + let dkg_begin = DkgBegin { + dkg_id: 0, + keep_constant: false, + }; let dkg_private_begin = DkgPrivateBegin { dkg_id: 0, key_ids: Default::default(), diff --git a/src/state_machine/coordinator/fire.rs b/src/state_machine/coordinator/fire.rs index 93e04a69..25fe350b 100644 --- a/src/state_machine/coordinator/fire.rs +++ b/src/state_machine/coordinator/fire.rs @@ -227,7 +227,7 @@ impl Coordinator { // that we start the next round at the correct id. (Do this rather // than overwriting afterwards to ensure logging is accurate) self.current_dkg_id = dkg_begin.dkg_id.wrapping_sub(1); - let packet = self.start_dkg_round()?; + let packet = self.start_dkg_round(dkg_begin.keep_constant)?; return Ok((Some(packet), None)); } else if let Message::NonceRequest(nonce_request) = &packet.msg { if self.current_sign_id >= nonce_request.sign_id { @@ -249,8 +249,7 @@ impl Coordinator { return Ok((None, None)); } State::DkgPublicDistribute => { - let packet = self.start_public_shares()?; - return Ok((Some(packet), None)); + return Err(Error::BadStateChange("should never be in ephemeral DkgPublicDistribute during process_message loop".to_string())); } State::DkgPublicGather => { self.gather_public_shares(packet)?; @@ -353,7 +352,7 @@ impl Coordinator { } /// Ask signers to send DKG public shares - pub fn start_public_shares(&mut self) -> Result { + pub fn start_public_shares(&mut self, keep_constant: bool) -> Result { self.dkg_public_shares.clear(); self.party_polynomials.clear(); self.dkg_wait_signer_ids = (0..self.config.num_signers).collect(); @@ -363,6 +362,7 @@ impl Coordinator { ); let dkg_begin = DkgBegin { dkg_id: self.current_dkg_id, + keep_constant, }; let dkg_begin_packet = Packet { sig: dkg_begin @@ -1196,11 +1196,11 @@ impl CoordinatorTrait for Coordinator { } /// Start a DKG round - fn start_dkg_round(&mut self) -> Result { + fn start_dkg_round(&mut self, keep_constant: bool) -> Result { self.current_dkg_id = self.current_dkg_id.wrapping_add(1); info!("Starting DKG round {}", self.current_dkg_id); self.move_to(State::DkgPublicDistribute)?; - self.start_public_shares() + self.start_public_shares(keep_constant) } /// Start a signing round @@ -1320,7 +1320,7 @@ pub mod test { coordinator.state = State::DkgPublicDistribute; // Must be in this state before calling start public shares - let result = coordinator.start_public_shares().unwrap(); + let result = coordinator.start_public_shares(false).unwrap(); assert!(matches!(result.msg, Message::DkgBegin(_))); assert_eq!(coordinator.get_state(), State::DkgPublicGather); @@ -1378,7 +1378,11 @@ pub mod test { setup::, SignerType>(num_signers, keys_per_signer); // We have started a dkg round - let message = coordinators.first_mut().unwrap().start_dkg_round().unwrap(); + let message = coordinators + .first_mut() + .unwrap() + .start_dkg_round(false) + .unwrap(); assert!(coordinators.first().unwrap().aggregate_public_key.is_none()); assert_eq!(coordinators.first().unwrap().state, State::DkgPublicGather); @@ -1428,6 +1432,88 @@ pub mod test { (coordinators, signers) } + #[test] + fn all_signers_dkg_keep_constant_v1() { + all_signers_dkg_keep_constant::(5, 2); + } + + #[test] + fn all_signers_dkg_keep_constant_v2() { + all_signers_dkg_keep_constant::(5, 2); + } + + fn all_signers_dkg_keep_constant( + num_signers: u32, + keys_per_signer: u32, + ) { + let (mut coordinators, mut signers) = + setup::, SignerType>(num_signers, keys_per_signer); + + let key0 = all_signers_rerun_dkg(&mut coordinators, &mut signers, false); + let key1 = all_signers_rerun_dkg(&mut coordinators, &mut signers, false); + let key2 = all_signers_rerun_dkg(&mut coordinators, &mut signers, true); + + assert!(key0 != key1); + assert_eq!(key1, key2); + } + + fn all_signers_rerun_dkg( + coordinators: &mut Vec>, + signers: &mut Vec>, + keep_constant: bool, + ) -> Point { + // We have started a dkg round + let message = coordinators + .first_mut() + .unwrap() + .start_dkg_round(keep_constant) + .unwrap(); + assert_eq!(coordinators.first().unwrap().state, State::DkgPublicGather); + + // Send the DKG Begin message to all signers and gather responses by sharing with all other signers and coordinators + let (outbound_messages, operation_results) = + feedback_messages(coordinators, signers, &[message]); + assert!(operation_results.is_empty()); + + // Successfully got an Aggregate Public Key... + assert_eq!(outbound_messages.len(), 1); + match &outbound_messages[0].msg { + Message::DkgPrivateBegin(_) => {} + _ => { + panic!("Expected DkgPrivateBegin message"); + } + } + // Send the DKG Private Begin message to all signers and share their responses with the coordinators and signers + let (outbound_messages, operation_results) = + feedback_messages(coordinators, signers, &outbound_messages); + assert_eq!(operation_results.len(), 0); + assert_eq!(outbound_messages.len(), 1); + match &outbound_messages[0].msg { + Message::DkgEndBegin(_) => {} + _ => { + panic!("Expected DkgEndBegin message"); + } + } + + // Send the DkgEndBegin message to all signers and share their responses with the coordinators and signers + let (outbound_messages, operation_results) = + feedback_messages(coordinators, signers, &outbound_messages); + assert_eq!(outbound_messages.len(), 0); + assert_eq!(operation_results.len(), 1); + match operation_results[0] { + OperationResult::Dkg(point) => { + assert_ne!(point, Point::default()); + for coordinator in coordinators.iter() { + assert_eq!(coordinator.get_aggregate_public_key(), Some(point)); + assert_eq!(coordinator.get_state(), State::Idle); + } + } + _ => panic!("Expected Dkg Operation result"), + } + + coordinators[0].get_aggregate_public_key().unwrap() + } + #[test] fn missing_public_keys_dkg_v1() { missing_public_keys_dkg::(10, 1); @@ -1456,7 +1542,11 @@ pub mod test { ); // Start a DKG round where we will not allow all signers to recv DkgBegin, so they will not respond with DkgPublicShares - let message = coordinators.first_mut().unwrap().start_dkg_round().unwrap(); + let message = coordinators + .first_mut() + .unwrap() + .start_dkg_round(false) + .unwrap(); assert!(coordinators.first().unwrap().aggregate_public_key.is_none()); assert_eq!(coordinators.first().unwrap().state, State::DkgPublicGather); @@ -1537,7 +1627,11 @@ pub mod test { ); // Start a DKG round where we will not allow all signers to recv DkgBegin, so they will not respond with DkgPublicShares - let message = coordinators.first_mut().unwrap().start_dkg_round().unwrap(); + let message = coordinators + .first_mut() + .unwrap() + .start_dkg_round(false) + .unwrap(); assert!(coordinators.first().unwrap().aggregate_public_key.is_none()); assert_eq!(coordinators.first().unwrap().state, State::DkgPublicGather); @@ -1690,7 +1784,11 @@ pub mod test { ); // Start a DKG round where we will not allow all signers to recv DkgBegin, so they will not respond with DkgPublicShares - let message = coordinators.first_mut().unwrap().start_dkg_round().unwrap(); + let message = coordinators + .first_mut() + .unwrap() + .start_dkg_round(false) + .unwrap(); assert!(coordinators.first().unwrap().aggregate_public_key.is_none()); assert_eq!(coordinators.first().unwrap().state, State::DkgPublicGather); @@ -1826,7 +1924,11 @@ pub mod test { setup::, SignerType>(num_signers, keys_per_signer); // We have started a dkg round - let message = coordinators.first_mut().unwrap().start_dkg_round().unwrap(); + let message = coordinators + .first_mut() + .unwrap() + .start_dkg_round(false) + .unwrap(); assert!(coordinators.first().unwrap().aggregate_public_key.is_none()); assert_eq!(coordinators.first().unwrap().state, State::DkgPublicGather); @@ -1948,7 +2050,11 @@ pub mod test { setup::, SignerType>(num_signers, keys_per_signer); // We have started a dkg round - let message = coordinators.first_mut().unwrap().start_dkg_round().unwrap(); + let message = coordinators + .first_mut() + .unwrap() + .start_dkg_round(false) + .unwrap(); assert!(coordinators.first().unwrap().aggregate_public_key.is_none()); assert_eq!(coordinators.first().unwrap().state, State::DkgPublicGather); @@ -2322,7 +2428,11 @@ pub mod test { let config = coordinators.first().unwrap().get_config(); // We have started a dkg round - let message = coordinators.first_mut().unwrap().start_dkg_round().unwrap(); + let message = coordinators + .first_mut() + .unwrap() + .start_dkg_round(false) + .unwrap(); assert!(coordinators.first().unwrap().aggregate_public_key.is_none()); assert_eq!(coordinators.first().unwrap().state, State::DkgPublicGather); @@ -2696,7 +2806,10 @@ pub mod test { let (packets, results) = coordinator .process_inbound_messages(&[Packet { sig: vec![], - msg: Message::DkgBegin(DkgBegin { dkg_id: old_id }), + msg: Message::DkgBegin(DkgBegin { + dkg_id: old_id, + keep_constant: false, + }), }]) .unwrap(); assert!(packets.is_empty()); @@ -2708,7 +2821,10 @@ pub mod test { let (packets, results) = coordinator .process_inbound_messages(&[Packet { sig: vec![], - msg: Message::DkgBegin(DkgBegin { dkg_id: id }), + msg: Message::DkgBegin(DkgBegin { + dkg_id: id, + keep_constant: false, + }), }]) .unwrap(); assert!(packets.is_empty()); diff --git a/src/state_machine/coordinator/frost.rs b/src/state_machine/coordinator/frost.rs index 9409388d..45075e1d 100644 --- a/src/state_machine/coordinator/frost.rs +++ b/src/state_machine/coordinator/frost.rs @@ -69,7 +69,7 @@ impl Coordinator { // that we start the next round at the correct id. (Do this rather // then overwriting afterwards to ensure logging is accurate) self.current_dkg_id = dkg_begin.dkg_id.wrapping_sub(1); - let packet = self.start_dkg_round()?; + let packet = self.start_dkg_round(dkg_begin.keep_constant)?; return Ok((Some(packet), None)); } else if let Message::NonceRequest(nonce_request) = &packet.msg { if self.current_sign_id >= nonce_request.sign_id { @@ -91,7 +91,7 @@ impl Coordinator { return Ok((None, None)); } State::DkgPublicDistribute => { - let packet = self.start_public_shares()?; + let packet = self.start_public_shares(false)?; return Ok((Some(packet), None)); } State::DkgPublicGather => { @@ -184,7 +184,7 @@ impl Coordinator { } /// Ask signers to send DKG public shares - pub fn start_public_shares(&mut self) -> Result { + pub fn start_public_shares(&mut self, keep_constant: bool) -> Result { self.dkg_public_shares.clear(); self.party_polynomials.clear(); self.ids_to_await = (0..self.config.num_signers).collect(); @@ -194,6 +194,7 @@ impl Coordinator { ); let dkg_begin = DkgBegin { dkg_id: self.current_dkg_id, + keep_constant, }; let dkg_begin_packet = Packet { @@ -715,11 +716,11 @@ impl CoordinatorTrait for Coordinator { } /// Start a DKG round - fn start_dkg_round(&mut self) -> Result { + fn start_dkg_round(&mut self, keep_constant: bool) -> Result { self.current_dkg_id = self.current_dkg_id.wrapping_add(1); info!("Starting DKG round {}", self.current_dkg_id); self.move_to(State::DkgPublicDistribute)?; - self.start_public_shares() + self.start_public_shares(keep_constant) } /// Start a signing round @@ -826,7 +827,7 @@ pub mod test { coordinator.state = State::DkgPublicDistribute; // Must be in this state before calling start public shares - let result = coordinator.start_public_shares().unwrap(); + let result = coordinator.start_public_shares(false).unwrap(); assert!(matches!(result.msg, Message::DkgBegin(_))); assert_eq!(coordinator.get_state(), State::DkgPublicGather); @@ -888,7 +889,10 @@ pub mod test { let (packets, results) = coordinator .process_inbound_messages(&[Packet { sig: vec![], - msg: Message::DkgBegin(DkgBegin { dkg_id: old_id }), + msg: Message::DkgBegin(DkgBegin { + dkg_id: old_id, + keep_constant: false, + }), }]) .unwrap(); assert!(packets.is_empty()); @@ -900,7 +904,10 @@ pub mod test { let (packets, results) = coordinator .process_inbound_messages(&[Packet { sig: vec![], - msg: Message::DkgBegin(DkgBegin { dkg_id: id }), + msg: Message::DkgBegin(DkgBegin { + dkg_id: id, + keep_constant: false, + }), }]) .unwrap(); assert!(packets.is_empty()); diff --git a/src/state_machine/coordinator/mod.rs b/src/state_machine/coordinator/mod.rs index 0ef15a6d..df9988b0 100644 --- a/src/state_machine/coordinator/mod.rs +++ b/src/state_machine/coordinator/mod.rs @@ -280,7 +280,7 @@ pub trait Coordinator: Clone + Debug + PartialEq { fn get_state(&self) -> State; /// Trigger a DKG round - fn start_dkg_round(&mut self) -> Result; + fn start_dkg_round(&mut self, keep_constant: bool) -> Result; /// Trigger a signing round fn start_signing_round( @@ -415,7 +415,7 @@ pub mod test { let mut rng = OsRng; let config = Config::new(10, 40, 28, Scalar::random(&mut rng)); let mut coordinator = Coordinator::new(config); - let result = coordinator.start_dkg_round(); + let result = coordinator.start_dkg_round(false); assert!(result.is_ok()); if let Message::DkgBegin(dkg_begin) = result.unwrap().msg { @@ -593,7 +593,11 @@ pub mod test { setup::(num_signers, keys_per_signer); // We have started a dkg round - let message = coordinators.first_mut().unwrap().start_dkg_round().unwrap(); + let message = coordinators + .first_mut() + .unwrap() + .start_dkg_round(false) + .unwrap(); assert!(coordinators .first_mut() .unwrap() diff --git a/src/state_machine/mod.rs b/src/state_machine/mod.rs index 14da2b52..0d237e1e 100644 --- a/src/state_machine/mod.rs +++ b/src/state_machine/mod.rs @@ -51,6 +51,7 @@ pub enum SignError { } /// Result of a DKG or sign operation +#[derive(Debug, Clone)] pub enum OperationResult { /// DKG succeeded with the wrapped public key Dkg(Point), diff --git a/src/state_machine/signer/mod.rs b/src/state_machine/signer/mod.rs index 2bd74f45..49a600bf 100644 --- a/src/state_machine/signer/mod.rs +++ b/src/state_machine/signer/mod.rs @@ -260,14 +260,14 @@ impl Signer { } /// Reset internal state - pub fn reset(&mut self, dkg_id: u64, rng: &mut T) { + pub fn reset(&mut self, dkg_id: u64, keep_constant: bool, rng: &mut T) { self.dkg_id = dkg_id; self.commitments.clear(); self.decrypted_shares.clear(); self.decryption_keys.clear(); self.invalid_private_shares.clear(); self.public_nonces.clear(); - self.signer.reset_polys(rng); + self.signer.reset_polys(keep_constant, rng); self.dkg_public_shares.clear(); self.dkg_private_shares.clear(); self.dkg_private_begin_msg = None; @@ -594,7 +594,7 @@ impl Signer { fn dkg_begin(&mut self, dkg_begin: &DkgBegin) -> Result, Error> { let mut rng = OsRng; - self.reset(dkg_begin.dkg_id, &mut rng); + self.reset(dkg_begin.dkg_id, dkg_begin.keep_constant, &mut rng); self.move_to(State::DkgPublicDistribute)?; //let _party_state = self.signer.save(); @@ -935,7 +935,10 @@ pub mod test { assert!(!signer.can_dkg_end()); // meet the conditions for DKG_END - let dkg_begin = Message::DkgBegin(DkgBegin { dkg_id: 1 }); + let dkg_begin = Message::DkgBegin(DkgBegin { + dkg_id: 1, + keep_constant: false, + }); let dkg_public_shares = signer .process(&dkg_begin) .expect("failed to process DkgBegin"); diff --git a/src/traits.rs b/src/traits.rs index 08a32361..77c5aa29 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -1,11 +1,12 @@ use core::{cmp::PartialEq, fmt::Debug}; use hashbrown::HashMap; -use polynomial::Polynomial; use rand_core::{CryptoRng, RngCore}; use serde::{Deserialize, Serialize}; use crate::{ - common::{MerkleRoot, Nonce, PolyCommitment, PublicNonce, Signature, SignatureShare}, + common::{ + MerkleRoot, Nonce, PolyCommitment, Polynomial, PublicNonce, Signature, SignatureShare, + }, curve::{point::Point, scalar::Scalar}, errors::{AggregatorError, DkgError}, taproot::SchnorrProof, @@ -15,7 +16,7 @@ use crate::{ /// The saved state required to reconstruct a party pub struct PartyState { /// The party's private polynomial - pub polynomial: Option>, + pub polynomial: Option>, /// The key IDS and associate private keys for this party pub private_keys: Vec<(u32, Scalar)>, /// The nonce being used by this party @@ -72,7 +73,7 @@ pub trait Signer: Clone + Debug + PartialEq { fn get_poly_commitments(&self, rng: &mut RNG) -> Vec; /// Reset all polynomials for this signer - fn reset_polys(&mut self, rng: &mut RNG); + fn reset_polys(&mut self, keep_constant: bool, rng: &mut RNG); /// Clear all polynomials for this signer fn clear_polys(&mut self); @@ -299,4 +300,33 @@ pub mod test_helpers { }, } } + + /// Check that the keep_constant param of reset_polys works + pub fn reset_polys() { + let mut rng = OsRng; + let id = 1; + let key_ids = [1, 2, 3]; + let n: u32 = 10; + let n_p = 4; + let t: u32 = 7; + + let mut signer = Signer::new(id, &key_ids, n, n_p, t, &mut rng); + let comms0 = signer.get_poly_commitments(&mut rng); + + signer.reset_polys(false, &mut rng); + + let comms1 = signer.get_poly_commitments(&mut rng); + + for (comm0, comm1) in comms0.into_iter().zip(comms1.clone()) { + assert!(comm0.poly[0] != comm1.poly[0]); + } + + signer.reset_polys(true, &mut rng); + + let comms2 = signer.get_poly_commitments(&mut rng); + + for (comm1, comm2) in comms1.into_iter().zip(comms2) { + assert!(comm1.poly[0] == comm2.poly[0]); + } + } } diff --git a/src/v1.rs b/src/v1.rs index 65f5def9..c7ebb750 100644 --- a/src/v1.rs +++ b/src/v1.rs @@ -1,11 +1,13 @@ use hashbrown::HashMap; use num_traits::{One, Zero}; -use polynomial::Polynomial; use rand_core::{CryptoRng, RngCore}; use tracing::warn; use crate::{ - common::{CheckPrivateShares, Nonce, PolyCommitment, PublicNonce, Signature, SignatureShare}, + common::{ + CheckPrivateShares, Nonce, PolyCommitment, Polynomial, PublicNonce, Signature, + SignatureShare, + }, compute, curve::{ point::{Point, G}, @@ -26,7 +28,7 @@ pub struct Party { /// The public key pub public_key: Point, /// The polynomial used for Lagrange interpolation - pub f: Option>, + pub f: Option>, num_keys: u32, threshold: u32, private_key: Scalar, @@ -92,10 +94,8 @@ impl Party { ) -> Option { if let Some(poly) = &self.f { Some(PolyCommitment { - id: ID::new(&self.id(), &poly.data()[0], rng), - poly: (0..poly.data().len()) - .map(|i| &poly.data()[i] * G) - .collect(), + id: ID::new(&self.id(), &poly[0], rng), + poly: (poly * G).params, }) } else { warn!("get_poly_commitment called with no polynomial"); @@ -104,8 +104,22 @@ impl Party { } /// Make a new polynomial - pub fn reset_poly(&mut self, rng: &mut RNG) { - self.f = Some(VSS::random_poly(self.threshold - 1, rng)); + pub fn reset_poly(&mut self, keep_constant: bool, rng: &mut RNG) { + let constant = if let Some(poly) = &self.f { + if keep_constant { + Some(poly[0]) + } else { + None + } + } else { + None + }; + + if let Some(c) = constant { + self.f = Some(VSS::random_poly_with_constant(self.threshold - 1, c, rng)); + } else { + self.f = Some(VSS::random_poly(self.threshold - 1, rng)); + } } /// Clear the polynomial @@ -546,9 +560,9 @@ impl traits::Signer for Signer { polys } - fn reset_polys(&mut self, rng: &mut RNG) { + fn reset_polys(&mut self, keep_constant: bool, rng: &mut RNG) { for party in self.parties.iter_mut() { - party.reset_poly(rng); + party.reset_poly(keep_constant, rng); } } @@ -783,6 +797,11 @@ mod tests { } } + #[test] + fn reset_polys() { + traits::test_helpers::reset_polys::(); + } + #[allow(non_snake_case)] #[test] fn aggregator_sign() { diff --git a/src/v2.rs b/src/v2.rs index f738a8c2..f72a4483 100644 --- a/src/v2.rs +++ b/src/v2.rs @@ -1,11 +1,10 @@ use hashbrown::HashMap; use num_traits::{One, Zero}; -use polynomial::Polynomial; use rand_core::{CryptoRng, RngCore}; use tracing::warn; use crate::{ - common::{Nonce, PolyCommitment, PublicNonce, Signature, SignatureShare}, + common::{Nonce, PolyCommitment, Polynomial, PublicNonce, Signature, SignatureShare}, compute, curve::{ point::{Point, G}, @@ -29,7 +28,7 @@ pub struct Party { num_keys: u32, num_parties: u32, threshold: u32, - f: Option>, + f: Option>, private_keys: HashMap, group_key: Point, nonce: Nonce, @@ -72,10 +71,8 @@ impl Party { ) -> Option { if let Some(poly) = &self.f { Some(PolyCommitment { - id: ID::new(&self.id(), &poly.data()[0], rng), - poly: (0..poly.data().len()) - .map(|i| &poly.data()[i] * G) - .collect(), + id: ID::new(&self.id(), &poly[0], rng), + poly: (poly * G).params, }) } else { warn!("get_poly_commitment called with no polynomial"); @@ -478,8 +475,22 @@ impl traits::Signer for Party { } } - fn reset_polys(&mut self, rng: &mut RNG) { - self.f = Some(VSS::random_poly(self.threshold - 1, rng)); + fn reset_polys(&mut self, keep_constant: bool, rng: &mut RNG) { + let constant = if let Some(poly) = &self.f { + if keep_constant { + Some(poly[0]) + } else { + None + } + } else { + None + }; + + if let Some(c) = constant { + self.f = Some(VSS::random_poly_with_constant(self.threshold - 1, c, rng)); + } else { + self.f = Some(VSS::random_poly(self.threshold - 1, rng)); + } } fn clear_polys(&mut self) { @@ -732,4 +743,9 @@ mod tests { traits::test_helpers::bad_polynomial_length::(gt); traits::test_helpers::bad_polynomial_length::(lt); } + + #[test] + fn reset_polys() { + traits::test_helpers::reset_polys::(); + } } diff --git a/src/vss.rs b/src/vss.rs index 4ffbcb45..dc419a56 100644 --- a/src/vss.rs +++ b/src/vss.rs @@ -1,6 +1,6 @@ -use polynomial::Polynomial; use rand_core::{CryptoRng, RngCore}; +use crate::common::Polynomial; use crate::curve::scalar::Scalar; /// A verifiable secret share algorithm @@ -8,8 +8,22 @@ pub struct VSS {} impl VSS { /// Construct a random polynomial of the passed degree `n` - pub fn random_poly(n: u32, rng: &mut RNG) -> Polynomial { - let params: Vec = (0..n + 1).map(|_| Scalar::random(rng)).collect(); - Polynomial::new(params) + pub fn random_poly( + n: u32, + rng: &mut RNG, + ) -> Polynomial { + Polynomial::random(n, rng) + } + + /// Construct a random polynomial of the passed degree `n` using the passed constant term + pub fn random_poly_with_constant( + n: u32, + constant: Scalar, + rng: &mut RNG, + ) -> Polynomial { + let mut poly = Polynomial::random(n, rng); + poly.params[0] = constant; + + poly } }