diff --git a/ferveo-wasm/tests/node.rs b/ferveo-wasm/tests/node.rs index b4234d07..25f24d9f 100644 --- a/ferveo-wasm/tests/node.rs +++ b/ferveo-wasm/tests/node.rs @@ -8,8 +8,9 @@ use wasm_bindgen_test::*; type TestSetup = ( u32, - usize, - usize, + u32, + u32, + u32, Vec, Vec, ValidatorArray, @@ -21,11 +22,13 @@ type TestSetup = ( fn setup_dkg() -> TestSetup { let tau = 1; - let shares_num = 16; + let shares_num: u32 = 16; let security_threshold = shares_num * 2 / 3; + let min_shares_num = shares_num; - let validator_keypairs = - (0..shares_num).map(gen_keypair).collect::>(); + let validator_keypairs = (0..shares_num as usize) + .map(gen_keypair) + .collect::>(); let validators = validator_keypairs .iter() .enumerate() @@ -38,8 +41,9 @@ fn setup_dkg() -> TestSetup { let messages = validators.iter().map(|sender| { let dkg = Dkg::new( tau, - shares_num as u32, - security_threshold as u32, + shares_num, + min_shares_num, + security_threshold, &validators_js, sender, ) @@ -54,8 +58,9 @@ fn setup_dkg() -> TestSetup { let mut dkg = Dkg::new( tau, - shares_num as u32, - security_threshold as u32, + shares_num, + min_shares_num, + security_threshold, &validators_js, &validators[0], ) @@ -81,6 +86,7 @@ fn setup_dkg() -> TestSetup { ( tau, shares_num, + min_shares_num, security_threshold, validator_keypairs, validators, @@ -97,6 +103,7 @@ fn tdec_simple() { let ( tau, shares_num, + min_shares_num, security_threshold, validator_keypairs, validators, @@ -112,8 +119,9 @@ fn tdec_simple() { .map(|(validator, keypair)| { let mut dkg = Dkg::new( tau, - shares_num as u32, - security_threshold as u32, + shares_num, + min_shares_num, + security_threshold, &validators_js, &validator, ) @@ -151,6 +159,7 @@ fn tdec_precomputed() { let ( tau, shares_num, + min_shares_num, security_threshold, validator_keypairs, validators, @@ -166,8 +175,9 @@ fn tdec_precomputed() { .map(|(validator, keypair)| { let mut dkg = Dkg::new( tau, - shares_num as u32, - security_threshold as u32, + shares_num, + min_shares_num, + security_threshold, &validators_js, &validator, ) diff --git a/ferveo/benches/benchmarks/validity_checks.rs b/ferveo/benches/benchmarks/validity_checks.rs index a6dd9f48..381f1070 100644 --- a/ferveo/benches/benchmarks/validity_checks.rs +++ b/ferveo/benches/benchmarks/validity_checks.rs @@ -45,11 +45,7 @@ fn setup_dkg( let me = validators[validator].clone(); PubliclyVerifiableDkg::new( &validators, - &DkgParams { - tau: 0, - security_threshold: shares_num / 3, - shares_num, - }, + &DkgParams::new(0, shares_num / 3, shares_num, shares_num).unwrap(), &me, ) .expect("Setup failed") diff --git a/ferveo/examples/bench_primitives_size.rs b/ferveo/examples/bench_primitives_size.rs index 18adf673..8f133200 100644 --- a/ferveo/examples/bench_primitives_size.rs +++ b/ferveo/examples/bench_primitives_size.rs @@ -80,11 +80,7 @@ fn setup_dkg( let me = validators[validator].clone(); PubliclyVerifiableDkg::new( &validators, - &DkgParams { - tau: 0, - security_threshold, - shares_num, - }, + &DkgParams::new(0, security_threshold, shares_num, shares_num).unwrap(), &me, ) .expect("Setup failed") diff --git a/ferveo/src/api.rs b/ferveo/src/api.rs index af3edcd4..e62a718a 100644 --- a/ferveo/src/api.rs +++ b/ferveo/src/api.rs @@ -199,15 +199,17 @@ impl Dkg { pub fn new( tau: u32, shares_num: u32, + min_shares_num: u32, security_threshold: u32, validators: &[Validator], me: &Validator, ) -> Result { - let dkg_params = crate::DkgParams { + let dkg_params = crate::DkgParams::new( tau, security_threshold, + min_shares_num, shares_num, - }; + )?; let dkg = crate::PubliclyVerifiableDkg::::new( validators, &dkg_params, @@ -312,7 +314,7 @@ impl AggregatedTranscript { .0 .domain .elements() - .take(dkg.0.dkg_params.shares_num as usize) + .take(dkg.0.dkg_params.shares_num() as usize) .collect(); self.0.make_decryption_share_simple_precomputed( &ciphertext_header.0, @@ -406,6 +408,7 @@ mod test_ferveo_api { rng: &mut StdRng, tau: u32, security_threshold: u32, + min_shares_num: u32, shares_num: u32, ) -> TestInputs { let validator_keypairs = gen_keypairs(shares_num); @@ -426,6 +429,7 @@ mod test_ferveo_api { let dkg = Dkg::new( tau, shares_num, + min_shares_num, security_threshold, &validators, sender, @@ -456,16 +460,28 @@ mod test_ferveo_api { // TODO: Refactor DKG constructor to not require security threshold or this case. // Or figure out a different way to simplify the precomputed variant API. let security_threshold = shares_num; + let min_shares_num = shares_num; - let (messages, validators, validator_keypairs) = - make_test_inputs(rng, tau, security_threshold, shares_num); + let (messages, validators, validator_keypairs) = make_test_inputs( + rng, + tau, + security_threshold, + min_shares_num, + shares_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, &validators, &me) - .unwrap(); + let mut dkg = Dkg::new( + tau, + shares_num, + min_shares_num, + security_threshold, + &validators, + &me, + ) + .unwrap(); let pvss_aggregated = dkg.aggregate_transcripts(&messages).unwrap(); assert!(pvss_aggregated.verify(shares_num, &messages).unwrap()); @@ -489,6 +505,7 @@ mod test_ferveo_api { tau, shares_num, security_threshold, + min_shares_num, &validators, validator, ) @@ -546,15 +563,22 @@ mod test_ferveo_api { for shares_num in [4, 7] { let tau = 1; let security_threshold = shares_num / 2 + 1; + let min_shares_num = shares_num; - let (messages, validators, validator_keypairs) = - make_test_inputs(rng, tau, security_threshold, shares_num); + let (messages, validators, validator_keypairs) = make_test_inputs( + rng, + tau, + security_threshold, + min_shares_num, + shares_num, + ); // 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( tau, shares_num, + min_shares_num, security_threshold, &validators, &validators[0], @@ -581,6 +605,7 @@ mod test_ferveo_api { let mut dkg = Dkg::new( tau, shares_num, + min_shares_num, security_threshold, &validators, validator, @@ -632,22 +657,34 @@ mod test_ferveo_api { let rng = &mut StdRng::seed_from_u64(0); let tau = 1; - let security_threshold = 3; let shares_num = 4; + let min_shares_num = shares_num; + let security_threshold = shares_num - 1; - let (messages, validators, _) = - make_test_inputs(rng, tau, security_threshold, shares_num); + let (messages, validators, _) = make_test_inputs( + rng, + tau, + security_threshold, + min_shares_num, + shares_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, &validators, &me) - .unwrap(); + let mut dkg = Dkg::new( + tau, + shares_num, + min_shares_num, + security_threshold, + &validators, + &me, + ) + .unwrap(); let local_aggregate = dkg.aggregate_transcripts(&messages).unwrap(); assert!(local_aggregate - .verify(dkg.0.dkg_params.shares_num, &messages) + .verify(dkg.0.dkg_params.shares_num(), &messages) .is_ok()); } @@ -656,11 +693,17 @@ mod test_ferveo_api { let rng = &mut StdRng::seed_from_u64(0); let tau = 1; - let security_threshold = 3; let shares_num = 4; + let security_threshold = shares_num - 1; + let min_shares_num = shares_num; - let (messages, _, _) = - make_test_inputs(rng, tau, security_threshold, shares_num); + let (messages, _, _) = make_test_inputs( + rng, + tau, + security_threshold, + min_shares_num, + shares_num, + ); // We only need `security_threshold` transcripts to aggregate let messages = &messages[..security_threshold as usize]; @@ -688,8 +731,13 @@ mod test_ferveo_api { // Unexpected transcripts in the aggregate or transcripts from a different ritual // Using same DKG parameters, but different DKG instances and validators - let (bad_messages, _, _) = - make_test_inputs(rng, tau, security_threshold, shares_num); + let (bad_messages, _, _) = make_test_inputs( + rng, + tau, + security_threshold, + min_shares_num, + shares_num, + ); let mixed_messages = [&messages[..2], &bad_messages[..1]].concat(); let bad_aggregate = AggregatedTranscript::new(&mixed_messages); let result = bad_aggregate.verify(shares_num, messages); diff --git a/ferveo/src/bindings_python.rs b/ferveo/src/bindings_python.rs index ed965f3e..09fa0753 100644 --- a/ferveo/src/bindings_python.rs +++ b/ferveo/src/bindings_python.rs @@ -93,6 +93,11 @@ impl From for PyErr { } Error::InvalidVariant(variant) => { InvalidVariant::new_err(variant.to_string()) + }, + Error::InvalidDkgParameters(num_shares, security_threshold, min_shares_num) => { + InvalidDkgParameters::new_err(format!( + "num_shares: {num_shares}, security_threshold: {security_threshold}, min_shares_num: {min_shares_num}" + )) } }, _ => default(), @@ -128,6 +133,7 @@ create_exception!(exceptions, ValidatorPublicKeyMismatch, PyValueError); create_exception!(exceptions, SerializationError, PyValueError); create_exception!(exceptions, InvalidByteLength, PyValueError); create_exception!(exceptions, InvalidVariant, PyValueError); +create_exception!(exceptions, InvalidDkgParameters, PyValueError); fn from_py_bytes(bytes: &[u8]) -> PyResult { T::from_bytes(bytes) @@ -452,6 +458,7 @@ impl Dkg { pub fn new( tau: u32, shares_num: u32, + min_shares_num: u32, security_threshold: u32, validators: Vec, me: &Validator, @@ -460,6 +467,7 @@ impl Dkg { let dkg = api::Dkg::new( tau, shares_num, + min_shares_num, security_threshold, &validators, &me.0, @@ -734,6 +742,7 @@ mod test_ferveo_python { fn make_test_inputs( tau: u32, security_threshold: u32, + min_shares_num: u32, shares_num: u32, ) -> TestInputs { let validator_keypairs = (0..shares_num) @@ -757,6 +766,7 @@ mod test_ferveo_python { let dkg = Dkg::new( tau, shares_num, + min_shares_num, security_threshold, validators.clone(), &sender, @@ -777,9 +787,14 @@ mod test_ferveo_python { let shares_num = 4; // In precomputed variant, the security threshold is equal to the number of shares let security_threshold = shares_num; + let min_shares_num = 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, + min_shares_num, + shares_num, + ); // Now that every validator holds a dkg instance and a transcript for every other validator, // every validator can aggregate the transcripts @@ -788,6 +803,7 @@ mod test_ferveo_python { let mut dkg = Dkg::new( tau, shares_num, + min_shares_num, security_threshold, validators.clone(), &me, @@ -817,6 +833,7 @@ mod test_ferveo_python { let mut dkg = Dkg::new( tau, shares_num, + min_shares_num, security_threshold, validators.clone(), validator, @@ -854,10 +871,15 @@ mod test_ferveo_python { fn test_server_api_tdec_simple() { let tau = 1; let shares_num = 4; - let security_threshold = 3; + let security_threshold = shares_num - 1; + let min_shares_num = 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, + min_shares_num, + shares_num, + ); // Now that every validator holds a dkg instance and a transcript for every other validator, // every validator can aggregate the transcripts @@ -865,6 +887,7 @@ mod test_ferveo_python { let mut dkg = Dkg::new( tau, shares_num, + min_shares_num, security_threshold, validators.clone(), &me, @@ -894,6 +917,7 @@ mod test_ferveo_python { let mut dkg = Dkg::new( tau, shares_num, + min_shares_num, security_threshold, validators.clone(), validator, diff --git a/ferveo/src/bindings_wasm.rs b/ferveo/src/bindings_wasm.rs index e412b6e0..5daae742 100644 --- a/ferveo/src/bindings_wasm.rs +++ b/ferveo/src/bindings_wasm.rs @@ -340,6 +340,7 @@ impl Dkg { pub fn new( tau: u32, shares_num: u32, + min_shares_num: u32, security_threshold: u32, validators_js: &ValidatorArray, me: &Validator, @@ -352,6 +353,7 @@ impl Dkg { let dkg = api::Dkg::new( tau, shares_num, + min_shares_num, security_threshold, &validators, &me.to_inner()?, @@ -510,15 +512,13 @@ impl AggregatedTranscript { #[wasm_bindgen] pub fn verify( &self, - shares_num: usize, + shares_num: u32, messages: &ValidatorMessageArray, ) -> JsResult { set_panic_hook(); let messages = unwrap_messages_js(messages)?; - let is_valid = self - .0 - .verify(shares_num as u32, &messages) - .map_err(map_js_err)?; + let is_valid = + self.0.verify(shares_num, &messages).map_err(map_js_err)?; Ok(is_valid) } diff --git a/ferveo/src/dkg.rs b/ferveo/src/dkg.rs index 3f7fc09d..cf11acf8 100644 --- a/ferveo/src/dkg.rs +++ b/ferveo/src/dkg.rs @@ -15,9 +15,63 @@ use crate::{ #[derive(Copy, Clone, Debug, Serialize, Deserialize)] pub struct DkgParams { - pub tau: u32, - pub security_threshold: u32, - pub shares_num: u32, + tau: u32, + security_threshold: u32, + min_shares_num: u32, + shares_num: u32, +} + +impl DkgParams { + pub fn new( + tau: u32, + security_threshold: u32, + min_shares_num: u32, + shares_num: u32, + ) -> Result { + if min_shares_num > shares_num { + return Err(Error::InvalidDkgParameters( + shares_num, + security_threshold, + min_shares_num, + )); + } + if shares_num < security_threshold { + return Err(Error::InvalidDkgParameters( + shares_num, + security_threshold, + min_shares_num, + )); + } + if min_shares_num < security_threshold { + return Err(Error::InvalidDkgParameters( + shares_num, + security_threshold, + min_shares_num, + )); + } + Ok(Self { + tau, + security_threshold, + min_shares_num, + shares_num, + }) + } + + pub fn tau(&self) -> u32 { + self.tau + } + + pub fn security_threshold(&self) -> u32 { + self.security_threshold + } + + pub fn min_shares_num(&self) -> u32 { + self.min_shares_num + } + + pub fn shares_num(&self) -> u32 { + self.shares_num + } } #[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)] @@ -350,6 +404,7 @@ pub(crate) mod test_common { pub fn setup_dkg_for_n_validators( security_threshold: u32, + min_shares_num: u32, shares_num: u32, my_index: usize, ) -> TestSetup { @@ -359,11 +414,8 @@ pub(crate) mod test_common { let me = validators[my_index].clone(); let dkg = PubliclyVerifiableDkg::new( &validators, - &DkgParams { - tau: 0, - security_threshold, - shares_num, - }, + &DkgParams::new(0, security_threshold, min_shares_num, shares_num) + .unwrap(), &me, ) .expect("Setup failed"); @@ -374,18 +426,19 @@ pub(crate) mod test_common { /// /// The [`test_dkg_init`] module checks correctness of this setup pub fn setup_dkg(validator: usize) -> TestSetup { - setup_dkg_for_n_validators(2, 4, validator) + setup_dkg_for_n_validators(2, 4, 4, validator) } /// Set up a dkg with enough pvss transcripts to meet the threshold /// /// The correctness of this function is tested in the module [`test_dealing`] pub fn setup_dealt_dkg() -> TestSetup { - setup_dealt_dkg_with_n_validators(2, 4) + setup_dealt_dkg_with_n_validators(2, 4, 4) } pub fn setup_dealt_dkg_with_n_validators( security_threshold: u32, + min_shares_num: u32, shares_num: u32, ) -> TestSetup { let rng = &mut ark_std::test_rng(); @@ -395,6 +448,7 @@ pub(crate) mod test_common { .map(|my_index| { let (mut dkg, _) = setup_dkg_for_n_validators( security_threshold, + min_shares_num, shares_num, my_index as usize, ); @@ -405,8 +459,12 @@ pub(crate) mod test_common { .collect(); // Create a test DKG instance - let (mut dkg, keypairs) = - setup_dkg_for_n_validators(security_threshold, shares_num, 0); + let (mut dkg, keypairs) = setup_dkg_for_n_validators( + security_threshold, + min_shares_num, + shares_num, + 0, + ); messages.iter().for_each(|(sender, message)| { dkg.apply_message(sender, message).expect("Setup failed"); }); @@ -436,6 +494,7 @@ mod test_dkg_init { &DkgParams { tau: 0, security_threshold: shares_num / 2, + min_shares_num: shares_num - 1, shares_num, }, &unknown_validator, diff --git a/ferveo/src/lib.rs b/ferveo/src/lib.rs index 59a44024..22f10f65 100644 --- a/ferveo/src/lib.rs +++ b/ferveo/src/lib.rs @@ -101,6 +101,9 @@ pub enum Error { #[error("Invalid variant: {0}")] InvalidVariant(String), + + #[error("Invalid DKG parameters: number of shares {0}, threshold {1}, minimum number of shares {2}")] + InvalidDkgParameters(u32, u32, u32), } pub type Result = std::result::Result; @@ -196,8 +199,12 @@ mod test_dkg_full { // Works for both power of 2 and non-power of 2 for shares_num in [4, 7] { let threshold = shares_num / 2 + 1; - let (dkg, validator_keypairs) = - setup_dealt_dkg_with_n_validators(threshold, shares_num); + let min_shares_num = shares_num - 1; + let (dkg, validator_keypairs) = setup_dealt_dkg_with_n_validators( + threshold, + min_shares_num, + shares_num, + ); let msg = "my-msg".as_bytes().to_vec(); let aad: &[u8] = "my-aad".as_bytes(); let public_key = dkg.public_key(); @@ -235,8 +242,12 @@ mod test_dkg_full { for shares_num in [4, 7] { // In precomputed variant, threshold must be equal to shares_num let threshold = shares_num; - let (dkg, validator_keypairs) = - setup_dealt_dkg_with_n_validators(threshold, shares_num); + let min_shares_num = shares_num; + let (dkg, validator_keypairs) = setup_dealt_dkg_with_n_validators( + threshold, + min_shares_num, + shares_num, + ); let msg = "my-msg".as_bytes().to_vec(); let aad: &[u8] = "my-aad".as_bytes(); let public_key = dkg.public_key(); @@ -295,8 +306,15 @@ mod test_dkg_full { #[test] fn test_dkg_simple_tdec_share_verification() { let rng = &mut test_rng(); + let shares_num = 4; + let security_threshold = shares_num - 1; + let min_shares_num = shares_num - 1; - let (dkg, validator_keypairs) = setup_dealt_dkg_with_n_validators(3, 4); + let (dkg, validator_keypairs) = setup_dealt_dkg_with_n_validators( + security_threshold, + min_shares_num, + shares_num, + ); let msg = "my-msg".as_bytes().to_vec(); let aad: &[u8] = "my-aad".as_bytes(); let public_key = dkg.public_key(); @@ -360,10 +378,14 @@ mod test_dkg_full { fn test_dkg_simple_tdec_share_recovery() { let rng = &mut test_rng(); - let security_threshold = 3; let shares_num = 4; - let (dkg, validator_keypairs) = - setup_dealt_dkg_with_n_validators(security_threshold, shares_num); + let security_threshold = shares_num - 1; + let min_shares_num = shares_num - 1; + let (dkg, validator_keypairs) = setup_dealt_dkg_with_n_validators( + security_threshold, + min_shares_num, + shares_num, + ); let msg = "my-msg".as_bytes().to_vec(); let aad: &[u8] = "my-aad".as_bytes(); let public_key = &dkg.public_key(); @@ -410,7 +432,7 @@ mod test_dkg_full { &domain_points, &dkg.pvss_params.h.into_affine(), &x_r, - dkg.dkg_params.security_threshold as usize, + dkg.dkg_params.security_threshold() as usize, rng, ); (v_addr.clone(), deltas_i) @@ -519,10 +541,14 @@ mod test_dkg_full { fn test_dkg_simple_tdec_share_refreshing() { let rng = &mut test_rng(); - let security_threshold = 3; let shares_num = 4; - let (dkg, validator_keypairs) = - setup_dealt_dkg_with_n_validators(security_threshold, shares_num); + let min_shares_num = shares_num - 1; + let security_threshold = shares_num - 1; + let (dkg, validator_keypairs) = setup_dealt_dkg_with_n_validators( + security_threshold, + min_shares_num, + shares_num, + ); let msg = "my-msg".as_bytes().to_vec(); let aad: &[u8] = "my-aad".as_bytes(); let public_key = &dkg.public_key(); @@ -552,7 +578,7 @@ mod test_dkg_full { let deltas_i = prepare_share_updates_for_refresh::( &domain_points, &dkg.pvss_params.h.into_affine(), - dkg.dkg_params.security_threshold as usize, + dkg.dkg_params.security_threshold() as usize, rng, ); (v_addr.clone(), deltas_i) diff --git a/ferveo/src/pvss.rs b/ferveo/src/pvss.rs index 4f63da82..db91f9d2 100644 --- a/ferveo/src/pvss.rs +++ b/ferveo/src/pvss.rs @@ -138,7 +138,7 @@ impl PubliclyVerifiableSS { ) -> Result { let phi = SecretPolynomial::::new( s, - (dkg.dkg_params.security_threshold - 1) as usize, + (dkg.dkg_params.security_threshold() - 1) as usize, rng, ); @@ -482,7 +482,7 @@ mod test_pvss { // Check that a polynomial of the correct degree was created assert_eq!( pvss.coeffs.len(), - dkg.dkg_params.security_threshold as usize + dkg.dkg_params.security_threshold() as usize ); // Check that the correct number of shares were created assert_eq!(pvss.shares.len(), dkg.validators.len()); @@ -544,6 +544,7 @@ mod test_pvss { let shares_num = 4; let security_threshold = shares_num - 1; + let min_shares_num = shares_num - 1; let keypairs = gen_keypairs(shares_num); let mut validators = gen_validators(&keypairs); let me = validators[0].clone(); @@ -555,11 +556,8 @@ mod test_pvss { // And because of that the DKG should fail let result = PubliclyVerifiableDkg::new( &validators, - &DkgParams { - tau: 0, - security_threshold, - shares_num, - }, + &DkgParams::new(0, security_threshold, min_shares_num, shares_num) + .unwrap(), &me, ); assert!(result.is_err()); @@ -578,7 +576,7 @@ mod test_pvss { // Check that a polynomial of the correct degree was created assert_eq!( aggregate.coeffs.len(), - dkg.dkg_params.security_threshold as usize + dkg.dkg_params.security_threshold() as usize ); // Check that the correct number of shares were created assert_eq!(aggregate.shares.len(), dkg.validators.len());