From b01d5b5092b6e550e2d4d06bcf9f6614b70697ca Mon Sep 17 00:00:00 2001 From: Piotr Roslaniec Date: Tue, 30 Jan 2024 09:59:49 +0100 Subject: [PATCH] feature: relax dkg ceremony constraints --- ferveo/src/api.rs | 11 +-- ferveo/src/bindings_python.rs | 176 +++++++++++++++++++--------------- ferveo/src/dkg.rs | 40 +++++++- ferveo/src/lib.rs | 8 +- ferveo/src/pvss.rs | 7 +- 5 files changed, 143 insertions(+), 99 deletions(-) diff --git a/ferveo/src/api.rs b/ferveo/src/api.rs index 4c6defd2..80ab58d6 100644 --- a/ferveo/src/api.rs +++ b/ferveo/src/api.rs @@ -324,6 +324,7 @@ impl AggregatedTranscript { aad: &[u8], validator_keypair: &Keypair, ) -> Result { + // Prevent users from using the precomputed variant with improper DKG parameters if dkg.0.dkg_params.shares_num() != dkg.0.dkg_params.security_threshold() { @@ -332,18 +333,12 @@ impl AggregatedTranscript { dkg.0.dkg_params.security_threshold(), )); } - let domain_points: Vec<_> = dkg - .0 - .domain - .elements() - .take(dkg.0.dkg_params.shares_num() as usize) - .collect(); self.0.make_decryption_share_simple_precomputed( &ciphertext_header.0, aad, &validator_keypair.decryption_key, dkg.0.me.share_index as usize, - &domain_points, + &dkg.0.domain_points(), &dkg.0.pvss_params.g_inv(), ) } @@ -362,7 +357,7 @@ impl AggregatedTranscript { dkg.0.me.share_index as usize, &dkg.0.pvss_params.g_inv(), )?; - let domain_point = dkg.0.domain.element(dkg.0.me.share_index as usize); + let domain_point = dkg.0.get_domain_point(dkg.0.me.share_index)?; Ok(DecryptionShareSimple { share, domain_point, diff --git a/ferveo/src/bindings_python.rs b/ferveo/src/bindings_python.rs index 72407a16..ef03033e 100644 --- a/ferveo/src/bindings_python.rs +++ b/ferveo/src/bindings_python.rs @@ -92,30 +92,30 @@ impl From for PyErr { } Error::InvalidVariant(variant) => { InvalidVariant::new_err(variant.to_string()) - }, - Error::InvalidDkgParameters(num_shares, security_threshold) => { + } + Error::InvalidDkgParameters(shares_num, security_threshold) => { InvalidDkgParameters::new_err(format!( - "num_shares: {num_shares}, security_threshold: {security_threshold}" + "shares_num: {shares_num}, security_threshold: {security_threshold}" )) - }, + } Error::InvalidShareIndex(index) => { InvalidShareIndex::new_err(format!( "{index}" )) - }, - Error::InvalidDkgParametersForPrecomputedVariant(num_shares, security_threshold) => { + } + Error::InvalidDkgParametersForPrecomputedVariant(shares_num, security_threshold) => { InvalidDkgParameters::new_err(format!( - "num_shares: {num_shares}, security_threshold: {security_threshold}" + "shares_num: {shares_num}, security_threshold: {security_threshold}" )) - }, + } Error::DuplicatedShareIndex(index) => { DuplicatedShareIndex::new_err(format!( "{index}" )) - }, + } Error::NoTranscriptsToAggregate => { NoTranscriptsToAggregate::new_err("") - }, + } }, _ => default(), } @@ -751,6 +751,7 @@ pub fn make_ferveo_py_module(py: Python<'_>, m: &PyModule) -> PyResult<()> { #[cfg(test)] mod test_ferveo_python { use itertools::izip; + use test_case::test_case; use crate::{bindings_python::*, test_common::*}; @@ -760,8 +761,9 @@ mod test_ferveo_python { tau: u32, security_threshold: u32, shares_num: u32, + validators_num: u32, ) -> TestInputs { - let validator_keypairs = (0..shares_num) + let validator_keypairs = (0..validators_num) .map(|_| Keypair::random()) .collect::>(); let validators: Vec<_> = validator_keypairs @@ -800,13 +802,18 @@ mod test_ferveo_python { (messages, validators, validator_keypairs) } - #[test] - fn test_server_api_tdec_precomputed() { + #[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_server_api_tdec_precomputed(shares_num: u32, validators_num: u32) { // In precomputed variant, the security threshold is equal to the number of shares - let security_threshold = SHARES_NUM; + let security_threshold = shares_num; - let (messages, validators, validator_keypairs) = - make_test_inputs(TAU, security_threshold, SHARES_NUM); + let (messages, validators, validator_keypairs) = make_test_inputs( + TAU, + security_threshold, + shares_num, + validators_num, + ); // Now that every validator holds a dkg instance and a transcript for every other validator, // every validator can aggregate the transcripts @@ -814,19 +821,20 @@ mod test_ferveo_python { let me = validators[0].clone(); let mut dkg = Dkg::new( TAU, - SHARES_NUM, + shares_num, security_threshold, validators.clone(), &me, ) .unwrap(); - // Lets say that we've only receives `security_threshold` transcripts + // Lets say that we've only received `security_threshold` transcripts let messages = messages[..security_threshold as usize].to_vec(); let pvss_aggregated = dkg.aggregate_transcripts(messages.clone()).unwrap(); + // TODO: Redo how verification API works; assert!(pvss_aggregated - .verify(SHARES_NUM, messages.clone()) + .verify(validators_num, messages.clone()) .unwrap()); // At this point, any given validator should be able to provide a DKG public key @@ -836,36 +844,38 @@ mod test_ferveo_python { let ciphertext = encrypt(MSG.to_vec(), AAD, &dkg_public_key).unwrap(); // Having aggregated the transcripts, the validators can now create decryption shares - let decryption_shares: Vec<_> = 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( - TAU, - SHARES_NUM, - security_threshold, - validators.clone(), - validator, - ) - .unwrap(); - let aggregate = - dkg.aggregate_transcripts(messages.clone()).unwrap(); - assert!(pvss_aggregated - .verify(SHARES_NUM, messages.clone()) - .is_ok()); - aggregate - .create_decryption_share_precomputed( - &dkg, - &ciphertext.header().unwrap(), - AAD, - validator_keypair, + let decryption_shares: Vec<_> = + izip!(validators.clone(), &validator_keypairs) + .map(|(validator, validator_keypair)| { + // Each validator holds their own instance of DKG and creates their own aggregate + let mut validator_dkg = Dkg::new( + TAU, + shares_num, + security_threshold, + validators.clone(), + &validator, ) - .unwrap() - }) - .collect(); + .unwrap(); + let aggregate = validator_dkg + .aggregate_transcripts(messages.clone()) + .unwrap(); + // TODO: Redo how verification API works; + assert!(pvss_aggregated + .verify(validators_num, messages.clone()) + .is_ok()); + aggregate + .create_decryption_share_precomputed( + &validator_dkg, + &ciphertext.header().unwrap(), + AAD, + validator_keypair, + ) + .unwrap() + }) + .collect(); // Now, the decryption share can be used to decrypt the ciphertext // This part is part of the client API - let shared_secret = combine_decryption_shares_precomputed(decryption_shares); @@ -875,29 +885,36 @@ mod test_ferveo_python { assert_eq!(plaintext, MSG); } - #[test] - fn test_server_api_tdec_simple() { - let (messages, validators, validator_keypairs) = - make_test_inputs(TAU, SECURITY_THRESHOLD, SHARES_NUM); + #[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_server_api_tdec_simple(shares_num: u32, validators_num: u32) { + let security_threshold = shares_num - 1; + let (messages, validators, validator_keypairs) = make_test_inputs( + TAU, + security_threshold, + shares_num, + validators_num, + ); // 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 = Dkg::new( TAU, - SHARES_NUM, - SECURITY_THRESHOLD, + shares_num, + security_threshold, validators.clone(), &me, ) .unwrap(); // Lets say that we've only receives `security_threshold` transcripts - let messages = messages[..SECURITY_THRESHOLD as usize].to_vec(); + let messages = messages[..security_threshold as usize].to_vec(); let pvss_aggregated = dkg.aggregate_transcripts(messages.clone()).unwrap(); + // TODO: Redo how verification API works; assert!(pvss_aggregated - .verify(SHARES_NUM, messages.clone()) + .verify(validators_num, messages.clone()) .unwrap()); // At this point, any given validator should be able to provide a DKG public key @@ -907,32 +924,35 @@ mod test_ferveo_python { let ciphertext = encrypt(MSG.to_vec(), AAD, &dkg_public_key).unwrap(); // Having aggregated the transcripts, the validators can now create decryption shares - let decryption_shares: Vec<_> = 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( - TAU, - SHARES_NUM, - SECURITY_THRESHOLD, - validators.clone(), - validator, - ) - .unwrap(); - let aggregate = - dkg.aggregate_transcripts(messages.clone()).unwrap(); - assert!(aggregate - .verify(SHARES_NUM, messages.clone()) - .unwrap()); - aggregate - .create_decryption_share_simple( - &dkg, - &ciphertext.header().unwrap(), - AAD, - validator_keypair, + let decryption_shares: Vec<_> = + izip!(validators.clone(), &validator_keypairs) + .map(|(validator, validator_keypair)| { + // Each validator holds their own instance of DKG and creates their own aggregate + let mut dkg = Dkg::new( + TAU, + shares_num, + security_threshold, + validators.clone(), + &validator, ) - .unwrap() - }) - .collect(); + .unwrap(); + let aggregate = + dkg.aggregate_transcripts(messages.clone()).unwrap(); + + // TODO: Redo how verification API works; + assert!(aggregate + .verify(validators_num, messages.clone()) + .unwrap()); + aggregate + .create_decryption_share_simple( + &dkg, + &ciphertext.header().unwrap(), + AAD, + validator_keypair, + ) + .unwrap() + }) + .collect(); // Now, the decryption share can be used to decrypt the ciphertext // This part is part of the client API diff --git a/ferveo/src/dkg.rs b/ferveo/src/dkg.rs index cbd90971..80e32905 100644 --- a/ferveo/src/dkg.rs +++ b/ferveo/src/dkg.rs @@ -94,8 +94,11 @@ pub struct PubliclyVerifiableDkg { pub pvss_params: PubliclyVerifiableParams, pub validators: ValidatorsMap, pub vss: PVSSMap, + // TODO: Remove pub? + // TODO: Consider replacing with domain_points entirely pub domain: ark_poly::GeneralEvaluationDomain, pub me: Validator, + // TODO: Remove pub? pub state: DkgState, } @@ -197,6 +200,25 @@ impl PubliclyVerifiableDkg { .into_affine() } + // TODO: Use instead of domain.element + /// Return a domain point for the share_index + pub fn get_domain_point(&self, share_index: u32) -> Result { + let domain_points = self.domain_points(); + domain_points + .get(share_index as usize) + .ok_or_else(|| Error::InvalidShareIndex(share_index)) + .copied() + } + + /// Return an appropriate amount of domain points for the DKG + pub fn domain_points(&self) -> Vec { + self.domain.elements().take(self.validators.len()).collect() + // self.domain + // .elements() + // .take(self.dkg_params.shares_num as usize) + // .collect() + } + /// `payload` is the content of the message pub fn verify_message( &self, @@ -323,6 +345,7 @@ pub struct Aggregation { public_key: E::G1Affine, } +// 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", @@ -593,8 +616,8 @@ mod test_aggregation { use crate::{dkg::*, test_common::*, DkgState, Message}; /// Test that if the security threshold is met, we can create a final key - #[test_case(4,4; "number of validators is equal to the number of shares")] - #[test_case(4,6; "number of validators is greater than the number of shares")] + #[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( @@ -602,10 +625,17 @@ mod test_aggregation { shares_num, validators_num, ); - let aggregate = dkg.aggregate().unwrap(); + let aggregate_msg = dkg.aggregate().unwrap(); + if let Message::Aggregate(Aggregation { public_key, .. }) = + &aggregate_msg + { + assert_eq!(public_key, &dkg.public_key()); + } else { + panic!("Expected aggregate message") + } let sender = dkg.me.clone(); - assert!(dkg.verify_message(&sender, &aggregate).is_ok()); - assert!(dkg.apply_message(&sender, &aggregate).is_ok()); + assert!(dkg.verify_message(&sender, &aggregate_msg).is_ok()); + assert!(dkg.apply_message(&sender, &aggregate_msg).is_ok()); assert!(matches!(dkg.state, DkgState::Success { .. })); } diff --git a/ferveo/src/lib.rs b/ferveo/src/lib.rs index 2931defe..bba06458 100644 --- a/ferveo/src/lib.rs +++ b/ferveo/src/lib.rs @@ -197,7 +197,7 @@ mod test_dkg_full { #[test_case(4, 4; "number of shares (validators) is a power of 2")] #[test_case(7, 7; "number of shares (validators) is not a power of 2")] - #[test_case(4, 6; "number of validators is greater than the number of shares")] + #[test_case(4, 6; "number of validators greater than the number of shares")] fn test_dkg_simple_tdec(shares_num: u32, validators_num: u32) { let rng = &mut test_rng(); @@ -236,7 +236,7 @@ mod test_dkg_full { #[test_case(4, 4; "number of shares (validators) is a power of 2")] #[test_case(7, 7; "number of shares (validators) is not a power of 2")] - #[test_case(4, 6; "number of validators is greater than the number of shares")] + #[test_case(4, 6; "number of validators greater than the number of shares")] fn test_dkg_simple_tdec_precomputed(shares_num: u32, validators_num: u32) { let rng = &mut test_rng(); @@ -301,8 +301,8 @@ mod test_dkg_full { assert_eq!(plaintext, MSG); } - #[test_case(4, 4; "number of validators is equal to the number of shares")] - #[test_case(4, 6; "number of validators is greater than the number of shares")] + #[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_dkg_simple_tdec_share_verification( shares_num: u32, validators_num: u32, diff --git a/ferveo/src/pvss.rs b/ferveo/src/pvss.rs index edbd8578..6bc074c2 100644 --- a/ferveo/src/pvss.rs +++ b/ferveo/src/pvss.rs @@ -262,7 +262,7 @@ pub fn do_verify_aggregation( // Now, we verify that the aggregated PVSS transcript is a valid aggregation let mut y = E::G1::zero(); - for (_, pvss) in vss.iter() { + for pvss in vss.values() { y += pvss.coeffs[0].into_group(); } if y.into_affine() == pvss_agg_coefficients[0] { @@ -276,7 +276,7 @@ pub fn do_verify_aggregation( impl PubliclyVerifiableSS { /// Verify that this PVSS instance is a valid aggregation of /// the PVSS instances, produced by [`aggregate`], - /// and received by the DKG context `dkg` + /// and received by the DKG context `dkg`. /// Returns the total nr of shares in the aggregated PVSS pub fn verify_aggregation( &self, @@ -395,7 +395,7 @@ pub(crate) fn aggregate( // So now we're iterating over the PVSS instances, and adding their coefficients and shares, and their sigma // sigma is the sum of all the sigma_i, which is the proof of knowledge of the secret polynomial - // Aggregating is just adding the corresponding values in pvss instances, so pvss = pvss + pvss_j + // Aggregating is just adding the corresponding values in PVSS instances, so PVSS_i = PVSS_(i-1) PVSS_i for next_pvss in pvss_iter { sigma = (sigma + next_pvss.sigma).into(); coeffs @@ -502,7 +502,6 @@ mod test_pvss { assert!(!bad_pvss.verify_full(&dkg)); } - // TODO: Move this code to dkg.rs /// Check that the canonical share indices of validators are expected and enforced /// by the DKG methods. #[test]