diff --git a/ferveo-python/examples/server_api_precomputed.py b/ferveo-python/examples/server_api_precomputed.py index a9b98001..a37ad573 100644 --- a/ferveo-python/examples/server_api_precomputed.py +++ b/ferveo-python/examples/server_api_precomputed.py @@ -59,7 +59,7 @@ def gen_eth_addr(i: int) -> str: # In the meantime, the client creates a ciphertext and decryption request msg = "abc".encode() aad = "my-aad".encode() -ciphertext = encrypt(msg, aad, dkg.public_key) +ciphertext = encrypt(msg, aad, client_aggregate.public_key) # Having aggregated the transcripts, the validators can now create decryption shares decryption_shares = [] diff --git a/ferveo-python/examples/server_api_simple.py b/ferveo-python/examples/server_api_simple.py index 44fb69c4..beda8133 100644 --- a/ferveo-python/examples/server_api_simple.py +++ b/ferveo-python/examples/server_api_simple.py @@ -62,7 +62,7 @@ def gen_eth_addr(i: int) -> str: # In the meantime, the client creates a ciphertext and decryption request msg = "abc".encode() aad = "my-aad".encode() -ciphertext = encrypt(msg, aad, dkg.public_key) +ciphertext = encrypt(msg, aad, client_aggregate.public_key) # The client can serialize/deserialize ciphertext for transport ciphertext_ser = bytes(ciphertext) diff --git a/ferveo-python/ferveo/__init__.py b/ferveo-python/ferveo/__init__.py index 58a3a140..5088675d 100644 --- a/ferveo-python/ferveo/__init__.py +++ b/ferveo-python/ferveo/__init__.py @@ -40,4 +40,5 @@ NoTranscriptsToAggregate, InvalidAggregateVerificationParameters, UnknownValidator, + TooManyTranscripts, ) diff --git a/ferveo-python/ferveo/__init__.pyi b/ferveo-python/ferveo/__init__.pyi index 894f71ed..f4b5fd9f 100644 --- a/ferveo-python/ferveo/__init__.pyi +++ b/ferveo-python/ferveo/__init__.pyi @@ -105,6 +105,7 @@ class DecryptionSharePrecomputed: @final class AggregatedTranscript: + public_key: DkgPublicKey def __init__(self, messages: Sequence[ValidatorMessage]): ... def verify( self, validators_num: int, messages: Sequence[ValidatorMessage] @@ -222,3 +223,6 @@ class InvalidAggregateVerificationParameters(Exception): class UnknownValidator(Exception): pass + +class TooManyTranscripts(Exception): + pass diff --git a/ferveo-python/test/test_ferveo.py b/ferveo-python/test/test_ferveo.py index 51af3867..82cbc4f1 100644 --- a/ferveo-python/test/test_ferveo.py +++ b/ferveo-python/test/test_ferveo.py @@ -89,7 +89,7 @@ def scenario_for_variant( # Client creates a ciphertext and requests decryption shares from validators msg = "abc".encode() aad = "my-aad".encode() - ciphertext = encrypt(msg, aad, dkg.public_key) + ciphertext = encrypt(msg, aad, client_aggregate.public_key) # Having aggregated the transcripts, the validators can now create decryption shares decryption_shares = [] diff --git a/ferveo-python/test/test_serialization.py b/ferveo-python/test/test_serialization.py index 6c600771..d188ea4d 100644 --- a/ferveo-python/test/test_serialization.py +++ b/ferveo-python/test/test_serialization.py @@ -5,6 +5,7 @@ DkgPublicKey, FerveoPublicKey, FerveoVariant, + ValidatorMessage ) @@ -32,7 +33,10 @@ def make_dkg_public_key(): validators=validators, me=me, ) - return dkg.public_key + transcripts = [ValidatorMessage(v, dkg.generate_transcript()) for v in validators] + aggregate = dkg.aggregate_transcripts(transcripts) + assert aggregate.verify(shares_num, transcripts) + return aggregate.public_key def make_shared_secret(): diff --git a/ferveo-tdec/src/ciphertext.rs b/ferveo-tdec/src/ciphertext.rs index cdaf956c..d5563132 100644 --- a/ferveo-tdec/src/ciphertext.rs +++ b/ferveo-tdec/src/ciphertext.rs @@ -108,8 +108,7 @@ pub fn encrypt( // h let h_gen = E::G2Affine::generator(); - let ry_prep = - E::G1Prepared::from(pubkey.public_key_share.mul(rand_element).into()); + let ry_prep = E::G1Prepared::from(pubkey.0.mul(rand_element).into()); // s let product = E::pairing(ry_prep, h_gen).0; // u @@ -150,7 +149,7 @@ pub fn decrypt_symmetric( ciphertext.check(aad, g_inv)?; let shared_secret = E::pairing( E::G1Prepared::from(ciphertext.commitment), - E::G2Prepared::from(private_key.private_key_share), + E::G2Prepared::from(private_key.0), ) .0; let shared_secret = SharedSecret(shared_secret); diff --git a/ferveo-tdec/src/decryption.rs b/ferveo-tdec/src/decryption.rs index dc93fee4..316d82f1 100644 --- a/ferveo-tdec/src/decryption.rs +++ b/ferveo-tdec/src/decryption.rs @@ -74,13 +74,13 @@ impl ValidatorShareChecksum { #[serde_as] #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -#[serde(bound( - serialize = "ValidatorShareChecksum: Serialize", - deserialize = "ValidatorShareChecksum: DeserializeOwned" -))] pub struct DecryptionShareSimple { #[serde_as(as = "serialization::SerdeAs")] pub decryption_share: E::TargetField, + #[serde(bound( + serialize = "ValidatorShareChecksum: Serialize", + deserialize = "ValidatorShareChecksum: DeserializeOwned" + ))] pub validator_checksum: ValidatorShareChecksum, } @@ -110,11 +110,8 @@ impl DecryptionShareSimple { ciphertext_header: &CiphertextHeader, ) -> Result { // D_i = e(U, Z_i) - let decryption_share = E::pairing( - ciphertext_header.commitment, - private_key_share.private_key_share, - ) - .0; + let decryption_share = + E::pairing(ciphertext_header.commitment, private_key_share.0).0; let validator_checksum = ValidatorShareChecksum::new( validator_decryption_key, @@ -146,14 +143,14 @@ impl DecryptionShareSimple { #[serde_as] #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -#[serde(bound( - serialize = "ValidatorShareChecksum: Serialize", - deserialize = "ValidatorShareChecksum: DeserializeOwned" -))] pub struct DecryptionSharePrecomputed { pub decrypter_index: usize, #[serde_as(as = "serialization::SerdeAs")] pub decryption_share: E::TargetField, + #[serde(bound( + serialize = "ValidatorShareChecksum: Serialize", + deserialize = "ValidatorShareChecksum: DeserializeOwned" + ))] pub validator_checksum: ValidatorShareChecksum, } @@ -188,11 +185,8 @@ impl DecryptionSharePrecomputed { let u_to_lagrange_coeff = ciphertext_header.commitment.mul(lagrange_coeff); // C_{λ_i} = e(U_{λ_i}, Z_i) - let decryption_share = E::pairing( - u_to_lagrange_coeff, - private_key_share.private_key_share, - ) - .0; + let decryption_share = + E::pairing(u_to_lagrange_coeff, private_key_share.0).0; let validator_checksum = ValidatorShareChecksum::new( validator_decryption_key, diff --git a/ferveo-tdec/src/key_share.rs b/ferveo-tdec/src/key_share.rs index 4c164f6e..236386ae 100644 --- a/ferveo-tdec/src/key_share.rs +++ b/ferveo-tdec/src/key_share.rs @@ -9,11 +9,12 @@ use serde::{Deserialize, Serialize}; use serde_with::serde_as; use zeroize::{Zeroize, ZeroizeOnDrop}; -#[derive(Debug, Clone)] +#[serde_as] +#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq, Eq)] // TODO: Should we rename it to PublicKey or SharedPublicKey? -pub struct PublicKeyShare { - pub public_key_share: E::G1Affine, // A_{i, \omega_i} -} +pub struct PublicKeyShare( + #[serde_as(as = "serialization::SerdeAs")] pub E::G1Affine, // A_{i, \omega_i} +); #[derive(Debug, Clone)] pub struct BlindedKeyShare { @@ -31,7 +32,7 @@ impl BlindedKeyShare { let alpha = E::ScalarField::rand(rng); let alpha_a = E::G1Prepared::from( - g + public_key_share.public_key_share.mul(alpha).into_affine(), + g + public_key_share.0.mul(alpha).into_affine(), ); // \sum_i(Y_i) @@ -56,18 +57,16 @@ impl BlindedKeyShare { #[derive( Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Zeroize, ZeroizeOnDrop, )] -pub struct PrivateKeyShare { - // TODO: Replace with a tuple? - #[serde_as(as = "serialization::SerdeAs")] - pub private_key_share: E::G2Affine, -} +pub struct PrivateKeyShare( + #[serde_as(as = "serialization::SerdeAs")] pub E::G2Affine, +); impl PrivateKeyShare { pub fn blind(&self, b: E::ScalarField) -> BlindedKeyShare { let blinding_key = E::G2Affine::generator().mul(b).into_affine(); BlindedKeyShare:: { blinding_key, - blinded_key_share: self.private_key_share.mul(b).into_affine(), + blinded_key_share: self.0.mul(b).into_affine(), } } } diff --git a/ferveo-tdec/src/lib.rs b/ferveo-tdec/src/lib.rs index 322d1bf9..465b3717 100644 --- a/ferveo-tdec/src/lib.rs +++ b/ferveo-tdec/src/lib.rs @@ -138,9 +138,7 @@ pub mod test_common { ) .enumerate() { - let private_key_share = PrivateKeyShare { - private_key_share: *private, - }; + let private_key_share = PrivateKeyShare(*private); let b = E::ScalarField::rand(rng); let mut blinded_key_shares = private_key_share.blind(b); blinded_key_shares.multiply_by_omega_inv(domain_inv); @@ -159,9 +157,7 @@ pub mod test_common { }); public_contexts.push(PublicDecryptionContextFast:: { domain: *domain, - public_key_share: PublicKeyShare:: { - public_key_share: *public, - }, + public_key_share: PublicKeyShare::(*public), blinded_key_share: blinded_key_shares, lagrange_n_0: *domain, h_inv: E::G2Prepared::from(-h.into_group()), @@ -172,12 +168,8 @@ pub mod test_common { } ( - PublicKeyShare { - public_key_share: pubkey.into(), - }, - PrivateKeyShare { - private_key_share: privkey.into(), - }, + PublicKeyShare(pubkey.into()), + PrivateKeyShare(privkey.into()), private_contexts, ) } @@ -235,9 +227,7 @@ pub mod test_common { izip!(shares_x.iter(), pubkey_shares.iter(), privkey_shares.iter()) .enumerate() { - let private_key_share = PrivateKeyShare:: { - private_key_share: *private, - }; + let private_key_share = PrivateKeyShare::(*private); let b = E::ScalarField::rand(rng); let blinded_key_share = private_key_share.blind(b); private_contexts.push(PrivateDecryptionContextSimple:: { @@ -255,9 +245,7 @@ pub mod test_common { }); public_contexts.push(PublicDecryptionContextSimple:: { domain: *domain, - public_key_share: PublicKeyShare:: { - public_key_share: *public, - }, + public_key_share: PublicKeyShare::(*public), blinded_key_share, h, validator_public_key: h.mul(b), @@ -268,12 +256,8 @@ pub mod test_common { } ( - PublicKeyShare { - public_key_share: pubkey.into(), - }, - PrivateKeyShare { - private_key_share: privkey.into(), - }, + PublicKeyShare(pubkey.into()), + PrivateKeyShare(privkey.into()), private_contexts, ) } diff --git a/ferveo-wasm/examples/node/src/main.test.ts b/ferveo-wasm/examples/node/src/main.test.ts index 00da665b..57071421 100644 --- a/ferveo-wasm/examples/node/src/main.test.ts +++ b/ferveo-wasm/examples/node/src/main.test.ts @@ -62,7 +62,7 @@ function setupTest( // Client creates a ciphertext and requests decryption shares from validators const msg = Buffer.from("my-msg"); const aad = Buffer.from("my-aad"); - const ciphertext = ferveoEncrypt(msg, aad, dkg.publicKey()); + const ciphertext = ferveoEncrypt(msg, aad, clientAggregate.publicKey); return { validatorKeypairs, diff --git a/ferveo-wasm/tests/node.rs b/ferveo-wasm/tests/node.rs index 7b35efa5..5d4ffbb4 100644 --- a/ferveo-wasm/tests/node.rs +++ b/ferveo-wasm/tests/node.rs @@ -80,7 +80,8 @@ fn setup_dkg( // In the meantime, the client creates a ciphertext and decryption request let msg = "my-msg".as_bytes().to_vec(); let aad = "my-aad".as_bytes().to_vec(); - let ciphertext = ferveo_encrypt(&msg, &aad, &dkg.public_key()).unwrap(); + let ciphertext = + ferveo_encrypt(&msg, &aad, &client_aggregate.public_key()).unwrap(); ( validator_keypairs, diff --git a/ferveo/benches/benchmarks/validity_checks.rs b/ferveo/benches/benchmarks/validity_checks.rs index e6a27b74..72fc4946 100644 --- a/ferveo/benches/benchmarks/validity_checks.rs +++ b/ferveo/benches/benchmarks/validity_checks.rs @@ -55,11 +55,14 @@ fn setup_dkg( fn setup( shares_num: u32, rng: &mut StdRng, -) -> (PubliclyVerifiableDkg, Message) { +) -> ( + PubliclyVerifiableDkg, + PubliclyVerifiableSS, +) { let mut transcripts = vec![]; for i in 0..shares_num { - let mut dkg = setup_dkg(i as usize, shares_num); - transcripts.push(dkg.share(rng).expect("Test failed")); + let dkg = setup_dkg(i as usize, shares_num); + transcripts.push(dkg.generate_transcript(rng).expect("Test failed")); } let dkg = setup_dkg(0, shares_num); let transcript = transcripts[0].clone(); @@ -78,20 +81,12 @@ pub fn bench_verify_full(c: &mut Criterion) { let pvss_verify_optimistic = { move || { - if let Message::Deal(ss) = transcript { - black_box(ss.verify_optimistic()); - } else { - panic!("Expected Deal"); - } + black_box(transcript.verify_optimistic()); } }; let pvss_verify_full = { move || { - if let Message::Deal(ss) = transcript { - black_box(ss.verify_full(&dkg)); - } else { - panic!("Expected Deal"); - } + black_box(transcript.verify_full(&dkg).unwrap()); } }; diff --git a/ferveo/examples/bench_primitives_size.rs b/ferveo/examples/bench_primitives_size.rs index d44d394c..755d10e1 100644 --- a/ferveo/examples/bench_primitives_size.rs +++ b/ferveo/examples/bench_primitives_size.rs @@ -91,20 +91,18 @@ fn setup( shares_num: u32, security_threshold: u32, rng: &mut StdRng, -) -> PubliclyVerifiableDkg { +) -> ( + PubliclyVerifiableDkg, + Vec>, +) { let mut transcripts = vec![]; for i in 0..shares_num { - let mut dkg = setup_dkg(i as usize, shares_num, security_threshold); - let message = dkg.share(rng).expect("Test failed"); - let sender = dkg.get_validator(&dkg.me.public_key).unwrap(); - transcripts.push((sender.clone(), message.clone())); - } - - let mut dkg = setup_dkg(0, shares_num, security_threshold); - for (sender, pvss) in transcripts.into_iter() { - dkg.apply_message(&sender, &pvss).expect("Setup failed"); + let dkg = setup_dkg(i as usize, shares_num, security_threshold); + let transcript = dkg.generate_transcript(rng).expect("Test failed"); + transcripts.push(transcript.clone()); } - dkg + let dkg = setup_dkg(0, shares_num, security_threshold); + (dkg, transcripts) } fn main() { @@ -128,9 +126,8 @@ fn main() { for (shares_num, threshold) in configs { println!("shares_num: {shares_num}, threshold: {threshold}"); - let dkg = setup(*shares_num as u32, threshold, rng); - let transcript = &dkg.vss.values().next().unwrap(); - let transcript_bytes = bincode::serialize(&transcript).unwrap(); + let (_, transcripts) = setup(*shares_num as u32, threshold, rng); + let transcript_bytes = bincode::serialize(&transcripts[0]).unwrap(); save_data( *shares_num as usize, diff --git a/ferveo/src/api.rs b/ferveo/src/api.rs index fe25a931..57ae0694 100644 --- a/ferveo/src/api.rs +++ b/ferveo/src/api.rs @@ -11,13 +11,12 @@ pub use ferveo_tdec::api::{ DecryptionSharePrecomputed, Fr, G1Affine, G1Prepared, G2Affine, SecretBox, E, }; -use ferveo_tdec::PublicKeyShare; use generic_array::{ typenum::{Unsigned, U48}, GenericArray, }; use rand::{thread_rng, RngCore}; -use serde::{Deserialize, Serialize}; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; use serde_with::serde_as; #[cfg(feature = "bindings-python")] @@ -26,7 +25,7 @@ use crate::bindings_python; use crate::bindings_wasm; pub use crate::EthereumAddress; use crate::{ - do_verify_aggregation, DomainPoint, Error, Message, PVSSMap, + do_verify_aggregation, DomainPoint, Error, PVSSMap, PubliclyVerifiableParams, PubliclyVerifiableSS, Result, }; @@ -54,17 +53,11 @@ pub fn from_bytes(bytes: &[u8]) -> Result { pub fn encrypt( message: SecretBox>, aad: &[u8], - pubkey: &DkgPublicKey, + public_key: &DkgPublicKey, ) -> Result { - let mut rng = rand::thread_rng(); - let ciphertext = ferveo_tdec::api::encrypt( - message, - aad, - &PublicKeyShare { - public_key_share: pubkey.0, - }, - &mut rng, - )?; + let mut rng = thread_rng(); + let ciphertext = + ferveo_tdec::api::encrypt(message, aad, &public_key.0, &mut rng)?; Ok(Ciphertext(ciphertext)) } @@ -147,16 +140,19 @@ impl From for FerveoVariant { } } -#[serde_as] #[derive(Copy, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] pub struct DkgPublicKey( - // TODO: Consider not using G1Affine directly here - #[serde_as(as = "serialization::SerdeAs")] pub(crate) G1Affine, + #[serde(bound( + serialize = "ferveo_tdec::PublicKeyShare: Serialize", + deserialize = "ferveo_tdec::PublicKeyShare: DeserializeOwned" + ))] + pub(crate) ferveo_tdec::PublicKeyShare, ); +// TODO: Consider moving these implementation details to ferveo_tdec::PublicKeyShare impl DkgPublicKey { pub fn to_bytes(&self) -> Result> { - let as_bytes = to_bytes(&self.0)?; + let as_bytes = to_bytes(&self.0 .0)?; Ok(GenericArray::::from_slice(&as_bytes).to_owned()) } @@ -169,7 +165,8 @@ impl DkgPublicKey { bytes.len(), ) })?; - from_bytes(&bytes).map(DkgPublicKey) + let pk: G1Affine = from_bytes(&bytes)?; + Ok(DkgPublicKey(ferveo_tdec::PublicKeyShare(pk))) } pub fn serialized_size() -> usize { @@ -179,9 +176,9 @@ impl DkgPublicKey { /// Generate a random DKG public key. /// Use this for testing only. pub fn random() -> Self { - let mut rng = rand::thread_rng(); + let mut rng = thread_rng(); let g1 = G1Affine::rand(&mut rng); - Self(g1) + Self(ferveo_tdec::PublicKeyShare(g1)) } } @@ -222,43 +219,23 @@ impl Dkg { Ok(Self(dkg)) } - pub fn public_key(&self) -> DkgPublicKey { - DkgPublicKey(self.0.public_key().public_key_share) - } - pub fn generate_transcript( &mut self, rng: &mut R, ) -> Result { - match self.0.share(rng) { - Ok(Message::Deal(transcript)) => Ok(transcript), - Err(e) => Err(e), - _ => Err(Error::InvalidDkgStateToDeal), - } + self.0.generate_transcript(rng) } pub fn aggregate_transcripts( - &mut self, + &self, messages: &[ValidatorMessage], ) -> Result { - // We must use `deal` here instead of to produce AggregatedTranscript instead of simply - // creating an AggregatedTranscript from the messages, because `deal` also updates the - // internal state of the DKG. - // If we didn't do that, that would cause the DKG to produce incorrect decryption shares - // in the future. - // TODO: Remove this dependency on DKG state - // TODO: Avoid mutating current state here - for (validator, transcript) in messages { - self.0.deal(validator, transcript)?; - } - let pvss = messages - .iter() - .map(|(_, t)| t) - .cloned() - .collect::>>(); - Ok(AggregatedTranscript(crate::pvss::aggregate(&pvss)?)) + self.0 + .aggregate_transcripts(messages) + .map(AggregatedTranscript) } + // TODO: Unused? pub fn public_params(&self) -> DkgPublicParameters { DkgPublicParameters { g1_inv: self.0.pvss_params.g_inv(), @@ -283,16 +260,17 @@ fn make_pvss_map(messages: &[ValidatorMessage]) -> PVSSMap { } #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -pub struct AggregatedTranscript(PubliclyVerifiableSS); +pub struct AggregatedTranscript(crate::AggregatedTranscript); impl AggregatedTranscript { pub fn new(messages: &[ValidatorMessage]) -> Result { - let pvss_list = messages + let transcripts: Vec<_> = messages .iter() - .map(|(_, t)| t) - .cloned() - .collect::>>(); - Ok(AggregatedTranscript(crate::pvss::aggregate(&pvss_list)?)) + .map(|(_, transcript)| transcript.clone()) + .collect(); + let aggregated_transcript = + crate::AggregatedTranscript::::from_transcripts(&transcripts)?; + Ok(AggregatedTranscript(aggregated_transcript)) } pub fn verify( @@ -312,7 +290,7 @@ impl AggregatedTranscript { GeneralEvaluationDomain::::new(validators_num as usize) .expect("Unable to construct an evaluation domain"); - let is_valid_optimistic = self.0.verify_optimistic(); + let is_valid_optimistic = self.0.aggregate.verify_optimistic(); if !is_valid_optimistic { return Err(Error::InvalidTranscriptAggregate); } @@ -326,8 +304,8 @@ impl AggregatedTranscript { // This check also includes `verify_full`. See impl. for details. let is_valid = do_verify_aggregation( - &self.0.coeffs, - &self.0.shares, + &self.0.aggregate.coeffs, + &self.0.aggregate.shares, &pvss_params, &validators, &domain, @@ -353,7 +331,7 @@ impl AggregatedTranscript { dkg.0.dkg_params.security_threshold(), )); } - self.0.create_decryption_share_simple_precomputed( + self.0.aggregate.create_decryption_share_simple_precomputed( &ciphertext_header.0, aad, validator_keypair, @@ -371,7 +349,7 @@ impl AggregatedTranscript { aad: &[u8], validator_keypair: &Keypair, ) -> Result { - let share = self.0.create_decryption_share_simple( + let share = self.0.aggregate.create_decryption_share_simple( &ciphertext_header.0, aad, validator_keypair, @@ -392,11 +370,16 @@ impl AggregatedTranscript { ) -> Result { Ok(PrivateKeyShare( self.0 + .aggregate .decrypt_private_key_share(validator_keypair, share_index)? .0 .clone(), )) } + + pub fn public_key(&self) -> DkgPublicKey { + DkgPublicKey(self.0.public_key) + } } #[serde_as] @@ -404,7 +387,7 @@ impl AggregatedTranscript { pub struct DecryptionShareSimple { share: ferveo_tdec::api::DecryptionShareSimple, #[serde_as(as = "serialization::SerdeAs")] - domain_point: Fr, + domain_point: DomainPoint, } // TODO: Deprecate? @@ -448,8 +431,6 @@ pub fn combine_shares_simple(shares: &[DecryptionShareSimple]) -> SharedSecret { pub struct SharedSecret(pub ferveo_tdec::api::SharedSecret); #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -// TODO: serde is failing to serialize E = ark_bls12_381::Bls12_381 -// pub struct ShareRecoveryUpdate(pub crate::refresh::ShareRecoveryUpdate); pub struct ShareRecoveryUpdate(pub ferveo_tdec::PrivateKeyShare); impl ShareRecoveryUpdate { @@ -676,7 +657,7 @@ mod test_ferveo_api { let messages: Vec<_> = validators .iter() .map(|sender| { - let mut dkg = Dkg::new( + let dkg = Dkg::new( tau, shares_num, security_threshold, @@ -684,7 +665,7 @@ mod test_ferveo_api { sender, ) .unwrap(); - (sender.clone(), dkg.generate_transcript(rng).unwrap()) + (sender.clone(), dkg.0.generate_transcript(rng).unwrap()) }) .collect(); @@ -717,18 +698,16 @@ mod test_ferveo_api { validators_num, ); - // Now that every validator holds a dkg instance and a transcript for every other validator, - // every validator can aggregate the transcripts + // Every validator can aggregate the transcripts let me = validators[0].clone(); - let mut dkg = + let dkg = Dkg::new(TAU, shares_num, security_threshold, &validators, &me) .unwrap(); - let pvss_aggregated = dkg.aggregate_transcripts(&messages).unwrap(); assert!(pvss_aggregated.verify(validators_num, &messages).unwrap()); // At this point, any given validator should be able to provide a DKG public key - let dkg_public_key = dkg.public_key(); + let dkg_public_key = pvss_aggregated.public_key(); // In the meantime, the client creates a ciphertext and decryption request let ciphertext = @@ -740,7 +719,7 @@ mod test_ferveo_api { izip!(&validators, &validator_keypairs) .map(|(validator, validator_keypair)| { // Each validator holds their own instance of DKG and creates their own aggregate - let mut dkg = Dkg::new( + let dkg = Dkg::new( TAU, shares_num, security_threshold, @@ -810,7 +789,7 @@ mod test_ferveo_api { // Now that every validator holds a dkg instance and a transcript for every other validator, // every validator can aggregate the transcripts - let mut dkg = Dkg::new( + let dkg = Dkg::new( TAU, shares_num, security_threshold, @@ -818,12 +797,11 @@ mod test_ferveo_api { &validators[0], ) .unwrap(); - let pvss_aggregated = dkg.aggregate_transcripts(&messages).unwrap(); assert!(pvss_aggregated.verify(validators_num, &messages).unwrap()); // At this point, any given validator should be able to provide a DKG public key - let public_key = dkg.public_key(); + let public_key = pvss_aggregated.public_key(); // In the meantime, the client creates a ciphertext and decryption request let ciphertext = @@ -834,7 +812,7 @@ mod test_ferveo_api { izip!(&validators, &validator_keypairs) .map(|(validator, validator_keypair)| { // Each validator holds their own instance of DKG and creates their own aggregate - let mut dkg = Dkg::new( + let dkg = Dkg::new( TAU, shares_num, security_threshold, @@ -905,10 +883,9 @@ mod test_ferveo_api { // Now that every validator holds a dkg instance and a transcript for every other validator, // every validator can aggregate the transcripts let me = validators[0].clone(); - let mut dkg = + let dkg = Dkg::new(TAU, shares_num, security_threshold, &validators, &me) .unwrap(); - let good_aggregate = dkg.aggregate_transcripts(&messages).unwrap(); assert!(good_aggregate.verify(validators_num, &messages).is_ok()); @@ -923,14 +900,14 @@ mod test_ferveo_api { .is_err()); // Should fail if no transcripts are provided - let mut dkg = + let dkg = Dkg::new(TAU, shares_num, security_threshold, &validators, &me) .unwrap(); let result = dkg.aggregate_transcripts(&[]); assert!(result.is_err()); // Not enough transcripts - let mut dkg = + let dkg = Dkg::new(TAU, shares_num, security_threshold, &validators, &me) .unwrap(); let not_enough_messages = &messages[..security_threshold as usize - 1]; @@ -940,19 +917,51 @@ mod test_ferveo_api { let result = insufficient_aggregate.verify(validators_num, &messages); assert!(result.is_err()); + // Duplicated transcripts + let message_with_duplicated_validator = ( + // Duplicating the validator but with a different, valid transcript + messages[0].0.clone(), + messages[security_threshold as usize].1.clone(), + ); + let mut messages_with_duplicates = messages.clone(); + messages_with_duplicates.push(message_with_duplicated_validator); + // assert_eq!(messages_with_duplicates.len(), security_threshold as usize); + assert!(dkg + .aggregate_transcripts(&messages_with_duplicates) + .is_err()); + + // TODO: Transcripts are not hashable? + // let message_with_duplicated_transcript = ( + // // Duplicating the transcript but with a different, valid validator + // validators[security_threshold as usize - 1].clone(), + // messages[security_threshold as usize - 1].1.clone(), + // ); + // let messages_with_duplicates = [ + // &messages[..(security_threshold - 1) as usize], + // &[message_with_duplicated_transcript], + // ] + // .concat(); + // assert_eq!(messages_with_duplicates.len(), security_threshold as usize); + // assert!(dkg + // .aggregate_transcripts(&messages_with_duplicates) + // .is_err()); + // Unexpected transcripts in the aggregate or transcripts from a different ritual // Using same DKG parameters, but different DKG instances and validators let mut dkg = Dkg::new(TAU, shares_num, security_threshold, &validators, &me) .unwrap(); - let (bad_messages, _, _) = make_test_inputs( - rng, - TAU, - security_threshold, - shares_num, - validators_num, + let bad_message = ( + // Reusing a good validator, but giving them a bad transcript + messages[security_threshold as usize - 1].0.clone(), + dkg.generate_transcript(rng).unwrap(), ); - let mixed_messages = [&messages[..2], &bad_messages[..1]].concat(); + let mixed_messages = [ + &messages[..(security_threshold - 1) as usize], + &[bad_message], + ] + .concat(); + assert_eq!(mixed_messages.len(), security_threshold as usize); let bad_aggregate = dkg.aggregate_transcripts(&mixed_messages).unwrap(); let result = bad_aggregate.verify(validators_num, &messages); assert!(result.is_err()); @@ -1041,7 +1050,7 @@ mod test_ferveo_api { shares_num, validators_num, ); - let mut dkgs = validators + let dkgs = validators .iter() .map(|validator| { Dkg::new( @@ -1054,17 +1063,25 @@ mod test_ferveo_api { .unwrap() }) .collect::>(); - let pvss_aggregated = dkgs[0].aggregate_transcripts(&messages).unwrap(); + + // Creating a copy to avoiding accidentally changing DKG state + let mut dkg = dkgs[0].clone(); + let pvss_aggregated = dkg.aggregate_transcripts(&messages).unwrap(); assert!(pvss_aggregated.verify(validators_num, &messages).unwrap()); // Create an initial shared secret for testing purposes - let public_key = &dkgs[0].public_key(); + let public_key = pvss_aggregated.public_key(); let ciphertext = - encrypt(SecretBox::new(MSG.to_vec()), AAD, public_key).unwrap(); + encrypt(SecretBox::new(MSG.to_vec()), AAD, &public_key).unwrap(); let ciphertext_header = ciphertext.header().unwrap(); + for (validator, transcript) in messages.iter() { + dkg.0 + .vss + .insert(validator.address.clone(), transcript.clone()); + } let (_, _, old_shared_secret) = crate::test_dkg_full::create_shared_secret_simple_tdec( - &dkgs[0].0, + &dkg.0, AAD, &ciphertext_header.0, validator_keypairs.as_slice(), diff --git a/ferveo/src/bindings_python.rs b/ferveo/src/bindings_python.rs index f6fce72c..a229568c 100644 --- a/ferveo/src/bindings_python.rs +++ b/ferveo/src/bindings_python.rs @@ -58,8 +58,8 @@ impl From for PyErr { Error::DuplicateDealer(dealer) => { DuplicateDealer::new_err(dealer.to_string()) } - Error::InvalidPvssTranscript => { - InvalidPvssTranscript::new_err("") + Error::InvalidPvssTranscript(validator_addr) => { + InvalidPvssTranscript::new_err(validator_addr.to_string()) } Error::InsufficientTranscriptsForAggregate( expected, @@ -124,6 +124,11 @@ impl From for PyErr { Error::UnknownValidator(validator) => { UnknownValidator::new_err(validator.to_string()) }, + Error::TooManyTranscripts(expected, received) => { + TooManyTranscripts::new_err(format!( + "expected: {expected}, received: {received}" + )) + } // Remember to create Python exceptions using `create_exception!` macro, and to register them in the // `make_ferveo_py_module` function. You will have to update the `ferveo/__init__.{py, pyi}` files too. }, @@ -174,6 +179,7 @@ create_exception!( PyValueError ); create_exception!(exceptions, UnknownValidator, PyValueError); +create_exception!(exceptions, TooManyTranscripts, PyValueError); fn from_py_bytes(bytes: &[u8]) -> PyResult { T::from_bytes(bytes) @@ -516,11 +522,6 @@ impl Dkg { Ok(Self(dkg)) } - #[getter] - pub fn public_key(&self) -> DkgPublicKey { - DkgPublicKey(self.0.public_key()) - } - pub fn generate_transcript(&mut self) -> PyResult { let rng = &mut thread_rng(); let transcript = self @@ -669,6 +670,11 @@ impl AggregatedTranscript { .map_err(FerveoPythonError::FerveoError)?; Ok(DecryptionShareSimple(decryption_share)) } + + #[getter] + pub fn public_key(&self) -> DkgPublicKey { + DkgPublicKey(self.0.public_key()) + } } // Since adding functions in pyo3 requires a two-step process @@ -789,6 +795,7 @@ pub fn make_ferveo_py_module(py: Python<'_>, m: &PyModule) -> PyResult<()> { py.get_type::(), )?; m.add("UnknownValidator", py.get_type::())?; + m.add("TooManyTranscripts", py.get_type::())?; Ok(()) } @@ -883,7 +890,7 @@ mod test_ferveo_python { .unwrap()); // At this point, any given validator should be able to provide a DKG public key - let dkg_public_key = dkg.public_key(); + let dkg_public_key = pvss_aggregated.public_key(); // In the meantime, the client creates a ciphertext and decryption request let ciphertext = encrypt(MSG.to_vec(), AAD, &dkg_public_key).unwrap(); @@ -961,7 +968,7 @@ mod test_ferveo_python { .unwrap()); // At this point, any given validator should be able to provide a DKG public key - let dkg_public_key = dkg.public_key(); + let dkg_public_key = pvss_aggregated.public_key(); // In the meantime, the client creates a ciphertext and decryption request let ciphertext = encrypt(MSG.to_vec(), AAD, &dkg_public_key).unwrap(); diff --git a/ferveo/src/bindings_wasm.rs b/ferveo/src/bindings_wasm.rs index 3c885269..56325092 100644 --- a/ferveo/src/bindings_wasm.rs +++ b/ferveo/src/bindings_wasm.rs @@ -360,11 +360,6 @@ impl Dkg { Ok(Self(dkg)) } - #[wasm_bindgen(js_name = "publicKey")] - pub fn public_key(&self) -> DkgPublicKey { - DkgPublicKey(self.0.public_key()) - } - #[wasm_bindgen(js_name = "generateTranscript")] pub fn generate_transcript(&mut self) -> JsResult { let rng = &mut thread_rng(); @@ -496,6 +491,14 @@ impl ValidatorMessage { #[derive(Clone, Debug, Serialize, Deserialize)] pub struct AggregatedTranscript(api::AggregatedTranscript); +#[wasm_bindgen] +impl AggregatedTranscript { + #[wasm_bindgen(getter, js_name = "publicKey")] + pub fn public_key(&self) -> DkgPublicKey { + DkgPublicKey(self.0.public_key()) + } +} + generate_common_methods!(AggregatedTranscript); #[wasm_bindgen] diff --git a/ferveo/src/dkg.rs b/ferveo/src/dkg.rs index 1ee6ec6e..fe1e18c2 100644 --- a/ferveo/src/dkg.rs +++ b/ferveo/src/dkg.rs @@ -1,21 +1,20 @@ -use std::collections::BTreeMap; +use std::collections::{BTreeMap, HashSet}; -use ark_ec::{pairing::Pairing, AffineRepr, CurveGroup}; +use ark_ec::pairing::Pairing; use ark_poly::EvaluationDomain; use ark_std::UniformRand; use ferveo_common::PublicKey; use measure_time::print_time; use rand::RngCore; -use serde::{de::DeserializeOwned, Deserialize, Serialize}; -use serde_with::serde_as; +use serde::{Deserialize, Serialize}; use crate::{ - aggregate, assert_no_share_duplicates, AggregatedPvss, Error, - EthereumAddress, PubliclyVerifiableParams, PubliclyVerifiableSS, Result, - Validator, + assert_no_share_duplicates, AggregatedTranscript, Error, EthereumAddress, + PubliclyVerifiableParams, PubliclyVerifiableSS, Result, Validator, }; pub type DomainPoint = ::ScalarField; +pub type ValidatorMessage = (Validator, PubliclyVerifiableSS); #[derive(Copy, Clone, Debug, Serialize, Deserialize)] pub struct DkgParams { @@ -68,29 +67,6 @@ impl DkgParams { pub type ValidatorsMap = BTreeMap>; pub type PVSSMap = BTreeMap>; -#[derive(Debug, Clone)] -pub enum DkgState { - // TODO: Do we need to keep track of the block number? - Sharing { - accumulated_shares: u32, - block: u32, - }, - Dealt, - Success { - public_key: ferveo_tdec::PublicKeyShare, - }, - Invalid, -} - -impl DkgState { - fn new() -> Self { - DkgState::Sharing { - accumulated_shares: 0, - block: 0, - } - } -} - /// The DKG context that holds all the local state for participating in the DKG // TODO: Consider removing Clone to avoid accidentally NOT-mutating state. // Currently, we're assuming that the DKG is only mutated by the owner of the instance. @@ -100,10 +76,10 @@ pub struct PubliclyVerifiableDkg { pub dkg_params: DkgParams, pub pvss_params: PubliclyVerifiableParams, pub validators: ValidatorsMap, + // TODO: Remove vss? pub vss: PVSSMap, pub domain: ark_poly::GeneralEvaluationDomain, pub me: Validator, - state: DkgState, } impl PubliclyVerifiableDkg { @@ -145,10 +121,10 @@ impl PubliclyVerifiableDkg { domain, me: me.clone(), validators, - state: DkgState::new(), }) } + /// Get the validator with for the given public key pub fn get_validator( &self, public_key: &PublicKey, @@ -159,48 +135,25 @@ impl PubliclyVerifiableDkg { } /// Create a new PVSS instance within this DKG session, contributing to the final key - /// `rng` is a cryptographic random number generator - /// Returns a PVSS dealing message to post on-chain - pub fn share(&mut self, rng: &mut R) -> Result> { + pub fn generate_transcript( + &self, + rng: &mut R, + ) -> Result> { print_time!("PVSS Sharing"); - match self.state { - DkgState::Sharing { .. } | DkgState::Dealt => { - let vss = PubliclyVerifiableSS::::new( - &DomainPoint::::rand(rng), - self, - rng, - )?; - Ok(Message::Deal(vss)) - } - _ => Err(Error::InvalidDkgStateToDeal), - } + PubliclyVerifiableSS::::new(&DomainPoint::::rand(rng), self, rng) } /// Aggregate all received PVSS messages into a single message, prepared to post on-chain - pub fn aggregate(&self) -> Result> { - match self.state { - DkgState::Dealt => { - let public_key = self.public_key(); - let pvss_list = self.vss.values().cloned().collect::>(); - Ok(Message::Aggregate(Aggregation { - vss: aggregate(&pvss_list)?, - public_key: public_key.public_key_share, - })) - } - _ => Err(Error::InvalidDkgStateToAggregate), - } - } - - /// Returns the public key generated by the DKG - pub fn public_key(&self) -> ferveo_tdec::PublicKeyShare { - ferveo_tdec::PublicKeyShare { - public_key_share: self - .vss - .values() - .map(|vss| vss.coeffs[0].into_group()) - .sum::() - .into_affine(), - } + pub fn aggregate_transcripts( + &self, + messages: &[ValidatorMessage], + ) -> Result> { + self.verify_transcripts(messages)?; + let transcripts: Vec> = messages + .iter() + .map(|(_sender, transcript)| transcript.clone()) + .collect(); + AggregatedTranscript::::from_transcripts(&transcripts) } /// Return a domain point for the share_index @@ -212,10 +165,12 @@ impl PubliclyVerifiableDkg { } /// Return an appropriate amount of domain points for the DKG + /// The number of domain points should be equal to the number of validators pub fn domain_points(&self) -> Vec> { self.domain.elements().take(self.validators.len()).collect() } + /// Remove a validator from the DKG pub fn offboard_validator( &mut self, address: &EthereumAddress, @@ -228,142 +183,48 @@ impl PubliclyVerifiableDkg { } } - pub fn verify_message( + /// Verify PVSS transcripts against the set of validators in the DKG + // TODO: Make private? + pub fn verify_transcripts( &self, - sender: &Validator, - message: &Message, + messages: &[ValidatorMessage], ) -> Result<()> { - match message { - Message::Deal(pvss) - if matches!( - self.state, - DkgState::Sharing { .. } | DkgState::Dealt - ) => - { - if !self.validators.contains_key(&sender.address) { - Err(Error::UnknownDealer(sender.clone().address)) - } else if self.vss.contains_key(&sender.address) { - Err(Error::DuplicateDealer(sender.clone().address)) - } else if !pvss.verify_optimistic() { - Err(Error::InvalidPvssTranscript) - } else { - Ok(()) - } - } - Message::Aggregate(Aggregation { vss, public_key }) - if matches!(self.state, DkgState::Dealt) => - { - let minimum_shares = self.dkg_params.shares_num - - self.dkg_params.security_threshold; - let actual_shares = vss.shares.len() as u32; - // We reject aggregations that fail to meet the security threshold - if actual_shares < minimum_shares { - Err(Error::InsufficientTranscriptsForAggregate( - minimum_shares, - actual_shares, - )) - } else if vss.verify_aggregation(self).is_err() { - Err(Error::InvalidTranscriptAggregate) - } else if &self.public_key().public_key_share == public_key { - Ok(()) - } else { - Err(Error::InvalidDkgPublicKey) - } + let mut validator_set = HashSet::::new(); + // TODO: Transcripts are not hashable? + // let mut transcript_set = HashSet::>::new(); + for (sender, transcript) in messages.iter() { + let sender = &sender.address; + if !self.validators.contains_key(sender) { + return Err(Error::UnknownDealer(sender.clone())); + } else if validator_set.contains(sender) { + return Err(Error::DuplicateDealer(sender.clone())); + // } else if !transcript_set.contains(transcript) { + // return Err(Error::DuplicateTranscript(sender.clone())); + } else if !transcript.verify_optimistic() { + return Err(Error::InvalidPvssTranscript(sender.clone())); + } else { + validator_set.insert(sender.clone()); + // transcript_set.insert(sender.clone()); } - _ => Err(Error::InvalidDkgStateToVerify), } - } - /// After consensus has agreed to include a verified message on the blockchain, - /// we apply the chains to the state machine - pub fn apply_message( - &mut self, - sender: &Validator, - payload: &Message, - ) -> Result<()> { - match payload { - Message::Deal(pvss) - if matches!( - self.state, - DkgState::Sharing { .. } | DkgState::Dealt - ) => - { - if !self.validators.contains_key(&sender.address) { - return Err(Error::UnknownDealer(sender.clone().address)); - } - - // TODO: Throw error instead of silently accepting excess shares? - // if self.vss.len() < self.dkg_params.shares_num as usize { - // self.vss.insert(sender.address.clone(), pvss.clone()); - // } - self.vss.insert(sender.address.clone(), pvss.clone()); - - // we keep track of the amount of shares seen until the security - // threshold is met. Then we may change the state of the DKG - if let DkgState::Sharing { - ref mut accumulated_shares, - .. - } = &mut self.state - { - *accumulated_shares += 1; - if *accumulated_shares >= self.dkg_params.security_threshold - { - self.state = DkgState::Dealt; - } - } - Ok(()) - } - Message::Aggregate(_) if matches!(self.state, DkgState::Dealt) => { - // change state and cache the final key - self.state = DkgState::Success { - public_key: self.public_key(), - }; - Ok(()) - } - _ => Err(Error::InvalidDkgStateToIngest), + if validator_set.len() > self.validators.len() { + return Err(Error::TooManyTranscripts( + self.validators.len() as u32, + validator_set.len() as u32, + )); } - } + // if transcript_set.len() > self.validators.len() { + // return Err(Error::TooManyTranscripts( + // self.validators.len() as u32, + // transcript_set.len() as u32, + // )); + // } - pub fn deal( - &mut self, - sender: &Validator, - pvss: &PubliclyVerifiableSS, - ) -> Result<()> { - // Add the ephemeral public key and pvss transcript - let (sender_address, _) = self - .validators - .iter() - .find(|(probe_address, _)| sender.address == **probe_address) - .ok_or_else(|| Error::UnknownDealer(sender.address.clone()))?; - self.vss.insert(sender_address.clone(), pvss.clone()); Ok(()) } } -#[serde_as] -#[derive(Serialize, Deserialize, Clone, Debug)] -#[serde(bound( - serialize = "AggregatedPvss: Serialize", - deserialize = "AggregatedPvss: DeserializeOwned" -))] -pub struct Aggregation { - vss: AggregatedPvss, - #[serde_as(as = "ferveo_common::serialization::SerdeAs")] - public_key: E::G1Affine, -} - -// TODO: Remove these? -// TODO: These messages are not actually used anywhere, we use our own ValidatorMessage for Deal, and Aggregate for Message.Aggregate -#[derive(Serialize, Deserialize, Clone, Debug)] -#[serde(bound( - serialize = "AggregatedPvss: Serialize, PubliclyVerifiableSS: Serialize", - deserialize = "AggregatedPvss: DeserializeOwned, PubliclyVerifiableSS: DeserializeOwned" -))] -pub enum Message { - Deal(PubliclyVerifiableSS), - Aggregate(Aggregation), -} - /// Test initializing DKG #[cfg(test)] mod test_dkg_init { @@ -399,12 +260,8 @@ mod test_dkg_init { /// Test the dealing phase of the DKG #[cfg(test)] mod test_dealing { - use ark_ec::AffineRepr; - use ferveo_tdec::PublicKeyShare; - use crate::{ - test_common::*, DkgParams, DkgState, DkgState::Dealt, Error, - PubliclyVerifiableDkg, Validator, + test_common::*, DkgParams, Error, PubliclyVerifiableDkg, Validator, }; /// Check that the canonical share indices of validators are expected and enforced @@ -434,67 +291,25 @@ mod test_dealing { ); } - /// Test that dealing correct PVSS transcripts - /// pass verification an application and that - /// state is updated correctly + /// Test that dealing correct PVSS transcripts passes validation #[test] fn test_pvss_dealing() { let rng = &mut ark_std::test_rng(); + let (dkg, _) = setup_dkg(0); + let messages = make_messages(rng, &dkg); - // Create a test DKG instance - let (mut dkg, _) = setup_dkg(0); - - // Gather everyone's transcripts - let mut messages = vec![]; - for i in 0..dkg.dkg_params.shares_num() { - let (mut dkg, _) = setup_dkg(i as usize); - let message = dkg.share(rng).unwrap(); - let sender = dkg.me.clone(); - messages.push((sender, message)); - } - - let mut expected = 0u32; - for (sender, pvss) in messages.iter() { - // Check the verification passes - assert!(dkg.verify_message(sender, pvss).is_ok()); - - // Check that application passes - assert!(dkg.apply_message(sender, pvss).is_ok()); - - expected += 1; - if expected < dkg.dkg_params.security_threshold { - // check that shares accumulates correctly - match dkg.state { - DkgState::Sharing { - accumulated_shares, .. - } => { - assert_eq!(accumulated_shares, expected) - } - _ => panic!("Test failed"), - } - } else { - // Check that when enough shares is accumulated, we transition state - assert!(matches!(dkg.state, DkgState::Dealt)); - } - } + assert!(dkg.verify_transcripts(&messages).is_ok()); } - /// Test the verification and application of - /// pvss transcripts from unknown validators - /// are rejected + /// Test the verification and application of pvss transcripts from + /// unknown validators are rejected #[test] fn test_pvss_from_unknown_dealer_rejected() { let rng = &mut ark_std::test_rng(); - let (mut dkg, _) = setup_dkg(0); - assert!(matches!( - dkg.state, - DkgState::Sharing { - accumulated_shares: 0, - block: 0 - } - )); - let pvss = dkg.share(rng).unwrap(); - // Need to make sure this falls outside of the validator set: + let (dkg, _) = setup_dkg(0); + let mut messages = make_messages(rng, &dkg); + + // Need to make sure this falls outside the validator set: let unknown_validator_index = dkg.dkg_params.shares_num + VALIDATORS_NUM + 1; let sender = Validator:: { @@ -502,255 +317,75 @@ mod test_dealing { public_key: ferveo_common::Keypair::::new(rng).public_key(), share_index: unknown_validator_index, }; - // check that verification fails - assert!(dkg.verify_message(&sender, &pvss).is_err()); - // check that application fails - assert!(dkg.apply_message(&sender, &pvss).is_err()); - // check that state has not changed - assert!(matches!( - dkg.state, - DkgState::Sharing { - accumulated_shares: 0, - block: 0, - } - )); + let transcript = dkg.generate_transcript(rng).unwrap(); + messages.push((sender, transcript)); + + assert!(dkg.verify_transcripts(&messages).is_err()); } - /// Test that if a validator sends two pvss transcripts, - /// the second fails to verify + /// Test that if a validator sends two pvss transcripts, the second fails to verify #[test] fn test_pvss_sent_twice_rejected() { let rng = &mut ark_std::test_rng(); - let (mut dkg, _) = setup_dkg(0); - // We start with an empty state - assert!(matches!( - dkg.state, - DkgState::Sharing { - accumulated_shares: 0, - block: 0, - } - )); + let (dkg, _) = setup_dkg(0); + let mut messages = make_messages(rng, &dkg); - let pvss = dkg.share(rng).unwrap(); - - // This validator has already sent a PVSS - let sender = dkg.me.clone(); - - // First PVSS is accepted - assert!(dkg.verify_message(&sender, &pvss).is_ok()); - assert!(dkg.apply_message(&sender, &pvss).is_ok()); - assert!(matches!( - dkg.state, - DkgState::Sharing { - accumulated_shares: 1, - block: 0, - } - )); + messages.push(messages[0].clone()); - // Second PVSS is rejected - assert!(dkg.verify_message(&sender, &pvss).is_err()); + assert!(dkg.verify_transcripts(&messages).is_err()); } - /// Test that if a validators tries to verify it's own - /// share message, it passes + /// Test that if a validators tries to verify its own share message, it passes #[test] fn test_own_pvss() { let rng = &mut ark_std::test_rng(); - let (mut dkg, _) = setup_dkg(0); - // We start with an empty state - assert!(matches!( - dkg.state, - DkgState::Sharing { - accumulated_shares: 0, - block: 0, - } - )); - - // Sender creates a PVSS transcript - let pvss = dkg.share(rng).unwrap(); - // Note that state of DKG has not changed - assert!(matches!( - dkg.state, - DkgState::Sharing { - accumulated_shares: 0, - block: 0, - } - )); - - let sender = dkg.me.clone(); - - // Sender verifies it's own PVSS transcript - assert!(dkg.verify_message(&sender, &pvss).is_ok()); - assert!(dkg.apply_message(&sender, &pvss).is_ok()); - assert!(matches!( - dkg.state, - DkgState::Sharing { - accumulated_shares: 1, - block: 0, - } - )); - } - - /// Test that the [`PubliclyVerifiableDkg::share`] method - /// errors if its state is not [`DkgState::Shared{..} | Dkg::Dealt`] - #[test] - fn test_pvss_cannot_share_from_wrong_state() { - let rng = &mut ark_std::test_rng(); - let (mut dkg, _) = setup_dkg(0); - assert!(matches!( - dkg.state, - DkgState::Sharing { - accumulated_shares: 0, - block: 0, - } - )); - - dkg.state = DkgState::Success { - public_key: PublicKeyShare { - public_key_share: G1::zero(), - }, - }; - assert!(dkg.share(rng).is_err()); - - // check that even if security threshold is met, we can still share - dkg.state = Dealt; - assert!(dkg.share(rng).is_ok()); - } - - /// Check that share messages can only be - /// verified or applied if the dkg is in - /// state [`DkgState::Share{..} | DkgState::Dealt`] - #[test] - fn test_share_message_state_guards() { - let rng = &mut ark_std::test_rng(); - let (mut dkg, _) = setup_dkg(0); - let pvss = dkg.share(rng).unwrap(); - assert!(matches!( - dkg.state, - DkgState::Sharing { - accumulated_shares: 0, - block: 0, - } - )); + let (dkg, _) = setup_dkg(0); + let messages = make_messages(rng, &dkg) + .iter() + .take(1) + .cloned() + .collect::>(); - let sender = dkg.me.clone(); - dkg.state = DkgState::Success { - public_key: PublicKeyShare { - public_key_share: G1::zero(), - }, - }; - assert!(dkg.verify_message(&sender, &pvss).is_err()); - assert!(dkg.apply_message(&sender, &pvss).is_err()); - - // check that we can still accept pvss transcripts after meeting threshold - dkg.state = Dealt; - assert!(dkg.verify_message(&sender, &pvss).is_ok()); - assert!(dkg.apply_message(&sender, &pvss).is_ok()); - assert!(matches!(dkg.state, DkgState::Dealt)) + assert!(dkg.verify_transcripts(&messages).is_ok()); } } /// Test aggregating transcripts into final key #[cfg(test)] mod test_aggregation { - use ark_ec::AffineRepr; - use ferveo_tdec::PublicKeyShare; use test_case::test_case; - use crate::{dkg::*, test_common::*, DkgState, Message}; + use crate::test_common::*; /// Test that if the security threshold is met, we can create a final key #[test_case(4, 4; "number of validators equal to the number of shares")] #[test_case(4, 6; "number of validators greater than the number of shares")] - fn test_aggregate(shares_num: u32, validators_num: u32) { - let security_threshold = shares_num - 1; - let (mut dkg, _) = setup_dealt_dkg_with_n_validators( - security_threshold, - shares_num, - validators_num, - ); - let aggregate_msg = dkg.aggregate().unwrap(); - if let Message::Aggregate(Aggregation { public_key, .. }) = - &aggregate_msg - { - assert_eq!(public_key, &dkg.public_key().public_key_share); - } else { - panic!("Expected aggregate message") - } - let sender = dkg.me.clone(); - assert!(dkg.verify_message(&sender, &aggregate_msg).is_ok()); - assert!(dkg.apply_message(&sender, &aggregate_msg).is_ok()); - assert!(matches!(dkg.state, DkgState::Success { .. })); - } - - /// Test that aggregate only succeeds if we are in the state [`DkgState::Dealt] - #[test] - fn test_aggregate_state_guards() { - let (mut dkg, _) = setup_dealt_dkg(); - dkg.state = DkgState::Sharing { - accumulated_shares: 0, - block: 0, - }; - assert!(dkg.aggregate().is_err()); - dkg.state = DkgState::Success { - public_key: PublicKeyShare { - public_key_share: G1::zero(), - }, - }; - assert!(dkg.aggregate().is_err()); - } - - /// Test that aggregate message fail to be verified or applied unless - /// dkg.state is [`DkgState::Dealt`] - #[test] - fn test_aggregate_message_state_guards() { - let (mut dkg, _) = setup_dealt_dkg(); - let aggregate = dkg.aggregate().unwrap(); - let sender = dkg.me.clone(); - - dkg.state = DkgState::Sharing { - accumulated_shares: 0, - block: 0, - }; - assert!(dkg.verify_message(&sender, &aggregate).is_err()); - assert!(dkg.apply_message(&sender, &aggregate).is_err()); - - dkg.state = DkgState::Success { - public_key: PublicKeyShare { - public_key_share: G1::zero(), - }, - }; - assert!(dkg.verify_message(&sender, &aggregate).is_err()); - assert!(dkg.apply_message(&sender, &aggregate).is_err()) - } + fn test_aggregate(shares_num: u32, _validators_num: u32) { + let _security_threshold = shares_num - 1; + let rng = &mut ark_std::test_rng(); + let (dkg, _) = setup_dkg(0); + let all_messages = make_messages(rng, &dkg); - /// Test that an aggregate message will fail to verify if the - /// security threshold is not met - #[test] - fn test_aggregate_wont_verify_if_under_threshold() { - let (mut dkg, _) = setup_dealt_dkg(); - dkg.dkg_params.shares_num = 10; - let aggregate = dkg.aggregate().unwrap(); - let sender = dkg.me.clone(); - assert!(dkg.verify_message(&sender, &aggregate).is_err()); - } + let not_enough_messages = all_messages + .iter() + .take((dkg.dkg_params.security_threshold - 1) as usize) + .cloned() + .collect::>(); + let bad_aggregate = + dkg.aggregate_transcripts(¬_enough_messages).unwrap(); - /// If the aggregated pvss passes, check that the announced - /// key is correct. Verification should fail if it is not - #[test] - fn test_aggregate_wont_verify_if_wrong_key() { - let (dkg, _) = setup_dealt_dkg(); - let mut aggregate = dkg.aggregate().unwrap(); - while dkg.public_key().public_key_share == G1::zero() { - let (_dkg, _) = setup_dealt_dkg(); - } - if let Message::Aggregate(Aggregation { public_key, .. }) = - &mut aggregate - { - *public_key = G1::zero(); - } - let sender = dkg.me.clone(); - assert!(dkg.verify_message(&sender, &aggregate).is_err()); + let enough_messages = all_messages + .iter() + .take((dkg.dkg_params.security_threshold) as usize) + .cloned() + .collect::>(); + let good_aggregate_1 = + dkg.aggregate_transcripts(&enough_messages).unwrap(); + assert_ne!(bad_aggregate, good_aggregate_1); + + let good_aggregate_2 = + dkg.aggregate_transcripts(&all_messages).unwrap(); + assert_ne!(good_aggregate_1, good_aggregate_2); } } diff --git a/ferveo/src/lib.rs b/ferveo/src/lib.rs index ed39fc4b..94128a01 100644 --- a/ferveo/src/lib.rs +++ b/ferveo/src/lib.rs @@ -59,8 +59,8 @@ pub enum Error { DuplicateDealer(EthereumAddress), /// DKG received an invalid transcript for which optimistic verification failed - #[error("DKG received an invalid transcript")] - InvalidPvssTranscript, + #[error("DKG received an invalid transcript from validator: {0}")] + InvalidPvssTranscript(EthereumAddress), /// Aggregation failed because the DKG did not receive enough PVSS transcripts #[error( @@ -125,6 +125,14 @@ pub enum Error { /// Validator not found in the DKG set of validators #[error("Validator not found: {0}")] UnknownValidator(EthereumAddress), + + /// Too many transcripts received by the DKG + #[error("Too many transcripts. Expected: {0}, got: {1}")] + TooManyTranscripts(u32, u32), + // TODO: Transcripts are not hashable? + // /// Received a duplicated transcript from a validator + // #[error("Received a duplicated transcript from validator: {0}")] + // DuplicateTranscript(EthereumAddress), } pub type Result = std::result::Result; @@ -156,13 +164,14 @@ mod test_dkg_full { ciphertext_header: &ferveo_tdec::CiphertextHeader, validator_keypairs: &[Keypair], ) -> ( - PubliclyVerifiableSS, + AggregatedTranscript, Vec>, SharedSecret, ) { - let pvss_list = dkg.vss.values().cloned().collect::>(); - let pvss_aggregated = aggregate(&pvss_list).unwrap(); - assert!(pvss_aggregated.verify_aggregation(dkg).is_ok()); + let transcripts = dkg.vss.values().cloned().collect::>(); + let pvss_aggregated = + AggregatedTranscript::from_transcripts(&transcripts).unwrap(); + assert!(pvss_aggregated.aggregate.verify_aggregation(dkg).is_ok()); let decryption_shares: Vec> = validator_keypairs @@ -172,6 +181,7 @@ mod test_dkg_full { .get_validator(&validator_keypair.public_key()) .unwrap(); pvss_aggregated + .aggregate .create_decryption_share_simple( ciphertext_header, aad, @@ -213,7 +223,10 @@ mod test_dkg_full { validators_num, ); - let public_key = dkg.public_key(); + let transcripts = dkg.vss.values().cloned().collect::>(); + let public_key = AggregatedTranscript::from_transcripts(&transcripts) + .unwrap() + .public_key; let ciphertext = ferveo_tdec::encrypt::( SecretBox::new(MSG.to_vec()), AAD, @@ -252,7 +265,11 @@ mod test_dkg_full { shares_num, validators_num, ); - let public_key = dkg.public_key(); + let transcripts = dkg.vss.values().cloned().collect::>(); + let pvss_aggregated = + AggregatedTranscript::from_transcripts(&transcripts).unwrap(); + pvss_aggregated.aggregate.verify_aggregation(&dkg).unwrap(); + let public_key = pvss_aggregated.public_key; let ciphertext = ferveo_tdec::encrypt::( SecretBox::new(MSG.to_vec()), AAD, @@ -261,9 +278,6 @@ mod test_dkg_full { ) .unwrap(); - let pvss_list = dkg.vss.values().cloned().collect::>(); - let pvss_aggregated = aggregate(&pvss_list).unwrap(); - pvss_aggregated.verify_aggregation(&dkg).unwrap(); let domain_points = dkg .domain .elements() @@ -278,6 +292,7 @@ mod test_dkg_full { .get_validator(&validator_keypair.public_key()) .unwrap(); pvss_aggregated + .aggregate .create_decryption_share_simple_precomputed( &ciphertext.header().unwrap(), AAD, @@ -321,7 +336,10 @@ mod test_dkg_full { shares_num, validators_num, ); - let public_key = dkg.public_key(); + let transcripts = dkg.vss.values().cloned().collect::>(); + let public_key = AggregatedTranscript::from_transcripts(&transcripts) + .unwrap() + .public_key; let ciphertext = ferveo_tdec::encrypt::( SecretBox::new(MSG.to_vec()), AAD, @@ -339,7 +357,7 @@ mod test_dkg_full { ); izip!( - &pvss_aggregated.shares, + &pvss_aggregated.aggregate.shares, &validator_keypairs, &decryption_shares, ) @@ -361,7 +379,7 @@ mod test_dkg_full { let mut with_bad_decryption_share = decryption_share.clone(); with_bad_decryption_share.decryption_share = TargetField::zero(); assert!(!with_bad_decryption_share.verify( - &pvss_aggregated.shares[0], + &pvss_aggregated.aggregate.shares[0], &validator_keypairs[0].public_key().encryption_key, &dkg.pvss_params.h, &ciphertext, @@ -371,7 +389,7 @@ mod test_dkg_full { let mut with_bad_checksum = decryption_share; with_bad_checksum.validator_checksum.checksum = G1Affine::zero(); assert!(!with_bad_checksum.verify( - &pvss_aggregated.shares[0], + &pvss_aggregated.aggregate.shares[0], &validator_keypairs[0].public_key().encryption_key, &dkg.pvss_params.h, &ciphertext, @@ -393,11 +411,14 @@ mod test_dkg_full { shares_num, validators_num, ); - let public_key = &dkg.public_key(); + let transcripts = dkg.vss.values().cloned().collect::>(); + let public_key = AggregatedTranscript::from_transcripts(&transcripts) + .unwrap() + .public_key; let ciphertext = ferveo_tdec::encrypt::( SecretBox::new(MSG.to_vec()), AAD, - public_key, + &public_key, rng, ) .unwrap(); @@ -469,10 +490,9 @@ mod test_dkg_full { .unwrap(); // Creates updated private key shares - // TODO: Use self.aggregate upon simplifying Message handling - let pvss_list = dkg.vss.values().cloned().collect::>(); - let pvss_aggregated = aggregate(&pvss_list).unwrap(); - pvss_aggregated + AggregatedTranscript::from_transcripts(&transcripts) + .unwrap() + .aggregate .create_updated_private_key_share( validator_keypair, validator.share_index, @@ -496,11 +516,9 @@ mod test_dkg_full { .iter() .enumerate() .map(|(share_index, validator_keypair)| { - // TODO: Use self.aggregate upon simplifying Message handling - let pvss_list = - dkg.vss.values().cloned().collect::>(); - let pvss_aggregated = aggregate(&pvss_list).unwrap(); - pvss_aggregated + AggregatedTranscript::from_transcripts(&transcripts) + .unwrap() + .aggregate .create_decryption_share_simple( &ciphertext.header().unwrap(), AAD, @@ -563,11 +581,14 @@ mod test_dkg_full { shares_num, validators_num, ); - let public_key = &dkg.public_key(); + let transcripts = dkg.vss.values().cloned().collect::>(); + let public_key = AggregatedTranscript::from_transcripts(&transcripts) + .unwrap() + .public_key; let ciphertext = ferveo_tdec::encrypt::( SecretBox::new(MSG.to_vec()), AAD, - public_key, + &public_key, rng, ) .unwrap(); @@ -619,10 +640,9 @@ mod test_dkg_full { .unwrap(); // Creates updated private key shares - // TODO: Use self.aggregate upon simplifying Message handling - let pvss_list = dkg.vss.values().cloned().collect::>(); - let pvss_aggregated = aggregate(&pvss_list).unwrap(); - pvss_aggregated + AggregatedTranscript::from_transcripts(&transcripts) + .unwrap() + .aggregate .create_updated_private_key_share( validator_keypair, validator.share_index, diff --git a/ferveo/src/pvss.rs b/ferveo/src/pvss.rs index e98dffe4..003a411e 100644 --- a/ferveo/src/pvss.rs +++ b/ferveo/src/pvss.rs @@ -1,4 +1,4 @@ -use std::{marker::PhantomData, ops::Mul}; +use std::{hash::Hash, marker::PhantomData, ops::Mul}; use ark_ec::{pairing::Pairing, AffineRepr, CurveGroup, Group}; use ark_ff::{Field, Zero}; @@ -12,7 +12,7 @@ use ferveo_tdec::{ }; use itertools::Itertools; use rand::RngCore; -use serde::{Deserialize, Serialize}; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; use serde_with::serde_as; use subproductdomain::fast_multiexp; use zeroize::{self, Zeroize, ZeroizeOnDrop}; @@ -28,7 +28,7 @@ use crate::{ pub type ShareEncryptions = ::G2Affine; /// Marker struct for unaggregated PVSS transcripts -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, Hash)] pub struct Unaggregated; /// Marker struct for aggregated PVSS transcripts @@ -100,9 +100,8 @@ impl Drop for SecretPolynomial { impl ZeroizeOnDrop for SecretPolynomial {} -/// Each validator posts a transcript to the chain. Once enough -/// validators have done this (their total voting power exceeds -/// 2/3 the total), this will be aggregated into a final key +/// Each validator posts a transcript to the chain. Once enough (threshold) validators have done, +/// these will be aggregated into a final key #[serde_as] #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] pub struct PubliclyVerifiableSS { @@ -205,7 +204,7 @@ impl PubliclyVerifiableSS { /// If aggregation fails, a validator needs to know that their pvss /// transcript was at fault so that the can issue a new one. This /// function may also be used for that purpose. - pub fn verify_full(&self, dkg: &PubliclyVerifiableDkg) -> bool { + pub fn verify_full(&self, dkg: &PubliclyVerifiableDkg) -> Result { let validators = dkg.validators.values().cloned().collect::>(); do_verify_full( &self.coeffs, @@ -224,14 +223,14 @@ pub fn do_verify_full( pvss_params: &PubliclyVerifiableParams, validators: &[Validator], domain: &ark_poly::GeneralEvaluationDomain, -) -> bool { +) -> Result { + assert_no_share_duplicates(validators)?; + let mut commitment = batch_to_projective_g1::(pvss_coefficients); domain.fft_in_place(&mut commitment); - assert_no_share_duplicates(validators).expect("Validators must be unique"); - // Each validator checks that their share is correct - validators + Ok(validators .iter() .zip(pvss_encrypted_shares.iter()) .enumerate() @@ -246,7 +245,7 @@ pub fn do_verify_full( // See #4 in 4.2.3 section of https://eprint.iacr.org/2022/898.pdf // e(G,Y) = e(A, ek) E::pairing(pvss_params.g, *y_i) == E::pairing(a_i, ek_i) - }) + })) } pub fn do_verify_aggregation( @@ -263,7 +262,7 @@ pub fn do_verify_aggregation( pvss_params, validators, domain, - ); + )?; if !is_valid { return Err(Error::InvalidTranscriptAggregate); } @@ -317,9 +316,9 @@ impl PubliclyVerifiableSS { ), ) .into_affine(); - Ok(PrivateKeyShare(ferveo_tdec::PrivateKeyShare { + Ok(PrivateKeyShare(ferveo_tdec::PrivateKeyShare( private_key_share, - })) + ))) } /// Make a decryption share (simple variant) for a given ciphertext @@ -379,13 +378,45 @@ impl PubliclyVerifiableSS { } } +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] +pub struct AggregatedTranscript { + #[serde(bound( + serialize = "PubliclyVerifiableSS: Serialize", + deserialize = "PubliclyVerifiableSS: DeserializeOwned" + ))] + pub aggregate: PubliclyVerifiableSS, + #[serde(bound( + serialize = "ferveo_tdec::PublicKeyShare: Serialize", + deserialize = "ferveo_tdec::PublicKeyShare: DeserializeOwned" + ))] + pub public_key: ferveo_tdec::PublicKeyShare, +} + +impl AggregatedTranscript { + pub fn from_transcripts( + transcripts: &[PubliclyVerifiableSS], + ) -> Result { + let aggregate = aggregate(transcripts)?; + let public_key = transcripts + .iter() + .map(|pvss| pvss.coeffs[0].into_group()) + .sum::() + .into_affine(); + let public_key = ferveo_tdec::PublicKeyShare::(public_key); + Ok(AggregatedTranscript { + aggregate, + public_key, + }) + } +} + /// Aggregate the PVSS instances in `pvss` from DKG session `dkg` /// into a new PVSS instance /// See: https://nikkolasg.github.io/ferveo/pvss.html?highlight=aggregate#aggregation -pub(crate) fn aggregate( - pvss_list: &[PubliclyVerifiableSS], +fn aggregate( + transcripts: &[PubliclyVerifiableSS], ) -> Result> { - let mut pvss_iter = pvss_list.iter(); + let mut pvss_iter = transcripts.iter(); let first_pvss = pvss_iter .next() .ok_or_else(|| Error::NoTranscriptsToAggregate)?; @@ -394,7 +425,7 @@ pub(crate) fn aggregate( let mut shares = batch_to_projective_g2::(&first_pvss.shares); - // So now we're iterating over the PVSS instances, and adding their coefficients and shares, and their sigma + // So now we're iterating over the PVSS instances, and adding their coefficients and shares, and their // sigma is the sum of all the sigma_i, which is the proof of knowledge of the secret polynomial // Aggregating is just adding the corresponding values in PVSS instances, so PVSS_i = PVSS_(i-1) PVSS_i for next_pvss in pvss_iter { @@ -428,6 +459,22 @@ mod test_pvss { use super::*; use crate::test_common::*; + /// Test that an aggregate message will fail to verify if the + /// security threshold is not met + #[test] + fn test_aggregate_wont_verify_if_under_threshold() { + let (_dkg, _) = setup_dealt_dkg_with_n_transcript_dealt( + SECURITY_THRESHOLD, + SHARES_NUM, + VALIDATORS_NUM, + SECURITY_THRESHOLD - 1, + ); + // TODO: Fix after rewriting dkg.vss + // let messages = dkg.vss.iter().map(|(v, t)| (v.clone(), t.clone())).collect::>(); + // let aggregate = dkg.aggregate_transcripts(&messages).unwrap(); + // assert!(aggregate.aggregate.verify_aggregation(&dkg).unwrap()); + } + /// Test the happy flow such that the PVSS with the correct form is created /// and that appropriate validations pass #[test_case(4, 4; "number of validators is equal to the number of shares")] @@ -453,12 +500,12 @@ mod test_pvss { ); // Check that the correct number of shares were created assert_eq!(pvss.shares.len(), dkg.validators.len()); - // Check that the prove of knowledge is correct + // Check that the proof of knowledge is correct assert_eq!(pvss.sigma, G2::generator().mul(s)); // Check that the optimistic verify returns true assert!(pvss.verify_optimistic()); // Check that the full verify returns true - assert!(pvss.verify_full(&dkg)); + assert!(pvss.verify_full(&dkg).unwrap()); } /// Check that if the proof of knowledge is wrong, @@ -491,7 +538,7 @@ mod test_pvss { // So far, everything works assert!(pvss.verify_optimistic()); - assert!(pvss.verify_full(&dkg)); + assert!(pvss.verify_full(&dkg).unwrap()); // Now, we're going to tamper with the PVSS shares let mut bad_pvss = pvss; @@ -500,7 +547,7 @@ mod test_pvss { // Optimistic verification should not catch this issue assert!(bad_pvss.verify_optimistic()); // Full verification should catch this issue - assert!(!bad_pvss.verify_full(&dkg)); + assert!(!bad_pvss.verify_full(&dkg).unwrap()); } /// Check that happy flow of aggregating PVSS transcripts @@ -526,9 +573,9 @@ mod test_pvss { // Check that the optimistic verify returns true assert!(aggregate.verify_optimistic()); // Check that the full verify returns true - assert!(aggregate.verify_full(&dkg)); + assert!(aggregate.verify_full(&dkg).unwrap()); // Check that the verification of aggregation passes - assert!(aggregate.verify_aggregation(&dkg).expect("Test failed"),); + assert!(aggregate.verify_aggregation(&dkg).expect("Test failed")); } /// Check that if the aggregated PVSS transcript has an diff --git a/ferveo/src/refresh.rs b/ferveo/src/refresh.rs index 87797a9b..619eff1c 100644 --- a/ferveo/src/refresh.rs +++ b/ferveo/src/refresh.rs @@ -37,12 +37,8 @@ impl PrivateKeyShare { ) -> UpdatedPrivateKeyShare { let updated_key_share = share_updates .iter() - .fold(self.0.private_key_share, |acc, delta| { - (acc + delta.inner().private_key_share).into() - }); - let updated_key_share = InnerPrivateKeyShare { - private_key_share: updated_key_share, - }; + .fold(self.0 .0, |acc, delta| (acc + delta.inner().0).into()); + let updated_key_share = ferveo_tdec::PrivateKeyShare(updated_key_share); UpdatedPrivateKeyShare(updated_key_share) } @@ -56,11 +52,9 @@ impl PrivateKeyShare { // Interpolate new shares to recover y_r let lagrange = lagrange_basis_at::(domain_points, x_r); let prods = zip_eq(updated_private_shares, lagrange) - .map(|(y_j, l)| y_j.0.private_key_share.mul(l)); + .map(|(y_j, l)| y_j.0 .0.mul(l)); let y_r = prods.fold(E::G2::zero(), |acc, y_j| acc + y_j); - PrivateKeyShare(ferveo_tdec::PrivateKeyShare { - private_key_share: y_r.into_affine(), - }) + PrivateKeyShare(ferveo_tdec::PrivateKeyShare(y_r.into_affine())) } pub fn create_decryption_share_simple( @@ -226,9 +220,7 @@ fn prepare_share_updates_with_root( let eval = d_i.evaluate(x_i); h.mul(eval).into_affine() }) - .map(|p| InnerPrivateKeyShare { - private_key_share: p, - }) + .map(ferveo_tdec::PrivateKeyShare) .collect() } diff --git a/ferveo/src/test_common.rs b/ferveo/src/test_common.rs index dce10e5d..d947317c 100644 --- a/ferveo/src/test_common.rs +++ b/ferveo/src/test_common.rs @@ -1,12 +1,16 @@ /// Factory functions and variables for testing use std::str::FromStr; +use ark_bls12_381::Bls12_381; pub use ark_bls12_381::Bls12_381 as E; use ark_ec::pairing::Pairing; use ferveo_common::Keypair; -use rand::seq::SliceRandom; +use rand::{seq::SliceRandom, Rng}; -use crate::{DkgParams, EthereumAddress, PubliclyVerifiableDkg, Validator}; +use crate::{ + DkgParams, EthereumAddress, PubliclyVerifiableDkg, PubliclyVerifiableSS, + Validator, +}; pub type ScalarField = ::ScalarField; pub type G1 = ::G1Affine; @@ -80,6 +84,8 @@ pub fn setup_dealt_dkg() -> TestSetup { setup_dealt_dkg_with(SECURITY_THRESHOLD, SHARES_NUM) } +// TODO: Rewrite setup_utils to return messages separately + pub fn setup_dealt_dkg_with( security_threshold: u32, shares_num: u32, @@ -95,21 +101,49 @@ pub fn setup_dealt_dkg_with_n_validators( security_threshold: u32, shares_num: u32, validators_num: u32, +) -> TestSetup { + setup_dealt_dkg_with_n_transcript_dealt( + security_threshold, + shares_num, + validators_num, + security_threshold, + ) +} + +pub fn make_messages( + rng: &mut (impl Rng + Sized), + dkg: &PubliclyVerifiableDkg, +) -> Vec<(Validator, PubliclyVerifiableSS)> { + let mut messages = vec![]; + for i in 0..dkg.dkg_params.shares_num() { + let (dkg, _) = setup_dkg(i as usize); + let transcript = dkg.generate_transcript(rng).unwrap(); + let sender = dkg.me.clone(); + messages.push((sender, transcript)); + } + messages +} + +pub fn setup_dealt_dkg_with_n_transcript_dealt( + security_threshold: u32, + shares_num: u32, + validators_num: u32, + transcripts_to_use: u32, ) -> TestSetup { let rng = &mut ark_std::test_rng(); // Gather everyone's transcripts - let mut messages: Vec<_> = (0..validators_num) + let mut transcripts: Vec<_> = (0..validators_num) .map(|my_index| { - let (mut dkg, _) = setup_dkg_for_n_validators( + let (dkg, _) = setup_dkg_for_n_validators( security_threshold, shares_num, my_index as usize, validators_num, ); let me = dkg.me.clone(); - let message = dkg.share(rng).unwrap(); - (me, message) + let transcript = dkg.generate_transcript(rng).unwrap(); + (me, transcript) }) .collect(); @@ -122,9 +156,15 @@ pub fn setup_dealt_dkg_with_n_validators( ); // The ordering of messages should not matter - messages.shuffle(rng); - messages.iter().for_each(|(sender, message)| { - dkg.apply_message(sender, message).expect("Setup failed"); - }); + transcripts.shuffle(rng); + // Use only the first `transcripts_to_use` transcripts + transcripts + .iter() + .take(transcripts_to_use as usize) + .for_each(|(sender, message)| { + // TODO: How to do this in user-facing API? + // TODO: just return transcripts after getting rid of the dkg.vss + dkg.vss.insert(sender.address.clone(), message.clone()); + }); (dkg, keypairs) }