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..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, @@ -40,4 +36,6 @@ NoTranscriptsToAggregate, InvalidAggregateVerificationParameters, UnknownValidator, + TooManyTranscripts, + DuplicateTranscript, ) diff --git a/ferveo-python/ferveo/__init__.pyi b/ferveo-python/ferveo/__init__.pyi index 894f71ed..69c77488 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] @@ -157,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 @@ -181,12 +170,6 @@ class DuplicateDealer(Exception): class InvalidPvssTranscript(Exception): pass -class InsufficientTranscriptsForAggregate(Exception): - pass - -class InvalidDkgPublicKey(Exception): - pass - class InsufficientValidators(Exception): pass @@ -220,5 +203,8 @@ class NoTranscriptsToAggregate(Exception): class InvalidAggregateVerificationParameters(Exception): pass -class UnknownValidator(Exception): +class TooManyTranscripts(Exception): + pass + +class DuplicateTranscript(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/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 cdaf956c..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 @@ -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/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/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..cd04c356 100644 --- a/ferveo-tdec/src/key_share.rs +++ b/ferveo-tdec/src/key_share.rs @@ -9,11 +9,11 @@ use serde::{Deserialize, Serialize}; use serde_with::serde_as; use zeroize::{Zeroize, ZeroizeOnDrop}; -#[derive(Debug, Clone)] -// TODO: Should we rename it to PublicKey or SharedPublicKey? -pub struct PublicKeyShare { - pub public_key_share: E::G1Affine, // A_{i, \omega_i} -} +#[serde_as] +#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct PublicKey( + #[serde_as(as = "serialization::SerdeAs")] pub E::G1Affine, // A_{i, \omega_i} +); #[derive(Debug, Clone)] pub struct BlindedKeyShare { @@ -24,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.public_key_share.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( @@ -56,18 +55,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..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>, ) { @@ -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: PublicKey::(*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(), - }, + PublicKey(pubkey.into()), + PrivateKeyShare(privkey.into()), private_contexts, ) } @@ -187,7 +179,7 @@ pub mod test_common { shares_num: usize, rng: &mut impl rand::Rng, ) -> ( - PublicKeyShare, + PublicKey, PrivateKeyShare, Vec>, ) { @@ -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: PublicKey::(*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(), - }, + PublicKey(pubkey.into()), + PrivateKeyShare(privkey.into()), private_contexts, ) } @@ -282,7 +266,7 @@ pub mod test_common { shares_num: usize, rng: &mut impl rand::Rng, ) -> ( - PublicKeyShare, + PublicKey, PrivateKeyShare, Vec>, ) { 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..923de60a 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,8 +25,8 @@ use crate::bindings_python; use crate::bindings_wasm; pub use crate::EthereumAddress; use crate::{ - do_verify_aggregation, DomainPoint, Error, Message, PVSSMap, - PubliclyVerifiableParams, PubliclyVerifiableSS, Result, + do_verify_aggregation, DomainPoint, Error, PubliclyVerifiableParams, + PubliclyVerifiableSS, Result, }; pub type PublicKey = ferveo_common::PublicKey; @@ -37,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)?; @@ -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)) } @@ -73,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) } @@ -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::PublicKey: Serialize", + deserialize = "ferveo_tdec::PublicKey: DeserializeOwned" + ))] + pub(crate) ferveo_tdec::PublicKey, ); +// 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)?; + 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::PublicKey(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::PublicKey(g1)) } } @@ -222,47 +219,20 @@ 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)?)) - } - - pub fn public_params(&self) -> DkgPublicParameters { - DkgPublicParameters { - g1_inv: self.0.pvss_params.g_inv(), - } + self.0 + .aggregate_transcripts(messages) + .map(AggregatedTranscript) } pub fn me(&self) -> &Validator { @@ -274,27 +244,18 @@ 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( - 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( @@ -309,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.verify_optimistic(); + 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( - &self.0.coeffs, - &self.0.shares, + 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 @@ -355,13 +317,12 @@ 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, dkg.0.me.share_index, &dkg.0.domain_points(), - &dkg.0.pvss_params.g_inv(), ) } @@ -373,12 +334,11 @@ 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, 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 { @@ -394,11 +354,14 @@ impl AggregatedTranscript { ) -> Result { Ok(PrivateKeyShare( self.0 - .decrypt_private_key_share(validator_keypair, share_index)? - .0 - .clone(), + .aggregate + .decrypt_private_key_share(validator_keypair, share_index)?, )) } + + pub fn public_key(&self) -> DkgPublicKey { + DkgPublicKey(self.0.public_key) + } } #[serde_as] @@ -406,33 +369,7 @@ impl AggregatedTranscript { pub struct DecryptionShareSimple { share: ferveo_tdec::api::DecryptionShareSimple, #[serde_as(as = "serialization::SerdeAs")] - domain_point: Fr, -} - -// 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()) - } + domain_point: DomainPoint, } pub fn combine_shares_simple(shares: &[DecryptionShareSimple]) -> SharedSecret { @@ -450,8 +387,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 { @@ -488,7 +423,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> { @@ -499,8 +434,8 @@ impl ShareRefreshUpdate { dkg.0.dkg_params.security_threshold(), rng, ) - .iter() - .map(|update| ShareRefreshUpdate(update.0.clone())) + .into_iter() + .map(ShareRefreshUpdate) .collect(); Ok(updates) } @@ -516,11 +451,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()) @@ -531,9 +466,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( @@ -542,12 +476,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( @@ -556,11 +490,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 @@ -571,8 +505,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( @@ -580,7 +514,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 @@ -591,13 +525,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, - &dkg.public_params().g1_inv, - )?; + 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, @@ -614,16 +546,13 @@ impl PrivateKeyShare { share_index: u32, domain_points: &[DomainPoint], ) -> Result { - let dkg_public_params = DkgPublicParameters::default(); - let share = crate::PrivateKeyShare(self.0.clone()) - .create_decryption_share_simple_precomputed( - &ciphertext_header.0, - aad, - validator_keypair, - share_index, - domain_points, - &dkg_public_params.g1_inv, - )?; + let share = self.0.create_decryption_share_simple_precomputed( + &ciphertext_header.0, + aad, + validator_keypair, + share_index, + domain_points, + )?; Ok(share) } @@ -678,7 +607,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 +615,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 +648,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 +669,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 +739,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 +747,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 +762,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 +833,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 +851,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 +860,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 +872,51 @@ mod test_ferveo_api { Err(Error::InvalidTranscriptAggregate) )); + // Duplicated transcripts + 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_duplicated_transcript) + .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 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 +1015,7 @@ mod test_ferveo_api { shares_num, validators_num, ); - let mut dkgs = validators + let dkgs = validators .iter() .map(|validator| { Dkg::new( @@ -1071,20 +1028,29 @@ 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 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(); + let transcripts = messages + .iter() + .map(|(_, transcript)| transcript) + .cloned() + .collect::>(); 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(), + &transcripts, ); ( @@ -1142,13 +1108,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 f6fce72c..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()) } @@ -58,16 +46,9 @@ 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, - 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,14 @@ 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}" + )) + } + 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. }, @@ -174,6 +160,8 @@ create_exception!( PyValueError ); 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) @@ -516,11 +504,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 +652,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 +777,8 @@ 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::())?; + m.add("DuplicateTranscript", py.get_type::())?; Ok(()) } @@ -883,7 +873,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 +951,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..087b3069 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,8 @@ pub struct PubliclyVerifiableDkg { pub dkg_params: DkgParams, pub pvss_params: PubliclyVerifiableParams, pub validators: ValidatorsMap, - pub vss: PVSSMap, pub domain: ark_poly::GeneralEvaluationDomain, pub me: Validator, - state: DkgState, } impl PubliclyVerifiableDkg { @@ -141,14 +115,13 @@ impl PubliclyVerifiableDkg { Ok(Self { dkg_params: *dkg_params, pvss_params: PubliclyVerifiableParams::::default(), - vss: PVSSMap::::new(), 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 +132,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,158 +162,50 @@ 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() } - 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())) - } - } - - pub fn verify_message( + /// Verify PVSS transcripts against the set of validators in the DKG + 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(); + 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())); } - _ => Err(Error::InvalidDkgStateToVerify), + validator_set.insert(sender.clone()); + transcript_set.insert(transcript.clone()); } - } - /// 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 +241,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 +272,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 +298,74 @@ 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 pvss = dkg.share(rng).unwrap(); + let (dkg, _) = setup_dkg(0); + let mut messages = make_messages(rng, &dkg); - // This validator has already sent a PVSS - let sender = dkg.me.clone(); + messages.push(messages[0].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, - } - )); - - // 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 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..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), @@ -59,18 +43,8 @@ pub enum Error { DuplicateDealer(EthereumAddress), /// DKG received an invalid transcript for which optimistic verification failed - #[error("DKG received an invalid transcript")] - InvalidPvssTranscript, - - /// 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, + #[error("DKG received an invalid transcript from validator: {0}")] + InvalidPvssTranscript(EthereumAddress), /// Not enough validators to perform the DKG for a given number of shares #[error("Not enough validators (expected {0}, got {1})")] @@ -122,9 +96,13 @@ 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), + + /// Received a duplicated transcript from a validator + #[error("Received a duplicated transcript from validator: {0}")] + DuplicateTranscript(EthereumAddress), } pub type Result = std::result::Result; @@ -155,14 +133,18 @@ mod test_dkg_full { aad: &[u8], ciphertext_header: &ferveo_tdec::CiphertextHeader, validator_keypairs: &[Keypair], + transcripts: &[PubliclyVerifiableSS], ) -> ( - 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 pvss_aggregated = + AggregatedTranscript::from_transcripts(transcripts).unwrap(); + assert!(pvss_aggregated + .aggregate + .verify_aggregation(dkg, transcripts) + .is_ok()); let decryption_shares: Vec> = validator_keypairs @@ -172,12 +154,12 @@ mod test_dkg_full { .get_validator(&validator_keypair.public_key()) .unwrap(); pvss_aggregated + .aggregate .create_decryption_share_simple( ciphertext_header, aad, validator_keypair, validator.share_index, - &dkg.pvss_params.g_inv(), ) .unwrap() }) @@ -207,13 +189,17 @@ 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 public_key = dkg.public_key(); + 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; let ciphertext = ferveo_tdec::encrypt::( SecretBox::new(MSG.to_vec()), AAD, @@ -227,6 +213,7 @@ mod test_dkg_full { AAD, &ciphertext.header().unwrap(), validator_keypairs.as_slice(), + &transcripts, ); let plaintext = ferveo_tdec::decrypt_with_shared_secret( @@ -247,12 +234,21 @@ 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 public_key = dkg.public_key(); + 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, &transcripts) + .unwrap(); + let public_key = pvss_aggregated.public_key; let ciphertext = ferveo_tdec::encrypt::( SecretBox::new(MSG.to_vec()), AAD, @@ -261,9 +257,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,13 +271,13 @@ mod test_dkg_full { .get_validator(&validator_keypair.public_key()) .unwrap(); pvss_aggregated + .aggregate .create_decryption_share_simple_precomputed( &ciphertext.header().unwrap(), AAD, validator_keypair, validator.share_index, &domain_points, - &dkg.pvss_params.g_inv(), ) .unwrap() }) @@ -316,12 +309,17 @@ 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 public_key = dkg.public_key(); + 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; let ciphertext = ferveo_tdec::encrypt::( SecretBox::new(MSG.to_vec()), AAD, @@ -336,10 +334,11 @@ mod test_dkg_full { AAD, &ciphertext.header().unwrap(), validator_keypairs.as_slice(), + &transcripts, ); izip!( - &pvss_aggregated.shares, + &pvss_aggregated.aggregate.shares, &validator_keypairs, &decryption_shares, ) @@ -361,7 +360,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 +370,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, @@ -388,16 +387,21 @@ 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 public_key = &dkg.public_key(); + 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; let ciphertext = ferveo_tdec::encrypt::( SecretBox::new(MSG.to_vec()), AAD, - public_key, + &public_key, rng, ) .unwrap(); @@ -408,6 +412,7 @@ mod test_dkg_full { AAD, &ciphertext.header().unwrap(), validator_keypairs.as_slice(), + &transcripts, ); // Remove one participant from the contexts and all nested structure @@ -469,10 +474,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,17 +500,14 @@ 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, validator_keypair, share_index as u32, - &dkg.pvss_params.g_inv(), ) .unwrap() }) @@ -558,16 +559,21 @@ 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 public_key = &dkg.public_key(); + 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; let ciphertext = ferveo_tdec::encrypt::( SecretBox::new(MSG.to_vec()), AAD, - public_key, + &public_key, rng, ) .unwrap(); @@ -578,6 +584,7 @@ mod test_dkg_full { AAD, &ciphertext.header().unwrap(), validator_keypairs.as_slice(), + &transcripts, ); // Each participant prepares an update for each other participant @@ -619,10 +626,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..dc07d5b7 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,14 +12,14 @@ 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}; 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, }; @@ -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 { @@ -125,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 @@ -206,7 +214,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 +233,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 +255,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( @@ -256,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, @@ -264,16 +272,15 @@ pub fn do_verify_aggregation( pvss_params, validators, domain, - ); + )?; if !is_valid { return Err(Error::InvalidTranscriptAggregate); } // 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 { @@ -290,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( @@ -298,7 +306,7 @@ impl PubliclyVerifiableSS { &dkg.pvss_params, &validators, &dkg.domain, - &dkg.vss, + pvss, ) } @@ -318,9 +326,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 @@ -332,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, ) } @@ -353,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( @@ -362,7 +367,6 @@ impl PubliclyVerifiableSS { validator_keypair, share_index, domain_points, - g_inv, ) } @@ -380,13 +384,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::PublicKey: Serialize", + deserialize = "ferveo_tdec::PublicKey: DeserializeOwned" + ))] + pub public_key: ferveo_tdec::PublicKey, +} + +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::PublicKey::(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 +465,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, _, messages) = setup_dealt_dkg_with_n_transcript_dealt( + SECURITY_THRESHOLD, + SHARES_NUM, + VALIDATORS_NUM, + SECURITY_THRESHOLD - 1, + ); + 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 /// and that appropriate validations pass #[test_case(4, 4; "number of validators is equal to the number of shares")] @@ -437,7 +489,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, @@ -454,12 +506,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 +544,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 +553,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 @@ -510,12 +562,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!( @@ -527,27 +580,29 @@ 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, &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 87797a9b..bf633fa2 100644 --- a/ferveo/src/refresh.rs +++ b/ferveo/src/refresh.rs @@ -10,18 +10,26 @@ 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, Result}; +use crate::{DomainPoint, Error, PubliclyVerifiableParams, Result}; // TODO: Rename refresh.rs to key_share.rs? 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 { @@ -37,12 +45,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 +60,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( @@ -68,14 +70,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()) } @@ -87,9 +89,9 @@ impl PrivateKeyShare { validator_keypair: &Keypair, share_index: u32, domain_points: &[DomainPoint], - g_inv: &E::G1Prepared, ) -> Result> { - // In precomputed variant, we offload the some of the decryption related computation to the server-side: + 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); let lagrange_coeff = &lagrange_coeffs @@ -102,15 +104,21 @@ impl PrivateKeyShare { ciphertext_header, aad, lagrange_coeff, - g_inv, + &g_inv, ) .map_err(|e| e.into()) } } /// 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, ); @@ -128,7 +136,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; @@ -172,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, ); @@ -226,9 +237,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..df28f553 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, ValidatorMessage, +}; pub type ScalarField = ::ScalarField; pub type G1 = ::G1Affine; @@ -73,17 +77,25 @@ 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) } +// TODO: Rewrite setup_utils to return messages separately + pub fn setup_dealt_dkg_with( security_threshold: u32, shares_num: u32, -) -> TestSetup { +) -> DealtTestSetup { setup_dealt_dkg_with_n_validators( security_threshold, shares_num, @@ -95,36 +107,61 @@ 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, + 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, +) -> DealtTestSetup { let rng = &mut ark_std::test_rng(); // Gather everyone's transcripts - let mut messages: Vec<_> = (0..validators_num) + // Use only the first `transcripts_to_use` transcripts + let mut transcripts: Vec<_> = (0..transcripts_to_use) .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(); // 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 - messages.shuffle(rng); - messages.iter().for_each(|(sender, message)| { - dkg.apply_message(sender, message).expect("Setup failed"); - }); - (dkg, keypairs) + transcripts.shuffle(rng); + (dkg, keypairs, transcripts) }