From 315d2b4cc2825e13820d9c64639490c44b538385 Mon Sep 17 00:00:00 2001 From: Piotr Roslaniec Date: Wed, 14 Feb 2024 16:10:01 +0100 Subject: [PATCH 1/6] feature!: remove state from dkg, part 1 --- .../examples/server_api_precomputed.py | 2 +- ferveo-python/examples/server_api_simple.py | 2 +- ferveo-python/ferveo/__init__.py | 1 + ferveo-python/ferveo/__init__.pyi | 4 + ferveo-python/test/test_ferveo.py | 2 +- ferveo-python/test/test_serialization.py | 6 +- ferveo-tdec/src/ciphertext.rs | 5 +- ferveo-tdec/src/decryption.rs | 30 +- ferveo-tdec/src/key_share.rs | 21 +- ferveo-tdec/src/lib.rs | 32 +- ferveo-wasm/examples/node/src/main.test.ts | 2 +- ferveo-wasm/tests/node.rs | 3 +- ferveo/benches/benchmarks/validity_checks.rs | 21 +- ferveo/examples/bench_primitives_size.rs | 25 +- ferveo/src/api.rs | 195 +++--- ferveo/src/bindings_python.rs | 25 +- ferveo/src/bindings_wasm.rs | 13 +- ferveo/src/dkg.rs | 583 ++++-------------- ferveo/src/lib.rs | 84 ++- ferveo/src/pvss.rs | 95 ++- ferveo/src/refresh.rs | 18 +- ferveo/src/test_common.rs | 60 +- 22 files changed, 483 insertions(+), 746 deletions(-) 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 dd8e40bd..77372255 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,18 +260,17 @@ fn make_pvss_map(messages: &[ValidatorMessage]) -> PVSSMap { } #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -pub struct AggregatedTranscript( - pub(crate) 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( @@ -314,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); } @@ -328,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, @@ -355,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, @@ -373,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, @@ -394,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] @@ -406,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? @@ -450,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 { @@ -678,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, @@ -686,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(); @@ -719,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 = @@ -742,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, @@ -812,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, @@ -820,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 = @@ -836,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, @@ -907,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()); @@ -926,7 +901,7 @@ mod test_ferveo_api { )); // Should fail if no transcripts are provided - let mut dkg = + let dkg = Dkg::new(TAU, shares_num, security_threshold, &validators, &me) .unwrap(); assert!(matches!( @@ -935,7 +910,7 @@ mod test_ferveo_api { )); // 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]; @@ -947,19 +922,51 @@ mod test_ferveo_api { Err(Error::InvalidTranscriptAggregate) )); + // 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(); assert!(matches!( bad_aggregate.verify(validators_num, &messages), @@ -1058,7 +1065,7 @@ mod test_ferveo_api { shares_num, validators_num, ); - let mut dkgs = validators + let dkgs = validators .iter() .map(|validator| { Dkg::new( @@ -1071,17 +1078,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 44ef5dc6..4409fb72 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 { @@ -206,7 +205,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, @@ -225,14 +224,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() @@ -247,7 +246,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( @@ -264,7 +263,7 @@ pub fn do_verify_aggregation( pvss_params, validators, domain, - ); + )?; if !is_valid { return Err(Error::InvalidTranscriptAggregate); } @@ -318,9 +317,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 @@ -380,13 +379,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)?; @@ -429,6 +460,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")] @@ -454,12 +501,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, @@ -492,7 +539,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; @@ -501,7 +548,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 @@ -527,9 +574,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) } From c9f1adc19198464d99d5759391e6b967ab505a70 Mon Sep 17 00:00:00 2001 From: Piotr Roslaniec Date: Mon, 19 Feb 2024 19:55:53 +0100 Subject: [PATCH 2/6] feature!: remove state from dkg, part 2 --- ferveo-python/ferveo/__init__.py | 1 + ferveo-python/ferveo/__init__.pyi | 3 + ferveo/src/api.rs | 139 ++++++++++-------------------- ferveo/src/bindings_python.rs | 5 ++ ferveo/src/dkg.rs | 46 +++------- ferveo/src/lib.rs | 99 ++++++++++++--------- ferveo/src/pvss.rs | 58 +++++++------ ferveo/src/refresh.rs | 6 +- ferveo/src/test_common.rs | 33 ++++--- 9 files changed, 178 insertions(+), 212 deletions(-) diff --git a/ferveo-python/ferveo/__init__.py b/ferveo-python/ferveo/__init__.py index 5088675d..209741b2 100644 --- a/ferveo-python/ferveo/__init__.py +++ b/ferveo-python/ferveo/__init__.py @@ -41,4 +41,5 @@ InvalidAggregateVerificationParameters, UnknownValidator, TooManyTranscripts, + DuplicateTranscript, ) diff --git a/ferveo-python/ferveo/__init__.pyi b/ferveo-python/ferveo/__init__.pyi index f4b5fd9f..e738f2ab 100644 --- a/ferveo-python/ferveo/__init__.pyi +++ b/ferveo-python/ferveo/__init__.pyi @@ -226,3 +226,6 @@ class UnknownValidator(Exception): class TooManyTranscripts(Exception): pass + +class DuplicateTranscript(Exception): + pass diff --git a/ferveo/src/api.rs b/ferveo/src/api.rs index 77372255..80c350ce 100644 --- a/ferveo/src/api.rs +++ b/ferveo/src/api.rs @@ -25,8 +25,8 @@ use crate::bindings_python; use crate::bindings_wasm; pub use crate::EthereumAddress; use crate::{ - do_verify_aggregation, DomainPoint, Error, PVSSMap, - PubliclyVerifiableParams, PubliclyVerifiableSS, Result, + do_verify_aggregation, DomainPoint, Error, PubliclyVerifiableParams, + PubliclyVerifiableSS, Result, }; pub type PublicKey = ferveo_common::PublicKey; @@ -66,12 +66,12 @@ pub fn decrypt_with_shared_secret( aad: &[u8], shared_secret: &SharedSecret, ) -> Result> { - let dkg_public_params = DkgPublicParameters::default(); + let g_inv = PubliclyVerifiableParams::::default().g_inv(); ferveo_tdec::api::decrypt_with_shared_secret( &ciphertext.0, aad, &shared_secret.0, - &dkg_public_params.g1_inv, + &g_inv, ) .map_err(Error::from) } @@ -235,13 +235,6 @@ impl Dkg { .map(AggregatedTranscript) } - // TODO: Unused? - pub fn public_params(&self) -> DkgPublicParameters { - DkgPublicParameters { - g1_inv: self.0.pvss_params.g_inv(), - } - } - pub fn me(&self) -> &Validator { &self.0.me } @@ -251,14 +244,6 @@ impl Dkg { } } -fn make_pvss_map(messages: &[ValidatorMessage]) -> PVSSMap { - let mut pvss_map: PVSSMap = PVSSMap::new(); - messages.iter().for_each(|(validator, transcript)| { - pvss_map.insert(validator.address.clone(), transcript.clone()); - }); - pvss_map -} - #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct AggregatedTranscript(crate::AggregatedTranscript); @@ -285,33 +270,34 @@ impl AggregatedTranscript { )); } - let pvss_params = PubliclyVerifiableParams::::default(); let domain = GeneralEvaluationDomain::::new(validators_num as usize) .expect("Unable to construct an evaluation domain"); - let is_valid_optimistic = self.0.aggregate.verify_optimistic(); if !is_valid_optimistic { return Err(Error::InvalidTranscriptAggregate); } - let pvss_map = make_pvss_map(messages); + let pvss_params = PubliclyVerifiableParams::::default(); let validators: Vec<_> = messages .iter() .map(|(validator, _)| validator) .cloned() .collect(); - + let pvss_list = messages + .iter() + .map(|(_validator, transcript)| transcript) + .cloned() + .collect::>(); // This check also includes `verify_full`. See impl. for details. - let is_valid = do_verify_aggregation( + do_verify_aggregation( &self.0.aggregate.coeffs, &self.0.aggregate.shares, &pvss_params, &validators, &domain, - &pvss_map, - )?; - Ok(is_valid) + &pvss_list, + ) } // TODO: Consider deprecating in favor of PrivateKeyShare::create_decryption_share_simple @@ -354,7 +340,6 @@ impl AggregatedTranscript { aad, validator_keypair, dkg.0.me.share_index, - &dkg.0.pvss_params.g_inv(), )?; let domain_point = dkg.0.get_domain_point(dkg.0.me.share_index)?; Ok(DecryptionShareSimple { @@ -390,32 +375,6 @@ pub struct DecryptionShareSimple { domain_point: DomainPoint, } -// TODO: Deprecate? -#[serde_as] -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -pub struct DkgPublicParameters { - #[serde_as(as = "serialization::SerdeAs")] - pub(crate) g1_inv: G1Prepared, -} - -impl Default for DkgPublicParameters { - fn default() -> Self { - DkgPublicParameters { - g1_inv: PubliclyVerifiableParams::::default().g_inv(), - } - } -} - -impl DkgPublicParameters { - pub fn from_bytes(bytes: &[u8]) -> Result { - bincode::deserialize(bytes).map_err(|e| e.into()) - } - - pub fn to_bytes(&self) -> Result> { - bincode::serialize(self).map_err(|e| e.into()) - } -} - pub fn combine_shares_simple(shares: &[DecryptionShareSimple]) -> SharedSecret { // Pick domain points that are corresponding to the shares we have. let domain_points: Vec<_> = shares.iter().map(|s| s.domain_point).collect(); @@ -575,7 +534,6 @@ impl PrivateKeyShare { &ciphertext_header.0, aad, validator_keypair, - &dkg.public_params().g1_inv, )?; let domain_point = dkg.0.get_domain_point(dkg.0.me.share_index)?; Ok(DecryptionShareSimple { @@ -593,7 +551,7 @@ impl PrivateKeyShare { share_index: u32, domain_points: &[DomainPoint], ) -> Result { - let dkg_public_params = DkgPublicParameters::default(); + let g_inv = PubliclyVerifiableParams::::default().g_inv(); let share = crate::PrivateKeyShare(self.0.clone()) .create_decryption_share_simple_precomputed( &ciphertext_header.0, @@ -601,7 +559,7 @@ impl PrivateKeyShare { validator_keypair, share_index, domain_points, - &dkg_public_params.g1_inv, + &g_inv, )?; Ok(share) } @@ -923,33 +881,33 @@ mod test_ferveo_api { )); // 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); + let messages_with_duplicated_transcript = [ + ( + validators[security_threshold as usize - 1].clone(), + messages[security_threshold as usize - 1].1.clone(), + ), + ( + validators[security_threshold as usize - 1].clone(), + messages[security_threshold as usize - 2].1.clone(), + ), + ]; assert!(dkg - .aggregate_transcripts(&messages_with_duplicates) + .aggregate_transcripts(&messages_with_duplicated_transcript) .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()); + let messages_with_duplicated_transcript = [ + ( + validators[security_threshold as usize - 1].clone(), + messages[security_threshold as usize - 1].1.clone(), + ), + ( + validators[security_threshold as usize - 2].clone(), + messages[security_threshold as usize - 1].1.clone(), + ), + ]; + assert!(dkg + .aggregate_transcripts(&messages_with_duplicated_transcript) + .is_err()); // Unexpected transcripts in the aggregate or transcripts from a different ritual // Using same DKG parameters, but different DKG instances and validators @@ -1080,7 +1038,7 @@ mod test_ferveo_api { .collect::>(); // Creating a copy to avoiding accidentally changing DKG state - let mut dkg = dkgs[0].clone(); + let dkg = dkgs[0].clone(); let pvss_aggregated = dkg.aggregate_transcripts(&messages).unwrap(); assert!(pvss_aggregated.verify(validators_num, &messages).unwrap()); @@ -1089,17 +1047,18 @@ mod test_ferveo_api { let ciphertext = 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 transcripts = messages + .iter() + .map(|(_, transcript)| transcript) + .cloned() + .collect::>(); let (_, _, old_shared_secret) = crate::test_dkg_full::create_shared_secret_simple_tdec( &dkg.0, AAD, &ciphertext_header.0, validator_keypairs.as_slice(), + &transcripts, ); ( @@ -1157,13 +1116,7 @@ mod test_ferveo_api { messages.pop().unwrap(); dkgs.pop(); validator_keypairs.pop().unwrap(); - let removed_validator = validators.pop().unwrap(); - for dkg in dkgs.iter_mut() { - dkg.0 - .offboard_validator(&removed_validator.address) - .expect("Unable to off-board a validator from the DKG context"); - } // Now, we're going to recover a new share at a random point or at a specific point // and check that the shared secret is still the same. diff --git a/ferveo/src/bindings_python.rs b/ferveo/src/bindings_python.rs index a229568c..8505ae42 100644 --- a/ferveo/src/bindings_python.rs +++ b/ferveo/src/bindings_python.rs @@ -129,6 +129,9 @@ impl From for PyErr { "expected: {expected}, received: {received}" )) } + Error::DuplicateTranscript(validator) => { + DuplicateTranscript::new_err(validator.to_string()) + } // 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. }, @@ -180,6 +183,7 @@ create_exception!( ); create_exception!(exceptions, UnknownValidator, PyValueError); create_exception!(exceptions, TooManyTranscripts, PyValueError); +create_exception!(exceptions, DuplicateTranscript, PyValueError); fn from_py_bytes(bytes: &[u8]) -> PyResult { T::from_bytes(bytes) @@ -796,6 +800,7 @@ pub fn make_ferveo_py_module(py: Python<'_>, m: &PyModule) -> PyResult<()> { )?; m.add("UnknownValidator", py.get_type::())?; m.add("TooManyTranscripts", py.get_type::())?; + m.add("DuplicateTranscript", py.get_type::())?; Ok(()) } diff --git a/ferveo/src/dkg.rs b/ferveo/src/dkg.rs index fe1e18c2..087b3069 100644 --- a/ferveo/src/dkg.rs +++ b/ferveo/src/dkg.rs @@ -76,8 +76,6 @@ 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, } @@ -117,7 +115,6 @@ impl PubliclyVerifiableDkg { Ok(Self { dkg_params: *dkg_params, pvss_params: PubliclyVerifiableParams::::default(), - vss: PVSSMap::::new(), domain, me: me.clone(), validators, @@ -170,42 +167,26 @@ impl PubliclyVerifiableDkg { self.domain.elements().take(self.validators.len()).collect() } - /// Remove a validator from the DKG - pub fn offboard_validator( - &mut self, - address: &EthereumAddress, - ) -> Result> { - if let Some(validator) = self.validators.remove(address) { - self.vss.remove(address); - Ok(validator) - } else { - Err(Error::UnknownValidator(address.clone())) - } - } - /// Verify PVSS transcripts against the set of validators in the DKG - // TODO: Make private? - pub fn verify_transcripts( + fn verify_transcripts( &self, messages: &[ValidatorMessage], ) -> Result<()> { let mut validator_set = HashSet::::new(); - // TODO: Transcripts are not hashable? - // let mut transcript_set = HashSet::>::new(); + 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_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()); } + validator_set.insert(sender.clone()); + transcript_set.insert(transcript.clone()); } if validator_set.len() > self.validators.len() { @@ -214,12 +195,12 @@ impl PubliclyVerifiableDkg { 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, - // )); - // } + if transcript_set.len() > self.validators.len() { + return Err(Error::TooManyTranscripts( + self.validators.len() as u32, + transcript_set.len() as u32, + )); + } Ok(()) } @@ -360,8 +341,7 @@ mod test_aggregation { /// 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; + fn test_aggregate(_shares_num: u32, _validators_num: u32) { let rng = &mut ark_std::test_rng(); let (dkg, _) = setup_dkg(0); let all_messages = make_messages(rng, &dkg); diff --git a/ferveo/src/lib.rs b/ferveo/src/lib.rs index 94128a01..5d52a4db 100644 --- a/ferveo/src/lib.rs +++ b/ferveo/src/lib.rs @@ -129,10 +129,10 @@ pub enum Error { /// 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), + + /// Received a duplicated transcript from a validator + #[error("Received a duplicated transcript from validator: {0}")] + DuplicateTranscript(EthereumAddress), } pub type Result = std::result::Result; @@ -163,15 +163,18 @@ mod test_dkg_full { aad: &[u8], ciphertext_header: &ferveo_tdec::CiphertextHeader, validator_keypairs: &[Keypair], + transcripts: &[PubliclyVerifiableSS], ) -> ( AggregatedTranscript, Vec>, SharedSecret, ) { - let transcripts = dkg.vss.values().cloned().collect::>(); let pvss_aggregated = - AggregatedTranscript::from_transcripts(&transcripts).unwrap(); - assert!(pvss_aggregated.aggregate.verify_aggregation(dkg).is_ok()); + AggregatedTranscript::from_transcripts(transcripts).unwrap(); + assert!(pvss_aggregated + .aggregate + .verify_aggregation(dkg, transcripts) + .is_ok()); let decryption_shares: Vec> = validator_keypairs @@ -187,7 +190,6 @@ mod test_dkg_full { aad, validator_keypair, validator.share_index, - &dkg.pvss_params.g_inv(), ) .unwrap() }) @@ -217,13 +219,14 @@ mod test_dkg_full { let rng = &mut test_rng(); let security_threshold = shares_num / 2 + 1; - let (dkg, validator_keypairs) = setup_dealt_dkg_with_n_validators( - security_threshold, - shares_num, - validators_num, - ); - - let transcripts = dkg.vss.values().cloned().collect::>(); + let (dkg, validator_keypairs, messages) = + setup_dealt_dkg_with_n_validators( + security_threshold, + shares_num, + validators_num, + ); + let transcripts = + messages.iter().map(|m| m.1.clone()).collect::>(); let public_key = AggregatedTranscript::from_transcripts(&transcripts) .unwrap() .public_key; @@ -240,6 +243,7 @@ mod test_dkg_full { AAD, &ciphertext.header().unwrap(), validator_keypairs.as_slice(), + &transcripts, ); let plaintext = ferveo_tdec::decrypt_with_shared_secret( @@ -260,15 +264,20 @@ mod test_dkg_full { // In precomputed variant, threshold must be equal to shares_num let security_threshold = shares_num; - let (dkg, validator_keypairs) = setup_dealt_dkg_with_n_validators( - security_threshold, - shares_num, - validators_num, - ); - let transcripts = dkg.vss.values().cloned().collect::>(); + let (dkg, validator_keypairs, messangers) = + setup_dealt_dkg_with_n_validators( + security_threshold, + shares_num, + validators_num, + ); + let transcripts = + messangers.iter().map(|m| m.1.clone()).collect::>(); let pvss_aggregated = AggregatedTranscript::from_transcripts(&transcripts).unwrap(); - pvss_aggregated.aggregate.verify_aggregation(&dkg).unwrap(); + pvss_aggregated + .aggregate + .verify_aggregation(&dkg, &transcripts) + .unwrap(); let public_key = pvss_aggregated.public_key; let ciphertext = ferveo_tdec::encrypt::( SecretBox::new(MSG.to_vec()), @@ -331,12 +340,14 @@ mod test_dkg_full { let rng = &mut test_rng(); let security_threshold = shares_num / 2 + 1; - let (dkg, validator_keypairs) = setup_dealt_dkg_with_n_validators( - security_threshold, - shares_num, - validators_num, - ); - let transcripts = dkg.vss.values().cloned().collect::>(); + let (dkg, validator_keypairs, messages) = + setup_dealt_dkg_with_n_validators( + security_threshold, + shares_num, + validators_num, + ); + let transcripts = + messages.iter().map(|m| m.1.clone()).collect::>(); let public_key = AggregatedTranscript::from_transcripts(&transcripts) .unwrap() .public_key; @@ -354,6 +365,7 @@ mod test_dkg_full { AAD, &ciphertext.header().unwrap(), validator_keypairs.as_slice(), + &transcripts, ); izip!( @@ -406,12 +418,14 @@ mod test_dkg_full { let rng = &mut test_rng(); let security_threshold = shares_num / 2 + 1; - let (dkg, validator_keypairs) = setup_dealt_dkg_with_n_validators( - security_threshold, - shares_num, - validators_num, - ); - let transcripts = dkg.vss.values().cloned().collect::>(); + let (dkg, validator_keypairs, messages) = + setup_dealt_dkg_with_n_validators( + security_threshold, + shares_num, + validators_num, + ); + let transcripts = + messages.iter().map(|m| m.1.clone()).collect::>(); let public_key = AggregatedTranscript::from_transcripts(&transcripts) .unwrap() .public_key; @@ -429,6 +443,7 @@ mod test_dkg_full { AAD, &ciphertext.header().unwrap(), validator_keypairs.as_slice(), + &transcripts, ); // Remove one participant from the contexts and all nested structure @@ -524,7 +539,6 @@ mod test_dkg_full { AAD, validator_keypair, share_index as u32, - &dkg.pvss_params.g_inv(), ) .unwrap() }) @@ -576,12 +590,14 @@ mod test_dkg_full { let rng = &mut test_rng(); let security_threshold = shares_num / 2 + 1; - let (dkg, validator_keypairs) = setup_dealt_dkg_with_n_validators( - security_threshold, - shares_num, - validators_num, - ); - let transcripts = dkg.vss.values().cloned().collect::>(); + let (dkg, validator_keypairs, messages) = + setup_dealt_dkg_with_n_validators( + security_threshold, + shares_num, + validators_num, + ); + let transcripts = + messages.iter().map(|m| m.1.clone()).collect::>(); let public_key = AggregatedTranscript::from_transcripts(&transcripts) .unwrap() .public_key; @@ -599,6 +615,7 @@ mod test_dkg_full { AAD, &ciphertext.header().unwrap(), validator_keypairs.as_slice(), + &transcripts, ); // Each participant prepares an update for each other participant diff --git a/ferveo/src/pvss.rs b/ferveo/src/pvss.rs index 4409fb72..ed3a8ef3 100644 --- a/ferveo/src/pvss.rs +++ b/ferveo/src/pvss.rs @@ -19,7 +19,7 @@ use zeroize::{self, Zeroize, ZeroizeOnDrop}; use crate::{ assert_no_share_duplicates, batch_to_projective_g1, batch_to_projective_g2, - DomainPoint, Error, PVSSMap, PrivateKeyShare, PrivateKeyShareUpdate, + DomainPoint, Error, PrivateKeyShare, PrivateKeyShareUpdate, PubliclyVerifiableDkg, Result, UpdatedPrivateKeyShare, Validator, }; @@ -28,7 +28,7 @@ use crate::{ pub type ShareEncryptions = ::G2Affine; /// Marker struct for unaggregated PVSS transcripts -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, Hash)] +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] pub struct Unaggregated; /// Marker struct for aggregated PVSS transcripts @@ -124,6 +124,15 @@ pub struct PubliclyVerifiableSS { phantom: PhantomData, } +// Manually implementing Hash trait because of the PhantomData +impl Hash for PubliclyVerifiableSS { + fn hash(&self, state: &mut H) { + self.coeffs.hash(state); + self.shares.hash(state); + self.sigma.hash(state); + } +} + impl PubliclyVerifiableSS { /// Create a new PVSS instance /// `s`: the secret constant coefficient to share @@ -255,7 +264,7 @@ pub fn do_verify_aggregation( pvss_params: &PubliclyVerifiableParams, validators: &[Validator], domain: &ark_poly::GeneralEvaluationDomain, - vss: &PVSSMap, + pvss: &[PubliclyVerifiableSS], ) -> Result { let is_valid = do_verify_full( pvss_agg_coefficients, @@ -269,10 +278,9 @@ pub fn do_verify_aggregation( } // Now, we verify that the aggregated PVSS transcript is a valid aggregation - let mut y = E::G1::zero(); - for pvss in vss.values() { - y += pvss.coeffs[0].into_group(); - } + let y = pvss + .iter() + .fold(E::G1::zero(), |acc, pvss| acc + pvss.coeffs[0].into_group()); if y.into_affine() == pvss_agg_coefficients[0] { Ok(true) } else { @@ -289,6 +297,7 @@ impl PubliclyVerifiableSS { pub fn verify_aggregation( &self, dkg: &PubliclyVerifiableDkg, + pvss: &[PubliclyVerifiableSS], ) -> Result { let validators = dkg.validators.values().cloned().collect::>(); do_verify_aggregation( @@ -297,7 +306,7 @@ impl PubliclyVerifiableSS { &dkg.pvss_params, &validators, &dkg.domain, - &dkg.vss, + pvss, ) } @@ -331,14 +340,12 @@ impl PubliclyVerifiableSS { aad: &[u8], validator_keypair: &Keypair, share_index: u32, - g_inv: &E::G1Prepared, ) -> Result> { self.decrypt_private_key_share(validator_keypair, share_index)? .create_decryption_share_simple( ciphertext_header, aad, validator_keypair, - g_inv, ) } @@ -464,16 +471,16 @@ mod test_pvss { /// security threshold is not met #[test] fn test_aggregate_wont_verify_if_under_threshold() { - let (_dkg, _) = setup_dealt_dkg_with_n_transcript_dealt( + let (dkg, _, messages) = 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()); + let pvss_list = + messages.iter().map(|(_, pvss)| pvss).cloned().collect_vec(); + let aggregate = aggregate(&pvss_list).unwrap(); + assert!(aggregate.verify_aggregation(&dkg, &pvss_list).unwrap()); } /// Test the happy flow such that the PVSS with the correct form is created @@ -484,7 +491,7 @@ mod test_pvss { let rng = &mut ark_std::test_rng(); let security_threshold = shares_num - 1; - let (dkg, _) = setup_dealt_dkg_with_n_validators( + let (dkg, _, _) = setup_dealt_dkg_with_n_validators( security_threshold, shares_num, validators_num, @@ -557,12 +564,13 @@ mod test_pvss { #[test_case(4, 6; "number of validators is greater than the number of shares")] fn test_aggregate_pvss(shares_num: u32, validators_num: u32) { let security_threshold = shares_num - 1; - let (dkg, _) = setup_dealt_dkg_with_n_validators( + let (dkg, _, messages) = setup_dealt_dkg_with_n_validators( security_threshold, shares_num, validators_num, ); - let pvss_list = dkg.vss.values().cloned().collect::>(); + let pvss_list = + messages.iter().map(|(_, pvss)| pvss).cloned().collect_vec(); let aggregate = aggregate(&pvss_list).unwrap(); // Check that a polynomial of the correct degree was created assert_eq!( @@ -576,25 +584,27 @@ mod test_pvss { // Check that the full verify returns true 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, &pvss_list) + .expect("Test failed")); } /// Check that if the aggregated PVSS transcript has an /// incorrect constant term, the verification fails #[test] fn test_verify_aggregation_fails_if_constant_term_wrong() { - let (dkg, _) = setup_dealt_dkg(); - let pvss_list = dkg.vss.values().cloned().collect::>(); + let (dkg, _, messages) = setup_dealt_dkg(); + let pvss_list = + messages.iter().map(|(_, pvss)| pvss).cloned().collect_vec(); let mut aggregated = aggregate(&pvss_list).unwrap(); while aggregated.coeffs[0] == G1::zero() { - let (dkg, _) = setup_dkg(0); - let pvss_list = dkg.vss.values().cloned().collect::>(); + let (_dkg, _) = setup_dkg(0); aggregated = aggregate(&pvss_list).unwrap(); } aggregated.coeffs[0] = G1::zero(); assert_eq!( aggregated - .verify_aggregation(&dkg) + .verify_aggregation(&dkg, &pvss_list) .expect_err("Test failed") .to_string(), "Transcript aggregate doesn't match the received PVSS instances" diff --git a/ferveo/src/refresh.rs b/ferveo/src/refresh.rs index 619eff1c..ddaa8215 100644 --- a/ferveo/src/refresh.rs +++ b/ferveo/src/refresh.rs @@ -13,7 +13,7 @@ use rand_core::RngCore; use serde::{Deserialize, Serialize}; use zeroize::ZeroizeOnDrop; -use crate::{DomainPoint, Error, Result}; +use crate::{DomainPoint, Error, PubliclyVerifiableParams, Result}; // TODO: Rename refresh.rs to key_share.rs? @@ -62,14 +62,14 @@ impl PrivateKeyShare { ciphertext_header: &CiphertextHeader, aad: &[u8], validator_keypair: &Keypair, - g_inv: &E::G1Prepared, ) -> Result> { + let g_inv = PubliclyVerifiableParams::::default().g_inv(); DecryptionShareSimple::create( &validator_keypair.decryption_key, &self.0, ciphertext_header, aad, - g_inv, + &g_inv, ) .map_err(|e| e.into()) } diff --git a/ferveo/src/test_common.rs b/ferveo/src/test_common.rs index d947317c..df28f553 100644 --- a/ferveo/src/test_common.rs +++ b/ferveo/src/test_common.rs @@ -9,7 +9,7 @@ use rand::{seq::SliceRandom, Rng}; use crate::{ DkgParams, EthereumAddress, PubliclyVerifiableDkg, PubliclyVerifiableSS, - Validator, + Validator, ValidatorMessage, }; pub type ScalarField = ::ScalarField; @@ -77,10 +77,16 @@ pub fn setup_dkg(my_validator_index: usize) -> TestSetup { ) } +pub type DealtTestSetup = ( + PubliclyVerifiableDkg, + Vec>, + Vec>, +); + /// Set up a dkg with enough pvss transcripts to meet the threshold /// /// The correctness of this function is tested in the module [`crate::dkg::test_dealing`] -pub fn setup_dealt_dkg() -> TestSetup { +pub fn setup_dealt_dkg() -> DealtTestSetup { setup_dealt_dkg_with(SECURITY_THRESHOLD, SHARES_NUM) } @@ -89,7 +95,7 @@ pub fn setup_dealt_dkg() -> TestSetup { pub fn setup_dealt_dkg_with( security_threshold: u32, shares_num: u32, -) -> TestSetup { +) -> DealtTestSetup { setup_dealt_dkg_with_n_validators( security_threshold, shares_num, @@ -101,7 +107,7 @@ pub fn setup_dealt_dkg_with_n_validators( security_threshold: u32, shares_num: u32, validators_num: u32, -) -> TestSetup { +) -> DealtTestSetup { setup_dealt_dkg_with_n_transcript_dealt( security_threshold, shares_num, @@ -129,11 +135,12 @@ pub fn setup_dealt_dkg_with_n_transcript_dealt( shares_num: u32, validators_num: u32, transcripts_to_use: u32, -) -> TestSetup { +) -> DealtTestSetup { let rng = &mut ark_std::test_rng(); // Gather everyone's transcripts - let mut transcripts: Vec<_> = (0..validators_num) + // Use only the first `transcripts_to_use` transcripts + let mut transcripts: Vec<_> = (0..transcripts_to_use) .map(|my_index| { let (dkg, _) = setup_dkg_for_n_validators( security_threshold, @@ -148,23 +155,13 @@ pub fn setup_dealt_dkg_with_n_transcript_dealt( .collect(); // Create a test DKG instance - let (mut dkg, keypairs) = setup_dkg_for_n_validators( + let (dkg, keypairs) = setup_dkg_for_n_validators( security_threshold, shares_num, 0, validators_num, ); - // The ordering of messages should not matter 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) + (dkg, keypairs, transcripts) } From 0ef7de4c9b4442e2c6125d457de9420146be50b7 Mon Sep 17 00:00:00 2001 From: Piotr Roslaniec Date: Tue, 20 Feb 2024 09:50:16 +0100 Subject: [PATCH 3/6] refactor: rename public key share to public key --- ferveo-tdec/benches/tpke.rs | 2 +- ferveo-tdec/src/ciphertext.rs | 4 ++-- ferveo-tdec/src/context.rs | 6 +++--- ferveo-tdec/src/key_share.rs | 10 ++++------ ferveo-tdec/src/lib.rs | 14 +++++++------- ferveo/src/api.rs | 12 ++++++------ ferveo/src/pvss.rs | 8 ++++---- ferveo/src/refresh.rs | 1 - 8 files changed, 27 insertions(+), 30 deletions(-) diff --git a/ferveo-tdec/benches/tpke.rs b/ferveo-tdec/benches/tpke.rs index db8a7424..b7a5b8f7 100644 --- a/ferveo-tdec/benches/tpke.rs +++ b/ferveo-tdec/benches/tpke.rs @@ -25,7 +25,7 @@ struct SetupShared { shares_num: usize, msg: Vec, aad: Vec, - pubkey: PublicKeyShare, + pubkey: PublicKey, privkey: PrivateKeyShare, ciphertext: Ciphertext, shared_secret: SharedSecret, diff --git a/ferveo-tdec/src/ciphertext.rs b/ferveo-tdec/src/ciphertext.rs index d5563132..57b6ca13 100644 --- a/ferveo-tdec/src/ciphertext.rs +++ b/ferveo-tdec/src/ciphertext.rs @@ -14,7 +14,7 @@ use sha2::{digest::Digest, Sha256}; use zeroize::ZeroizeOnDrop; use crate::{ - htp_bls12381_g2, Error, PrivateKeyShare, PublicKeyShare, Result, SecretBox, + htp_bls12381_g2, Error, PrivateKeyShare, PublicKey, Result, SecretBox, SharedSecret, }; @@ -98,7 +98,7 @@ impl CiphertextHeader { pub fn encrypt( message: SecretBox>, aad: &[u8], - pubkey: &PublicKeyShare, + pubkey: &PublicKey, rng: &mut impl rand::Rng, ) -> Result> { // r diff --git a/ferveo-tdec/src/context.rs b/ferveo-tdec/src/context.rs index 238db71c..6e565188 100644 --- a/ferveo-tdec/src/context.rs +++ b/ferveo-tdec/src/context.rs @@ -5,13 +5,13 @@ use ark_ec::{pairing::Pairing, CurveGroup}; use crate::{ prepare_combine_simple, BlindedKeyShare, Ciphertext, CiphertextHeader, DecryptionShareFast, DecryptionSharePrecomputed, DecryptionShareSimple, - PrivateKeyShare, PublicKeyShare, Result, + PrivateKeyShare, PublicKey, Result, }; #[derive(Clone, Debug)] pub struct PublicDecryptionContextFast { pub domain: E::ScalarField, - pub public_key_share: PublicKeyShare, + pub public_key: PublicKey, pub blinded_key_share: BlindedKeyShare, // This decrypter's contribution to N(0), namely (-1)^|domain| * \prod_i omega_i pub lagrange_n_0: E::ScalarField, @@ -21,7 +21,7 @@ pub struct PublicDecryptionContextFast { #[derive(Clone, Debug)] pub struct PublicDecryptionContextSimple { pub domain: E::ScalarField, - pub public_key_share: PublicKeyShare, + pub public_key: PublicKey, pub blinded_key_share: BlindedKeyShare, pub h: E::G2Affine, pub validator_public_key: E::G2, diff --git a/ferveo-tdec/src/key_share.rs b/ferveo-tdec/src/key_share.rs index 236386ae..cd04c356 100644 --- a/ferveo-tdec/src/key_share.rs +++ b/ferveo-tdec/src/key_share.rs @@ -11,8 +11,7 @@ use zeroize::{Zeroize, ZeroizeOnDrop}; #[serde_as] #[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq, Eq)] -// TODO: Should we rename it to PublicKey or SharedPublicKey? -pub struct PublicKeyShare( +pub struct PublicKey( #[serde_as(as = "serialization::SerdeAs")] pub E::G1Affine, // A_{i, \omega_i} ); @@ -25,15 +24,14 @@ pub struct BlindedKeyShare { impl BlindedKeyShare { pub fn verify_blinding( &self, - public_key_share: &PublicKeyShare, + public_key: &PublicKey, rng: &mut R, ) -> bool { let g = E::G1Affine::generator(); let alpha = E::ScalarField::rand(rng); - let alpha_a = E::G1Prepared::from( - g + public_key_share.0.mul(alpha).into_affine(), - ); + let alpha_a = + E::G1Prepared::from(g + public_key.0.mul(alpha).into_affine()); // \sum_i(Y_i) let alpha_z = E::G2Prepared::from( diff --git a/ferveo-tdec/src/lib.rs b/ferveo-tdec/src/lib.rs index 465b3717..e491bba7 100644 --- a/ferveo-tdec/src/lib.rs +++ b/ferveo-tdec/src/lib.rs @@ -77,7 +77,7 @@ pub mod test_common { shares_num: usize, rng: &mut impl RngCore, ) -> ( - PublicKeyShare, + PublicKey, PrivateKeyShare, Vec>, ) { @@ -157,7 +157,7 @@ pub mod test_common { }); public_contexts.push(PublicDecryptionContextFast:: { domain: *domain, - public_key_share: PublicKeyShare::(*public), + public_key: PublicKey::(*public), blinded_key_share: blinded_key_shares, lagrange_n_0: *domain, h_inv: E::G2Prepared::from(-h.into_group()), @@ -168,7 +168,7 @@ pub mod test_common { } ( - PublicKeyShare(pubkey.into()), + PublicKey(pubkey.into()), PrivateKeyShare(privkey.into()), private_contexts, ) @@ -179,7 +179,7 @@ pub mod test_common { shares_num: usize, rng: &mut impl rand::Rng, ) -> ( - PublicKeyShare, + PublicKey, PrivateKeyShare, Vec>, ) { @@ -245,7 +245,7 @@ pub mod test_common { }); public_contexts.push(PublicDecryptionContextSimple:: { domain: *domain, - public_key_share: PublicKeyShare::(*public), + public_key: PublicKey::(*public), blinded_key_share, h, validator_public_key: h.mul(b), @@ -256,7 +256,7 @@ pub mod test_common { } ( - PublicKeyShare(pubkey.into()), + PublicKey(pubkey.into()), PrivateKeyShare(privkey.into()), private_contexts, ) @@ -266,7 +266,7 @@ pub mod test_common { shares_num: usize, rng: &mut impl rand::Rng, ) -> ( - PublicKeyShare, + PublicKey, PrivateKeyShare, Vec>, ) { diff --git a/ferveo/src/api.rs b/ferveo/src/api.rs index 80c350ce..ef29308d 100644 --- a/ferveo/src/api.rs +++ b/ferveo/src/api.rs @@ -143,13 +143,13 @@ impl From for FerveoVariant { #[derive(Copy, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] pub struct DkgPublicKey( #[serde(bound( - serialize = "ferveo_tdec::PublicKeyShare: Serialize", - deserialize = "ferveo_tdec::PublicKeyShare: DeserializeOwned" + serialize = "ferveo_tdec::PublicKey: Serialize", + deserialize = "ferveo_tdec::PublicKey: DeserializeOwned" ))] - pub(crate) ferveo_tdec::PublicKeyShare, + pub(crate) ferveo_tdec::PublicKey, ); -// TODO: Consider moving these implementation details to ferveo_tdec::PublicKeyShare +// TODO: Consider moving these implementation details to ferveo_tdec::PublicKey impl DkgPublicKey { pub fn to_bytes(&self) -> Result> { let as_bytes = to_bytes(&self.0 .0)?; @@ -166,7 +166,7 @@ impl DkgPublicKey { ) })?; let pk: G1Affine = from_bytes(&bytes)?; - Ok(DkgPublicKey(ferveo_tdec::PublicKeyShare(pk))) + Ok(DkgPublicKey(ferveo_tdec::PublicKey(pk))) } pub fn serialized_size() -> usize { @@ -178,7 +178,7 @@ impl DkgPublicKey { pub fn random() -> Self { let mut rng = thread_rng(); let g1 = G1Affine::rand(&mut rng); - Self(ferveo_tdec::PublicKeyShare(g1)) + Self(ferveo_tdec::PublicKey(g1)) } } diff --git a/ferveo/src/pvss.rs b/ferveo/src/pvss.rs index ed3a8ef3..086cea50 100644 --- a/ferveo/src/pvss.rs +++ b/ferveo/src/pvss.rs @@ -394,10 +394,10 @@ pub struct AggregatedTranscript { ))] pub aggregate: PubliclyVerifiableSS, #[serde(bound( - serialize = "ferveo_tdec::PublicKeyShare: Serialize", - deserialize = "ferveo_tdec::PublicKeyShare: DeserializeOwned" + serialize = "ferveo_tdec::PublicKey: Serialize", + deserialize = "ferveo_tdec::PublicKey: DeserializeOwned" ))] - pub public_key: ferveo_tdec::PublicKeyShare, + pub public_key: ferveo_tdec::PublicKey, } impl AggregatedTranscript { @@ -410,7 +410,7 @@ impl AggregatedTranscript { .map(|pvss| pvss.coeffs[0].into_group()) .sum::() .into_affine(); - let public_key = ferveo_tdec::PublicKeyShare::(public_key); + let public_key = ferveo_tdec::PublicKey::(public_key); Ok(AggregatedTranscript { aggregate, public_key, diff --git a/ferveo/src/refresh.rs b/ferveo/src/refresh.rs index ddaa8215..e1c86c31 100644 --- a/ferveo/src/refresh.rs +++ b/ferveo/src/refresh.rs @@ -122,7 +122,6 @@ impl UpdatedPrivateKeyShare { } } -// TODO: Replace with an into trait? /// Trait for types that can be used to update a private key share. pub trait PrivateKeyShareUpdate { fn inner(&self) -> &InnerPrivateKeyShare; From ba12d6b861447d4f2017cee37fe075651d114534 Mon Sep 17 00:00:00 2001 From: Piotr Roslaniec Date: Tue, 20 Feb 2024 11:57:06 +0100 Subject: [PATCH 4/6] refactor: update serde serialization --- ferveo/src/api.rs | 71 ++++++++++++++++++++----------------------- ferveo/src/refresh.rs | 28 ++++++++++++++--- 2 files changed, 56 insertions(+), 43 deletions(-) diff --git a/ferveo/src/api.rs b/ferveo/src/api.rs index ef29308d..389acb4a 100644 --- a/ferveo/src/api.rs +++ b/ferveo/src/api.rs @@ -36,8 +36,8 @@ pub type Transcript = PubliclyVerifiableSS; pub type ValidatorMessage = (Validator, Transcript); // Normally, we would use a custom trait for this, but we can't because -// the arkworks will not let us create a blanket implementation for G1Affine -// and Fr types. So instead, we're using this shared utility function: +// the `arkworks` will not let us create a blanket implementation for G1Affine +// and `Fr` types. So instead, we're using this shared utility function: pub fn to_bytes(item: &T) -> Result> { let mut writer = Vec::new(); item.serialize_compressed(&mut writer)?; @@ -356,9 +356,7 @@ impl AggregatedTranscript { Ok(PrivateKeyShare( self.0 .aggregate - .decrypt_private_key_share(validator_keypair, share_index)? - .0 - .clone(), + .decrypt_private_key_share(validator_keypair, share_index)?, )) } @@ -426,7 +424,7 @@ impl ShareRecoveryUpdate { #[serde_as] #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -pub struct ShareRefreshUpdate(pub ferveo_tdec::PrivateKeyShare); +pub struct ShareRefreshUpdate(pub crate::ShareRefreshUpdate); impl ShareRefreshUpdate { pub fn create_share_updates(dkg: &Dkg) -> Result> { @@ -437,8 +435,8 @@ impl ShareRefreshUpdate { dkg.0.dkg_params.security_threshold(), rng, ) - .iter() - .map(|update| ShareRefreshUpdate(update.0.clone())) + .into_iter() + .map(ShareRefreshUpdate) .collect(); Ok(updates) } @@ -454,11 +452,11 @@ impl ShareRefreshUpdate { #[serde_as] #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -pub struct UpdatedPrivateKeyShare(pub ferveo_tdec::PrivateKeyShare); +pub struct UpdatedPrivateKeyShare(pub crate::UpdatedPrivateKeyShare); impl UpdatedPrivateKeyShare { pub fn into_private_key_share(self) -> PrivateKeyShare { - PrivateKeyShare(self.0) + PrivateKeyShare(self.0.inner()) } pub fn to_bytes(&self) -> Result> { bincode::serialize(self).map_err(|e| e.into()) @@ -469,9 +467,8 @@ impl UpdatedPrivateKeyShare { } } -#[serde_as] #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -pub struct PrivateKeyShare(pub ferveo_tdec::PrivateKeyShare); +pub struct PrivateKeyShare(pub crate::PrivateKeyShare); impl PrivateKeyShare { pub fn create_updated_private_key_share_for_recovery( @@ -480,12 +477,12 @@ impl PrivateKeyShare { ) -> Result { let share_updates: Vec<_> = share_updates .iter() - .map(|update| crate::refresh::ShareRecoveryUpdate(update.0.clone())) + .cloned() + .map(|update| crate::refresh::ShareRecoveryUpdate(update.0)) .collect(); // TODO: Remove this wrapping after figuring out serde_as - let updated_key_share = crate::PrivateKeyShare(self.0.clone()) - .create_updated_key_share(&share_updates); - Ok(UpdatedPrivateKeyShare(updated_key_share.0.clone())) + let updated_key_share = self.0.create_updated_key_share(&share_updates); + Ok(UpdatedPrivateKeyShare(updated_key_share)) } pub fn create_updated_private_key_share_for_refresh( @@ -494,11 +491,11 @@ impl PrivateKeyShare { ) -> Result { let share_updates: Vec<_> = share_updates .iter() - .map(|update| crate::refresh::ShareRefreshUpdate(update.0.clone())) + .cloned() + .map(|update| update.0) .collect(); - let updated_key_share = crate::PrivateKeyShare(self.0.clone()) - .create_updated_key_share(&share_updates); - Ok(UpdatedPrivateKeyShare(updated_key_share.0.clone())) + let updated_key_share = self.0.create_updated_key_share(&share_updates); + Ok(UpdatedPrivateKeyShare(updated_key_share)) } /// Recover a private key share from updated private key shares @@ -509,8 +506,8 @@ impl PrivateKeyShare { ) -> Result { let updated_shares: Vec<_> = updated_shares .iter() - // TODO: Remove this wrapping after figuring out serde_as - .map(|s| crate::refresh::UpdatedPrivateKeyShare(s.0.clone())) + .cloned() + .map(|updated| updated.0) .collect(); let share = crate::PrivateKeyShare::recover_share_from_updated_private_shares( @@ -518,7 +515,7 @@ impl PrivateKeyShare { domain_points, &updated_shares[..], ); - Ok(PrivateKeyShare(share.0.clone())) + Ok(PrivateKeyShare(share)) } /// Make a decryption share (simple variant) for a given ciphertext @@ -529,12 +526,11 @@ impl PrivateKeyShare { validator_keypair: &Keypair, aad: &[u8], ) -> Result { - let share = crate::PrivateKeyShare(self.0.clone()) - .create_decryption_share_simple( - &ciphertext_header.0, - aad, - validator_keypair, - )?; + let share = self.0.create_decryption_share_simple( + &ciphertext_header.0, + aad, + validator_keypair, + )?; let domain_point = dkg.0.get_domain_point(dkg.0.me.share_index)?; Ok(DecryptionShareSimple { share, @@ -552,15 +548,14 @@ impl PrivateKeyShare { domain_points: &[DomainPoint], ) -> Result { let g_inv = PubliclyVerifiableParams::::default().g_inv(); - let share = crate::PrivateKeyShare(self.0.clone()) - .create_decryption_share_simple_precomputed( - &ciphertext_header.0, - aad, - validator_keypair, - share_index, - domain_points, - &g_inv, - )?; + let share = self.0.create_decryption_share_simple_precomputed( + &ciphertext_header.0, + aad, + validator_keypair, + share_index, + domain_points, + &g_inv, + )?; Ok(share) } diff --git a/ferveo/src/refresh.rs b/ferveo/src/refresh.rs index e1c86c31..f6adef82 100644 --- a/ferveo/src/refresh.rs +++ b/ferveo/src/refresh.rs @@ -10,7 +10,7 @@ use ferveo_tdec::{ }; use itertools::zip_eq; use rand_core::RngCore; -use serde::{Deserialize, Serialize}; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; use zeroize::ZeroizeOnDrop; use crate::{DomainPoint, Error, PubliclyVerifiableParams, Result}; @@ -20,8 +20,16 @@ use crate::{DomainPoint, Error, PubliclyVerifiableParams, Result}; type InnerPrivateKeyShare = ferveo_tdec::PrivateKeyShare; /// Private key share held by a participant in the DKG protocol. -#[derive(Debug, Clone, PartialEq, Eq, ZeroizeOnDrop)] -pub struct PrivateKeyShare(pub InnerPrivateKeyShare); +#[derive( + Debug, Clone, PartialEq, Eq, ZeroizeOnDrop, Serialize, Deserialize, +)] +pub struct PrivateKeyShare( + #[serde(bound( + serialize = "ferveo_tdec::PrivateKeyShare: Serialize", + deserialize = "ferveo_tdec::PrivateKeyShare: DeserializeOwned" + ))] + pub InnerPrivateKeyShare, +); impl PrivateKeyShare { pub fn new(private_key_share: InnerPrivateKeyShare) -> Self { @@ -83,7 +91,7 @@ impl PrivateKeyShare { domain_points: &[DomainPoint], g_inv: &E::G1Prepared, ) -> Result> { - // In precomputed variant, we offload the some of the decryption related computation to the server-side: + // In precomputed variant, we offload some of the decryption related computation to the server-side: // We use the `prepare_combine_simple` function to precompute the lagrange coefficients let lagrange_coeffs = prepare_combine_simple::(domain_points); let lagrange_coeff = &lagrange_coeffs @@ -103,8 +111,14 @@ impl PrivateKeyShare { } /// An updated private key share, resulting from an intermediate step in a share recovery or refresh operation. -#[derive(Debug, Clone, PartialEq, Eq, ZeroizeOnDrop)] +#[derive( + Debug, Clone, PartialEq, Eq, ZeroizeOnDrop, Serialize, Deserialize, +)] pub struct UpdatedPrivateKeyShare( + #[serde(bound( + serialize = "ferveo_tdec::PrivateKeyShare: Serialize", + deserialize = "ferveo_tdec::PrivateKeyShare: DeserializeOwned" + ))] pub(crate) InnerPrivateKeyShare, ); @@ -165,6 +179,10 @@ impl ShareRecoveryUpdate { Serialize, Deserialize, Debug, Clone, PartialEq, Eq, ZeroizeOnDrop, )] pub struct ShareRefreshUpdate( + #[serde(bound( + serialize = "ferveo_tdec::PrivateKeyShare: Serialize", + deserialize = "ferveo_tdec::PrivateKeyShare: DeserializeOwned" + ))] pub(crate) ferveo_tdec::PrivateKeyShare, ); From b67aef9622d3b7a936ba3f930fb13609ae55a409 Mon Sep 17 00:00:00 2001 From: Piotr Roslaniec Date: Thu, 22 Feb 2024 15:55:14 +0100 Subject: [PATCH 5/6] refactor: hide g_inv from internat apis --- ferveo/src/api.rs | 3 --- ferveo/src/lib.rs | 1 - ferveo/src/pvss.rs | 2 -- ferveo/src/refresh.rs | 4 ++-- 4 files changed, 2 insertions(+), 8 deletions(-) diff --git a/ferveo/src/api.rs b/ferveo/src/api.rs index 389acb4a..923de60a 100644 --- a/ferveo/src/api.rs +++ b/ferveo/src/api.rs @@ -323,7 +323,6 @@ impl AggregatedTranscript { validator_keypair, dkg.0.me.share_index, &dkg.0.domain_points(), - &dkg.0.pvss_params.g_inv(), ) } @@ -547,14 +546,12 @@ impl PrivateKeyShare { share_index: u32, domain_points: &[DomainPoint], ) -> Result { - let g_inv = PubliclyVerifiableParams::::default().g_inv(); let share = self.0.create_decryption_share_simple_precomputed( &ciphertext_header.0, aad, validator_keypair, share_index, domain_points, - &g_inv, )?; Ok(share) } diff --git a/ferveo/src/lib.rs b/ferveo/src/lib.rs index 5d52a4db..9a43b1f2 100644 --- a/ferveo/src/lib.rs +++ b/ferveo/src/lib.rs @@ -308,7 +308,6 @@ mod test_dkg_full { validator_keypair, validator.share_index, &domain_points, - &dkg.pvss_params.g_inv(), ) .unwrap() }) diff --git a/ferveo/src/pvss.rs b/ferveo/src/pvss.rs index 086cea50..dc07d5b7 100644 --- a/ferveo/src/pvss.rs +++ b/ferveo/src/pvss.rs @@ -359,7 +359,6 @@ impl PubliclyVerifiableSS { validator_keypair: &Keypair, share_index: u32, domain_points: &[DomainPoint], - g_inv: &E::G1Prepared, ) -> Result> { self.decrypt_private_key_share(validator_keypair, share_index)? .create_decryption_share_simple_precomputed( @@ -368,7 +367,6 @@ impl PubliclyVerifiableSS { validator_keypair, share_index, domain_points, - g_inv, ) } diff --git a/ferveo/src/refresh.rs b/ferveo/src/refresh.rs index f6adef82..bf633fa2 100644 --- a/ferveo/src/refresh.rs +++ b/ferveo/src/refresh.rs @@ -89,8 +89,8 @@ impl PrivateKeyShare { validator_keypair: &Keypair, share_index: u32, domain_points: &[DomainPoint], - g_inv: &E::G1Prepared, ) -> Result> { + let g_inv = PubliclyVerifiableParams::::default().g_inv(); // In precomputed variant, we offload some of the decryption related computation to the server-side: // We use the `prepare_combine_simple` function to precompute the lagrange coefficients let lagrange_coeffs = prepare_combine_simple::(domain_points); @@ -104,7 +104,7 @@ impl PrivateKeyShare { ciphertext_header, aad, lagrange_coeff, - g_inv, + &g_inv, ) .map_err(|e| e.into()) } From 93807a2c92a271e7c0f8ebc31c76604d805fbd7c Mon Sep 17 00:00:00 2001 From: Piotr Roslaniec Date: Mon, 26 Feb 2024 17:37:20 +0100 Subject: [PATCH 6/6] feature: remove deprecated exceptions --- ferveo-python/ferveo/__init__.py | 4 ---- ferveo-python/ferveo/__init__.pyi | 21 --------------------- ferveo/src/bindings_python.rs | 22 ---------------------- ferveo/src/lib.rs | 30 ------------------------------ 4 files changed, 77 deletions(-) diff --git a/ferveo-python/ferveo/__init__.py b/ferveo-python/ferveo/__init__.py index 209741b2..7f63fa66 100644 --- a/ferveo-python/ferveo/__init__.py +++ b/ferveo-python/ferveo/__init__.py @@ -18,10 +18,6 @@ ValidatorMessage, FerveoVariant, ThresholdEncryptionError, - InvalidDkgStateToDeal, - InvalidDkgStateToAggregate, - InvalidDkgStateToVerify, - InvalidDkgStateToIngest, DealerNotInValidatorSet, UnknownDealer, DuplicateDealer, diff --git a/ferveo-python/ferveo/__init__.pyi b/ferveo-python/ferveo/__init__.pyi index e738f2ab..69c77488 100644 --- a/ferveo-python/ferveo/__init__.pyi +++ b/ferveo-python/ferveo/__init__.pyi @@ -158,18 +158,6 @@ def decrypt_with_shared_secret( class ThresholdEncryptionError(Exception): pass -class InvalidDkgStateToDeal(Exception): - pass - -class InvalidDkgStateToAggregate(Exception): - pass - -class InvalidDkgStateToVerify(Exception): - pass - -class InvalidDkgStateToIngest(Exception): - pass - class DealerNotInValidatorSet(Exception): pass @@ -182,12 +170,6 @@ class DuplicateDealer(Exception): class InvalidPvssTranscript(Exception): pass -class InsufficientTranscriptsForAggregate(Exception): - pass - -class InvalidDkgPublicKey(Exception): - pass - class InsufficientValidators(Exception): pass @@ -221,9 +203,6 @@ class NoTranscriptsToAggregate(Exception): class InvalidAggregateVerificationParameters(Exception): pass -class UnknownValidator(Exception): - pass - class TooManyTranscripts(Exception): pass diff --git a/ferveo/src/bindings_python.rs b/ferveo/src/bindings_python.rs index 8505ae42..ecdb9e56 100644 --- a/ferveo/src/bindings_python.rs +++ b/ferveo/src/bindings_python.rs @@ -37,18 +37,6 @@ impl From for PyErr { Error::ThresholdEncryptionError(err) => { ThresholdEncryptionError::new_err(err.to_string()) } - Error::InvalidDkgStateToDeal => { - InvalidDkgStateToDeal::new_err("") - } - Error::InvalidDkgStateToAggregate => { - InvalidDkgStateToAggregate::new_err("") - } - Error::InvalidDkgStateToVerify => { - InvalidDkgStateToVerify::new_err("") - } - Error::InvalidDkgStateToIngest => { - InvalidDkgStateToIngest::new_err("") - } Error::DealerNotInValidatorSet(dealer) => { DealerNotInValidatorSet::new_err(dealer.to_string()) } @@ -61,13 +49,6 @@ impl From for PyErr { Error::InvalidPvssTranscript(validator_addr) => { InvalidPvssTranscript::new_err(validator_addr.to_string()) } - Error::InsufficientTranscriptsForAggregate( - expected, - actual, - ) => InsufficientTranscriptsForAggregate::new_err(format!( - "expected: {expected}, actual: {actual}" - )), - Error::InvalidDkgPublicKey => InvalidDkgPublicKey::new_err(""), Error::InsufficientValidators(expected, actual) => { InsufficientValidators::new_err(format!( "expected: {expected}, actual: {actual}" @@ -121,9 +102,6 @@ impl From for PyErr { "validators_num: {validators_num}, messages_num: {messages_num}" )) }, - Error::UnknownValidator(validator) => { - UnknownValidator::new_err(validator.to_string()) - }, Error::TooManyTranscripts(expected, received) => { TooManyTranscripts::new_err(format!( "expected: {expected}, received: {received}" diff --git a/ferveo/src/lib.rs b/ferveo/src/lib.rs index 9a43b1f2..832f0564 100644 --- a/ferveo/src/lib.rs +++ b/ferveo/src/lib.rs @@ -30,22 +30,6 @@ pub enum Error { #[error(transparent)] ThresholdEncryptionError(#[from] ferveo_tdec::Error), - /// DKG is not in a valid state to deal PVSS shares - #[error("Invalid DKG state to deal PVSS shares")] - InvalidDkgStateToDeal, - - /// DKG is not in a valid state to aggregate PVSS transcripts - #[error("Invalid DKG state to aggregate PVSS transcripts")] - InvalidDkgStateToAggregate, - - /// DKG is not in a valid state to verify PVSS transcripts - #[error("Invalid DKG state to verify PVSS transcripts")] - InvalidDkgStateToVerify, - - /// DKG is not in a valid state to ingest PVSS transcripts - #[error("Invalid DKG state to ingest PVSS transcripts")] - InvalidDkgStateToIngest, - /// DKG validator set must contain the validator with the given address #[error("Expected validator to be a part of the DKG validator set: {0}")] DealerNotInValidatorSet(EthereumAddress), @@ -62,16 +46,6 @@ pub enum Error { #[error("DKG received an invalid transcript from validator: {0}")] InvalidPvssTranscript(EthereumAddress), - /// Aggregation failed because the DKG did not receive enough PVSS transcripts - #[error( - "Insufficient transcripts for aggregation (expected {0}, got {1})" - )] - InsufficientTranscriptsForAggregate(u32, u32), - - /// Failed to derive a valid final key for the DKG - #[error("Failed to derive a valid final key for the DKG")] - InvalidDkgPublicKey, - /// Not enough validators to perform the DKG for a given number of shares #[error("Not enough validators (expected {0}, got {1})")] InsufficientValidators(u32, u32), @@ -122,10 +96,6 @@ pub enum Error { #[error("Invalid aggregate verification parameters: number of validators {0}, number of messages: {1}")] InvalidAggregateVerificationParameters(u32, u32), - /// 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),