From 9459a78e75eca5db957039dc2f38a8272e575580 Mon Sep 17 00:00:00 2001 From: Joey Yandle Date: Mon, 25 Sep 2023 11:49:49 -0400 Subject: [PATCH] Add `Aggregator` trait (#21) * add aggregator trait; rename v1/v2 SignatureAggregator; inc major rev since API is changing * fmt fixes * add traits::Signer::new so we can have a consistent way of creating Signers * split Aggregator::new into a new that can't fail, and an init that can, since we can't return a result with Self from a trait * remove most non-snake case variables, only leave R which is the default terminology for schnorr proofs * remove another unnecessary snake case annotation --- Cargo.toml | 2 +- src/common.rs | 13 ++- src/main.rs | 10 ++- src/taproot.rs | 12 +-- src/traits.rs | 45 +++++++++- src/v1.rs | 218 ++++++++++++++++++++++++++----------------------- src/v2.rs | 216 +++++++++++++++++++++++++----------------------- 7 files changed, 292 insertions(+), 224 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b9880503..1e30e8fc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "wsts" -version = "3.0.0" +version = "4.0.0" edition = "2021" authors = ["Joey Yandle "] license = "Apache-2.0" diff --git a/src/common.rs b/src/common.rs index 2888eab1..2a342a10 100644 --- a/src/common.rs +++ b/src/common.rs @@ -16,26 +16,25 @@ use crate::compute::challenge; use crate::schnorr::ID; #[derive(Clone, Debug, Deserialize, Serialize)] -#[allow(non_snake_case)] /// A commitment to a polynonial, with a Schnorr proof of ownership bound to the ID pub struct PolyCommitment { /// The party ID with a schnorr proof pub id: ID, /// The public polynomial which commits to the secret polynomial - pub A: Vec, + pub poly: Vec, } impl PolyCommitment { /// Verify the wrapped schnorr ID pub fn verify(&self) -> bool { - self.id.verify(&self.A[0]) + self.id.verify(&self.poly[0]) } } impl Display for PolyCommitment { fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { write!(f, "{}", self.id.id)?; - for p in &self.A { + for p in &self.poly { write!(f, " {}", p)?; } Ok(()) @@ -183,9 +182,9 @@ impl<'a> CheckPrivateShares<'a> { /// Construct a new CheckPrivateShares object pub fn new(id: Scalar, shares: &HashMap, polys: &'a [PolyCommitment]) -> Self { let n: u32 = shares.len().try_into().unwrap(); - let t: u32 = polys[0].A.len().try_into().unwrap(); + let t: u32 = polys[0].poly.len().try_into().unwrap(); let x = id; - let mut powers = Vec::with_capacity(polys[0].A.len()); + let mut powers = Vec::with_capacity(polys[0].poly.len()); let mut pow = Scalar::one(); for _ in 0..t { @@ -228,7 +227,7 @@ impl<'a> MultiMult for CheckPrivateShares<'a> { let j = i / u; let k = i % u; - &self.polys[j].A[k] + &self.polys[j].poly[k] } else { &G } diff --git a/src/main.rs b/src/main.rs index 08626d7c..d36ddd5e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,7 @@ use rand_core::OsRng; use std::{env, time}; -use wsts::{common::test_helpers::gen_signer_ids, v1, v2}; +use wsts::{common::test_helpers::gen_signer_ids, traits::Aggregator, v1, v2}; #[allow(non_snake_case)] fn main() { @@ -41,7 +41,8 @@ fn main() { let dkg_time = dkg_start.elapsed(); let mut signers = signers[..(K * 3 / 4).try_into().unwrap()].to_vec(); - let mut aggregator = v1::SignatureAggregator::new(N, T, A).expect("aggregator ctor failed"); + let mut aggregator = v1::Aggregator::new(N, T); + aggregator.init(A).expect("aggregator init failed"); let party_sign_start = time::Instant::now(); let (nonces, sig_shares) = v1::test_helpers::sign(msg, &mut signers, &mut rng); @@ -49,7 +50,7 @@ fn main() { let group_sign_start = time::Instant::now(); let _sig = aggregator - .sign(msg, &nonces, &sig_shares) + .sign(msg, &nonces, &sig_shares, &[]) .expect("v1 group sign failed"); let group_sign_time = group_sign_start.elapsed(); @@ -76,7 +77,8 @@ fn main() { let dkg_time = dkg_start.elapsed(); let mut signers = signers[..(K * 3 / 4).try_into().unwrap()].to_vec(); - let mut aggregator = v2::SignatureAggregator::new(N, T, A).expect("aggregator ctor failed"); + let mut aggregator = v2::Aggregator::new(N, T); + aggregator.init(A).expect("aggregator init failed"); let party_sign_start = time::Instant::now(); let (nonces, sig_shares, key_ids) = v2::test_helpers::sign(msg, &mut signers, &mut rng); diff --git a/src/taproot.rs b/src/taproot.rs index 59360144..7fa5beb9 100644 --- a/src/taproot.rs +++ b/src/taproot.rs @@ -145,7 +145,7 @@ pub mod test_helpers { mod test { use super::{test_helpers, SchnorrProof}; - use crate::{compute, traits::Signer, v1, v2}; + use crate::{compute, traits::Aggregator, traits::Signer, v1, v2}; use rand_core::OsRng; #[test] @@ -192,11 +192,11 @@ mod test { }; let mut S = [signers[0].clone(), signers[1].clone(), signers[3].clone()].to_vec(); - let mut sig_agg = - v1::SignatureAggregator::new(N, T, A.clone()).expect("aggregator ctor failed"); + let mut sig_agg = v1::Aggregator::new(N, T); + sig_agg.init(A.clone()).expect("aggregator init failed"); let tweaked_public_key = compute::tweaked_public_key(&sig_agg.poly[0], merkle_root); let (nonces, sig_shares) = test_helpers::sign(&msg, &mut S, &mut rng, merkle_root); - let proof = match sig_agg.sign_taproot(&msg, &nonces, &sig_shares, merkle_root) { + let proof = match sig_agg.sign_taproot(&msg, &nonces, &sig_shares, &[], merkle_root) { Err(e) => panic!("Aggregator sign failed: {:?}", e), Ok(proof) => proof, }; @@ -255,8 +255,8 @@ mod test { let mut S = [signers[0].clone(), signers[1].clone(), signers[3].clone()].to_vec(); let key_ids = S.iter().flat_map(|s| s.get_key_ids()).collect::>(); - let mut sig_agg = - v2::SignatureAggregator::new(Nk, T, A.clone()).expect("aggregator ctor failed"); + let mut sig_agg = v2::Aggregator::new(Nk, T); + sig_agg.init(A.clone()).expect("aggregator init failed"); let tweaked_public_key = compute::tweaked_public_key(&sig_agg.poly[0], merkle_root); let (nonces, sig_shares) = test_helpers::sign(&msg, &mut S, &mut rng, merkle_root); let proof = match sig_agg.sign_taproot(&msg, &nonces, &sig_shares, &key_ids, merkle_root) { diff --git a/src/traits.rs b/src/traits.rs index 2cad3dba..46eaa461 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -3,12 +3,23 @@ use p256k1::{point::Point, scalar::Scalar}; use rand_core::{CryptoRng, RngCore}; use crate::{ - common::{PolyCommitment, PublicNonce, SignatureShare}, - errors::DkgError, + common::{PolyCommitment, PublicNonce, Signature, SignatureShare}, + errors::{AggregatorError, DkgError}, + taproot::SchnorrProof, }; -/// A trait which provides a common interface for `v1` and `v2` +/// A trait which provides a common `Signer` interface for `v1` and `v2` pub trait Signer { + /// Create a new `Signer` + fn new( + party_id: u32, + key_ids: &[u32], + num_signers: u32, + num_keys: u32, + threshold: u32, + rng: &mut RNG, + ) -> Self; + /// Get the signer ID for this signer fn get_id(&self) -> u32; @@ -61,3 +72,31 @@ pub trait Signer { merkle_root: Option<[u8; 32]>, ) -> Vec; } + +/// A trait which provides a common `Aggregator` interface for `v1` and `v2` +pub trait Aggregator { + /// Construct an Aggregator with the passed parameters + fn new(num_keys: u32, threshold: u32) -> Self; + + /// Initialize an Aggregator with the passed polynomial commitments + fn init(&mut self, poly_comms: Vec) -> Result<(), AggregatorError>; + + /// Check and aggregate the signature shares into a `Signature` + fn sign( + &mut self, + msg: &[u8], + nonces: &[PublicNonce], + sig_shares: &[SignatureShare], + key_ids: &[u32], + ) -> Result; + + /// Check and aggregate the signature shares into a `SchnorrProof` + fn sign_taproot( + &mut self, + msg: &[u8], + nonces: &[PublicNonce], + sig_shares: &[SignatureShare], + key_ids: &[u32], + merkle_root: Option<[u8; 32]>, + ) -> Result; +} diff --git a/src/v1.rs b/src/v1.rs index 0e37cb5b..138da92f 100644 --- a/src/v1.rs +++ b/src/v1.rs @@ -14,6 +14,7 @@ use crate::{ errors::{AggregatorError, DkgError}, schnorr::ID, taproot::SchnorrProof, + traits, vss::VSS, }; @@ -27,7 +28,6 @@ pub struct PartyState { } #[derive(Clone, Debug, Eq, PartialEq)] -#[allow(non_snake_case)] /// A FROST party, which encapsulates a single polynomial, nonce, and key pub struct Party { /// The ID @@ -44,7 +44,6 @@ pub struct Party { } impl Party { - #[allow(non_snake_case)] /// Construct a random Party with the passed ID and parameters pub fn new(id: u32, n: u32, t: u32, rng: &mut RNG) -> Self { Self { @@ -86,12 +85,11 @@ impl Party { PublicNonce::from(&self.nonce) } - #[allow(non_snake_case)] /// Get a public commitment to the private polynomial pub fn get_poly_commitment(&self, rng: &mut RNG) -> PolyCommitment { PolyCommitment { id: ID::new(&self.id(), &self.f.data()[0], rng), - A: (0..self.f.data().len()) + poly: (0..self.f.data().len()) .map(|i| &self.f.data()[i] * G) .collect(), } @@ -112,12 +110,11 @@ impl Party { shares } - #[allow(non_snake_case)] /// Compute this party's share of the group secret key pub fn compute_secret( &mut self, shares: HashMap, - A: &[PolyCommitment], + comms: &[PolyCommitment], ) -> Result<(), DkgError> { let mut missing_shares = Vec::new(); for i in 0..self.n { @@ -135,7 +132,7 @@ impl Party { let bad_ids: Vec = shares .keys() .cloned() - .filter(|i| !A[usize::try_from(*i).unwrap()].verify()) + .filter(|i| !comms[usize::try_from(*i).unwrap()].verify()) .collect(); if !bad_ids.is_empty() { return Err(DkgError::BadIds(bad_ids)); @@ -144,14 +141,14 @@ impl Party { // building a vector of scalars and points from public poly evaluations and expected values takes too much memory // instead make an object which implements p256k1 MultiMult trait, using the existing powers of x and shares - let mut check_shares = CheckPrivateShares::new(self.id(), &shares, A); + let mut check_shares = CheckPrivateShares::new(self.id(), &shares, comms); // if the batch verify fails then check them one by one and find the bad ones if Point::multimult_trait(&mut check_shares)? != Point::zero() { let mut bad_shares = Vec::new(); for (i, s) in shares.iter() { - let Ai = &A[usize::try_from(*i).unwrap()]; - if s * G != compute::poly(&self.id(), &Ai.A)? { + let comm = &comms[usize::try_from(*i).unwrap()]; + if s * G != compute::poly(&self.id(), &comm.poly)? { bad_shares.push(*i); } } @@ -159,10 +156,10 @@ impl Party { } for (i, s) in shares.iter() { - let Ai = &A[usize::try_from(*i).unwrap()]; + let comm = &comms[usize::try_from(*i).unwrap()]; self.private_key += s; - self.group_key += Ai.A[0]; + self.group_key += comm.poly[0]; } self.public_key = self.private_key * G; @@ -174,12 +171,11 @@ impl Party { compute::id(self.id) } - #[allow(non_snake_case)] /// Sign `msg` with this party's share of the group private key, using the set of `signers` and corresponding `nonces` pub fn sign(&self, msg: &[u8], signers: &[u32], nonces: &[PublicNonce]) -> SignatureShare { - let (_R_vec, R) = compute::intermediate(msg, signers, nonces); + let (_, aggregate_nonce) = compute::intermediate(msg, signers, nonces); let mut z = &self.nonce.d + &self.nonce.e * compute::binding(&self.id(), nonces, msg); - z += compute::challenge(&self.group_key, &R, msg) + z += compute::challenge(&self.group_key, &aggregate_nonce, msg) * &self.private_key * compute::lambda(self.id, signers); @@ -234,84 +230,17 @@ impl Party { } } -#[allow(non_snake_case)] /// The group signature aggregator -pub struct SignatureAggregator { - /// The total number of keys/parties - pub N: u32, +pub struct Aggregator { + /// The total number of keys + pub num_keys: u32, /// The threshold of signers needed to construct a valid signature - pub T: u32, + pub threshold: u32, /// The aggregate group polynomial; poly[0] is the group public key pub poly: Vec, } -impl SignatureAggregator { - #[allow(non_snake_case)] - /// Construct a SignatureAggregator with the passed parameters and polynomial commitments - pub fn new(N: u32, T: u32, A: Vec) -> Result { - let len = N.try_into().unwrap(); - if A.len() != len { - return Err(AggregatorError::BadPolyCommitmentLen(len, A.len())); - } - - let mut bad_poly_commitments = Vec::new(); - for A_i in &A { - if !A_i.verify() { - bad_poly_commitments.push(A_i.id.id); - } - } - if !bad_poly_commitments.is_empty() { - return Err(AggregatorError::BadPolyCommitments(bad_poly_commitments)); - } - - let mut poly = Vec::with_capacity(T.try_into().unwrap()); - - for i in 0..poly.capacity() { - poly.push(Point::zero()); - for p in &A { - poly[i] += &p.A[i]; - } - } - - Ok(Self { N, T, poly }) - } - - #[allow(non_snake_case)] - /// Check and aggregate the party signatures - pub fn sign( - &mut self, - msg: &[u8], - nonces: &[PublicNonce], - sig_shares: &[SignatureShare], - ) -> Result { - let (key, sig) = self.sign_with_tweak(msg, nonces, sig_shares, &Scalar::zero())?; - - if sig.verify(&key, msg) { - Ok(sig) - } else { - Err(AggregatorError::BadGroupSig) - } - } - - /// Check and aggregate the party signatures using a merke root to make a tweak - pub fn sign_taproot( - &mut self, - msg: &[u8], - nonces: &[PublicNonce], - sig_shares: &[SignatureShare], - merkle_root: Option<[u8; 32]>, - ) -> Result { - let tweak = compute::tweak(&self.poly[0], merkle_root); - let (key, sig) = self.sign_with_tweak(msg, nonces, sig_shares, &tweak)?; - let proof = SchnorrProof::new(&sig); - - if proof.verify(&key.x(), msg) { - Ok(proof) - } else { - Err(AggregatorError::BadGroupSig) - } - } - +impl Aggregator { #[allow(non_snake_case)] /// Check and aggregate the party signatures using a tweak pub fn sign_with_tweak( @@ -326,7 +255,7 @@ impl SignatureAggregator { } let signers: Vec = sig_shares.iter().map(|ss| ss.id).collect(); - let (R_vec, R) = compute::intermediate(msg, &signers, nonces); + let (Rs, R) = compute::intermediate(msg, &signers, nonces); let mut z = Scalar::zero(); let mut bad_party_keys = Vec::new(); let mut bad_party_sigs = Vec::new(); @@ -357,7 +286,7 @@ impl SignatureAggregator { let z_i = sig_shares[i].z_i; if z_i * G - != r_sign * R_vec[i] + != r_sign * Rs[i] + cx_sign * (compute::lambda(sig_shares[i].id, &signers) * c * public_key) { bad_party_sigs.push(sig_shares[i].id); @@ -381,6 +310,85 @@ impl SignatureAggregator { } } +impl traits::Aggregator for Aggregator { + /// Construct an Aggregator with the passed parameters + fn new(num_keys: u32, threshold: u32) -> Self { + Self { + num_keys, + threshold, + poly: Default::default(), + } + } + + /// Initialize the Aggregator polynomial + fn init(&mut self, comms: Vec) -> Result<(), AggregatorError> { + let len = self.num_keys.try_into().unwrap(); + if comms.len() != len { + return Err(AggregatorError::BadPolyCommitmentLen(len, comms.len())); + } + + let mut bad_poly_commitments = Vec::new(); + for comm in &comms { + if !comm.verify() { + bad_poly_commitments.push(comm.id.id); + } + } + if !bad_poly_commitments.is_empty() { + return Err(AggregatorError::BadPolyCommitments(bad_poly_commitments)); + } + + let mut poly = Vec::with_capacity(self.threshold.try_into().unwrap()); + + for i in 0..poly.capacity() { + poly.push(Point::zero()); + for p in &comms { + poly[i] += &p.poly[i]; + } + } + + self.poly = poly; + + Ok(()) + } + + /// Check and aggregate the party signatures + fn sign( + &mut self, + msg: &[u8], + nonces: &[PublicNonce], + sig_shares: &[SignatureShare], + _key_ids: &[u32], + ) -> Result { + let (key, sig) = self.sign_with_tweak(msg, nonces, sig_shares, &Scalar::zero())?; + + if sig.verify(&key, msg) { + Ok(sig) + } else { + Err(AggregatorError::BadGroupSig) + } + } + + /// Check and aggregate the party signatures using a merke root to make a tweak + fn sign_taproot( + &mut self, + msg: &[u8], + nonces: &[PublicNonce], + sig_shares: &[SignatureShare], + _key_ids: &[u32], + merkle_root: Option<[u8; 32]>, + ) -> Result { + let tweak = compute::tweak(&self.poly[0], merkle_root); + let (key, sig) = self.sign_with_tweak(msg, nonces, sig_shares, &tweak)?; + let proof = SchnorrProof::new(&sig); + + if proof.verify(&key.x(), msg) { + Ok(proof) + } else { + Err(AggregatorError::BadGroupSig) + } + } +} + #[derive(Debug, Deserialize, Serialize)] /// The saved state required to construct a Signer pub struct SignerState { @@ -461,7 +469,18 @@ impl Signer { } } -impl crate::traits::Signer for Signer { +impl traits::Signer for Signer { + fn new( + party_id: u32, + key_ids: &[u32], + _num_signers: u32, + num_keys: u32, + threshold: u32, + rng: &mut RNG, + ) -> Self { + Signer::new(party_id, key_ids, num_keys, threshold, rng) + } + fn get_id(&self) -> u32 { self.id } @@ -569,13 +588,12 @@ pub mod test_helpers { use hashbrown::HashMap; use rand_core::{CryptoRng, RngCore}; - #[allow(non_snake_case)] /// Run a distributed key generation round pub fn dkg( signers: &mut [v1::Signer], rng: &mut RNG, ) -> Result, HashMap> { - let A: Vec = signers + let comms: Vec = signers .iter() .flat_map(|s| s.get_poly_commitments(rng)) .collect(); @@ -589,13 +607,13 @@ pub mod test_helpers { let mut secret_errors = HashMap::new(); for signer in signers.iter_mut() { - if let Err(signer_secret_errors) = signer.compute_secrets(&private_shares, &A) { + if let Err(signer_secret_errors) = signer.compute_secrets(&private_shares, &comms) { secret_errors.extend(signer_secret_errors.into_iter()); } } if secret_errors.is_empty() { - Ok(A) + Ok(comms) } else { Err(secret_errors) } @@ -620,7 +638,7 @@ pub mod test_helpers { #[cfg(test)] mod tests { - use crate::traits::Signer; + use crate::traits::{Aggregator, Signer}; use crate::v1; use num_traits::Zero; @@ -698,8 +716,8 @@ mod tests { .map(|(id, ids)| v1::Signer::new(id.try_into().unwrap(), ids, N, T, &mut rng)) .collect(); - let A = match v1::test_helpers::dkg(&mut signers, &mut rng) { - Ok(A) => A, + let comms = match v1::test_helpers::dkg(&mut signers, &mut rng) { + Ok(comms) => comms, Err(secret_errors) => { panic!("Got secret errors from DKG: {:?}", secret_errors); } @@ -708,11 +726,11 @@ mod tests { // signers [0,1,3] who have T keys { let mut signers = [signers[0].clone(), signers[1].clone(), signers[3].clone()].to_vec(); - let mut sig_agg = - v1::SignatureAggregator::new(N, T, A.clone()).expect("aggregator ctor failed"); + let mut sig_agg = v1::Aggregator::new(N, T); + sig_agg.init(comms.clone()).expect("aggregator init failed"); let (nonces, sig_shares) = v1::test_helpers::sign(&msg, &mut signers, &mut rng); - if let Err(e) = sig_agg.sign(&msg, &nonces, &sig_shares) { + if let Err(e) = sig_agg.sign(&msg, &nonces, &sig_shares, &[]) { panic!("Aggregator sign failed: {:?}", e); } } diff --git a/src/v2.rs b/src/v2.rs index 6eb603ce..ba2bf872 100644 --- a/src/v2.rs +++ b/src/v2.rs @@ -8,12 +8,15 @@ use polynomial::Polynomial; use rand_core::{CryptoRng, RngCore}; use serde::{Deserialize, Serialize}; -use crate::common::{Nonce, PolyCommitment, PublicNonce, Signature, SignatureShare}; -use crate::compute; -use crate::errors::{AggregatorError, DkgError}; -use crate::schnorr::ID; -use crate::taproot::SchnorrProof; -use crate::vss::VSS; +use crate::{ + common::{Nonce, PolyCommitment, PublicNonce, Signature, SignatureShare}, + compute, + errors::{AggregatorError, DkgError}, + schnorr::ID, + taproot::SchnorrProof, + traits, + vss::VSS, +}; /// A map of private keys indexed by key ID pub type PrivKeyMap = HashMap; @@ -42,7 +45,6 @@ pub struct PartyState { } #[derive(Clone, Debug, Eq, PartialEq)] -#[allow(non_snake_case)] /// A WSTS party, which encapsulates a single polynomial, nonce, and one private key per key ID pub struct Party { /// The party ID @@ -60,7 +62,6 @@ pub struct Party { } impl Party { - #[allow(non_snake_case)] /// Construct a random Party with the passed party ID, key IDs, and parameters pub fn new( party_id: u32, @@ -119,12 +120,11 @@ impl Party { PublicNonce::from(&self.nonce) } - #[allow(non_snake_case)] /// Get a public commitment to the private polynomial pub fn get_poly_commitment(&self, rng: &mut RNG) -> PolyCommitment { PolyCommitment { id: ID::new(&self.id(), &self.f.data()[0], rng), - A: (0..self.f.data().len()) + poly: (0..self.f.data().len()) .map(|i| &self.f.data()[i] * G) .collect(), } @@ -139,12 +139,11 @@ impl Party { shares } - #[allow(non_snake_case)] /// Compute this party's share of the group secret key pub fn compute_secret( &mut self, shares: &HashMap>, - A: &[PolyCommitment], + comms: &[PolyCommitment], ) -> Result<(), DkgError> { let mut missing_shares = Vec::new(); for key_id in &self.key_ids { @@ -157,11 +156,11 @@ impl Party { } let mut bad_ids = Vec::new(); - for (i, Ai) in A.iter().enumerate() { - if !Ai.verify() { + for (i, comm) in comms.iter().enumerate() { + if !comm.verify() { bad_ids.push(i.try_into().unwrap()); } - self.group_key += Ai.A[0]; + self.group_key += comm.poly[0]; } if !bad_ids.is_empty() { return Err(DkgError::BadIds(bad_ids)); @@ -180,8 +179,8 @@ impl Party { let mut bad_shares = Vec::new(); for key_id in &self.key_ids { for (sender, s) in &shares[key_id] { - let Ai = &A[usize::try_from(*sender).unwrap()]; - if s * G != compute::poly(&compute::id(*key_id), &Ai.A)? { + let comm = &comms[usize::try_from(*sender).unwrap()]; + if s * G != compute::poly(&compute::id(*key_id), &comm.poly)? { bad_shares.push(*sender); } } @@ -208,7 +207,6 @@ impl Party { } /// Sign `msg` with this party's shares of the group private key, using the set of `party_ids`, `key_ids` and corresponding `nonces` - #[allow(non_snake_case)] pub fn sign( &self, msg: &[u8], @@ -230,7 +228,7 @@ impl Party { tweak: &Scalar, ) -> SignatureShare { let tweaked_public_key = self.group_key + tweak * G; - let (_R_vec, R) = compute::intermediate(msg, party_ids, nonces); + let (_, R) = compute::intermediate(msg, party_ids, nonces); let c = compute::challenge(&tweaked_public_key, &R, msg); let mut r = &self.nonce.d + &self.nonce.e * compute::binding(&self.id(), nonces, msg); if tweak != &Scalar::zero() && !R.has_even_y() { @@ -256,9 +254,8 @@ impl Party { } } -#[allow(non_snake_case)] /// The group signature aggregator -pub struct SignatureAggregator { +pub struct Aggregator { /// The total number of keys pub num_keys: u32, /// The threshold of signing keys needed to construct a valid signature @@ -267,79 +264,7 @@ pub struct SignatureAggregator { pub poly: Vec, } -impl SignatureAggregator { - #[allow(non_snake_case)] - /// Construct a SignatureAggregator with the passed parameters and polynomial commitments - pub fn new( - num_keys: u32, - threshold: u32, - A: Vec, // one per party_id - ) -> Result { - let mut bad_poly_commitments = Vec::new(); - for A_i in &A { - if !A_i.verify() { - bad_poly_commitments.push(A_i.id.id); - } - } - if !bad_poly_commitments.is_empty() { - return Err(AggregatorError::BadPolyCommitments(bad_poly_commitments)); - } - - let mut poly = Vec::with_capacity(threshold.try_into().unwrap()); - - for i in 0..poly.capacity() { - poly.push(Point::zero()); - for p in &A { - poly[i] += &p.A[i]; - } - } - - Ok(Self { - num_keys, - threshold, - poly, - }) - } - - /// Check and aggregate the party signatures - #[allow(non_snake_case)] - pub fn sign( - &mut self, - msg: &[u8], - nonces: &[PublicNonce], - sig_shares: &[SignatureShare], - key_ids: &[u32], - ) -> Result { - let (key, sig) = self.sign_with_tweak(msg, nonces, sig_shares, key_ids, &Scalar::zero())?; - - if sig.verify(&key, msg) { - Ok(sig) - } else { - Err(AggregatorError::BadGroupSig) - } - } - - /// Check and aggregate the party signatures - #[allow(non_snake_case)] - pub fn sign_taproot( - &mut self, - msg: &[u8], - nonces: &[PublicNonce], - sig_shares: &[SignatureShare], - key_ids: &[u32], - merkle_root: Option<[u8; 32]>, - ) -> Result { - let tweak = compute::tweak(&self.poly[0], merkle_root); - let (key, sig) = self.sign_with_tweak(msg, nonces, sig_shares, key_ids, &tweak)?; - let proof = SchnorrProof::new(&sig); - - if proof.verify(&key.x(), msg) { - Ok(proof) - } else { - Err(AggregatorError::BadGroupSig) - } - } - +impl Aggregator { /// Check and aggregate the party signatures #[allow(non_snake_case)] pub fn sign_with_tweak( @@ -355,7 +280,7 @@ impl SignatureAggregator { } let party_ids: Vec = sig_shares.iter().map(|ss| ss.id).collect(); - let (Ris, R) = compute::intermediate(msg, &party_ids, nonces); + let (Rs, R) = compute::intermediate(msg, &party_ids, nonces); let mut z = Scalar::zero(); let mut bad_party_keys = Vec::new(); let mut bad_party_sigs = Vec::new(); @@ -390,7 +315,7 @@ impl SignatureAggregator { cx += compute::lambda(*key_id, key_ids) * c * public_key; } - if z_i * G != (r_sign * Ris[i] + cx_sign * cx) { + if z_i * G != (r_sign * Rs[i] + cx_sign * cx) { bad_party_sigs.push(sig_shares[i].id); } @@ -410,12 +335,97 @@ impl SignatureAggregator { } } +impl traits::Aggregator for Aggregator { + /// Construct an Aggregator with the passed parameters + fn new(num_keys: u32, threshold: u32) -> Self { + Self { + num_keys, + threshold, + poly: Default::default(), + } + } + + /// Initialize the Aggregator polynomial + fn init(&mut self, comms: Vec) -> Result<(), AggregatorError> { + let mut bad_poly_commitments = Vec::new(); + for comm in &comms { + if !comm.verify() { + bad_poly_commitments.push(comm.id.id); + } + } + if !bad_poly_commitments.is_empty() { + return Err(AggregatorError::BadPolyCommitments(bad_poly_commitments)); + } + + let mut poly = Vec::with_capacity(self.threshold.try_into().unwrap()); + + for i in 0..poly.capacity() { + poly.push(Point::zero()); + for comm in &comms { + poly[i] += &comm.poly[i]; + } + } + + self.poly = poly; + + Ok(()) + } + + /// Check and aggregate the party signatures + fn sign( + &mut self, + msg: &[u8], + nonces: &[PublicNonce], + sig_shares: &[SignatureShare], + key_ids: &[u32], + ) -> Result { + let (key, sig) = self.sign_with_tweak(msg, nonces, sig_shares, key_ids, &Scalar::zero())?; + + if sig.verify(&key, msg) { + Ok(sig) + } else { + Err(AggregatorError::BadGroupSig) + } + } + + /// Check and aggregate the party signatures + fn sign_taproot( + &mut self, + msg: &[u8], + nonces: &[PublicNonce], + sig_shares: &[SignatureShare], + key_ids: &[u32], + merkle_root: Option<[u8; 32]>, + ) -> Result { + let tweak = compute::tweak(&self.poly[0], merkle_root); + let (key, sig) = self.sign_with_tweak(msg, nonces, sig_shares, key_ids, &tweak)?; + let proof = SchnorrProof::new(&sig); + + if proof.verify(&key.x(), msg) { + Ok(proof) + } else { + Err(AggregatorError::BadGroupSig) + } + } +} + /// Typedef so we can use the same tokens for v1 and v2 pub type SignerState = PartyState; /// Typedef so we can use the same tokens for v1 and v2 pub type Signer = Party; -impl crate::traits::Signer for Party { +impl traits::Signer for Party { + fn new( + party_id: u32, + key_ids: &[u32], + num_signers: u32, + num_keys: u32, + threshold: u32, + rng: &mut RNG, + ) -> Self { + Party::new(party_id, key_ids, num_signers, num_keys, threshold, rng) + } + fn get_id(&self) -> u32 { self.party_id } @@ -511,7 +521,6 @@ pub mod test_helpers { use hashbrown::HashMap; use rand_core::{CryptoRng, RngCore}; - #[allow(non_snake_case)] /// Run a distributed key generation round pub fn dkg( signers: &mut [v2::Party], @@ -573,7 +582,7 @@ pub mod test_helpers { #[cfg(test)] mod tests { - use crate::v2; + use crate::{traits::Aggregator, v2}; use rand_core::OsRng; @@ -613,8 +622,8 @@ mod tests { .map(|(pid, pkids)| v2::Party::new(pid.try_into().unwrap(), pkids, Np, Nk, T, &mut rng)) .collect(); - let A = match v2::test_helpers::dkg(&mut signers, &mut rng) { - Ok(A) => A, + let comms = match v2::test_helpers::dkg(&mut signers, &mut rng) { + Ok(comms) => comms, Err(secret_errors) => { panic!("Got secret errors from DKG: {:?}", secret_errors); } @@ -623,8 +632,9 @@ mod tests { // signers [0,1,3] who have T keys { let mut signers = [signers[0].clone(), signers[1].clone(), signers[3].clone()].to_vec(); - let mut sig_agg = - v2::SignatureAggregator::new(Nk, T, A.clone()).expect("aggregator ctor failed"); + let mut sig_agg = v2::Aggregator::new(Nk, T); + + sig_agg.init(comms.clone()).expect("aggregator init failed"); let (nonces, sig_shares, key_ids) = v2::test_helpers::sign(&msg, &mut signers, &mut rng);