From 85d96dededc34bd406650492d1f3ddd5eaa30342 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Tue, 30 Jan 2024 15:47:31 +0000 Subject: [PATCH] fixed bug in DKG to allow for different sets of dealers and receivers --- common/dkg/src/dealing.rs | 117 +++++++++++++++++++++++++++----- common/dkg/tests/integration.rs | 87 ++++++++++++++---------- 2 files changed, 150 insertions(+), 54 deletions(-) diff --git a/common/dkg/src/dealing.rs b/common/dkg/src/dealing.rs index fab0000ade..6cc8afccb3 100644 --- a/common/dkg/src/dealing.rs +++ b/common/dkg/src/dealing.rs @@ -320,9 +320,17 @@ impl<'a> TryFrom<&'a nym_contracts_common::dealings::ContractSafeBytes> for Deal } } -// this assumes all dealings have been verified +/// Attempt to run the `VkCombine` algorithm to obtain the public master verification key, `VK` +/// alongside shares of the verification key, `shvk_{1}`, `shvk_{2}`, ... `svhk_{n}`, where n is the number of receivers. +/// +/// # Arguments +/// +/// * `dealings`: map of dealer indices to dealings they generated +/// * `threshold`: explicit threshold value of the associated dealings +/// * `receivers`:map of receiver indices to their public keys +// note: this function assumes all dealings have already been verified pub fn try_recover_verification_keys( - dealings: &[Dealing], + dealings: &BTreeMap, threshold: Threshold, receivers: &BTreeMap, ) -> Result { @@ -330,24 +338,31 @@ pub fn try_recover_verification_keys( return Err(DkgError::NoDealingsAvailable); } - let threshold_usize = threshold as usize; + let threshold = threshold as usize; + + if dealings.len() < threshold { + return Err(DkgError::NotEnoughDealingsAvailable { + available: dealings.len(), + required: threshold, + }); + } if !dealings - .iter() - .all(|dealing| dealing.public_coefficients.size() == threshold_usize) + .values() + .all(|dealing| dealing.public_coefficients.size() == threshold) { return Err(DkgError::MismatchedDealings); } - let indices = receivers.keys().collect::>(); + let dealer_indices = dealings.keys().collect::>(); // Compute A0, ..., A_{t-1} - let mut interpolated_coefficients = Vec::with_capacity(threshold_usize); - for k in 0..threshold_usize { - let mut samples = Vec::with_capacity(indices.len()); - for (j, dealing) in dealings.iter().enumerate() { + let mut interpolated_coefficients = Vec::with_capacity(threshold); + for k in 0..threshold { + let mut samples = Vec::with_capacity(dealer_indices.len()); + for (dealer_index, dealing) in dealings.iter() { samples.push(( - Scalar::from(*indices[j]), + Scalar::from(*dealer_index), *dealing.public_coefficients.nth(k), )) } @@ -364,7 +379,7 @@ pub fn try_recover_verification_keys( // shvk_j = A0^{j^0} * A1^{j^1} * ... * A_{t-1}^{j^{t-1}} let verification_key_shares = receivers .keys() - .map(|index| interpolated_coefficients.evaluate_at(&Scalar::from(*index))) + .map(|receiver_index| interpolated_coefficients.evaluate_at(&Scalar::from(*receiver_index))) .collect(); Ok(RecoveredVerificationKeys { @@ -456,14 +471,17 @@ mod tests { let dealings = node_indices .iter() .map(|&dealer_index| { - Dealing::create(&mut rng, ¶ms, dealer_index, threshold, &receivers, None).0 + ( + dealer_index, + Dealing::create(&mut rng, ¶ms, dealer_index, threshold, &receivers, None).0, + ) }) - .collect::>(); + .collect::>(); let mut derived_secrets = Vec::new(); - for (i, (ref mut dk, _)) in full_keys.iter_mut().enumerate() { + for (i, (ref dk, _)) in full_keys.iter().enumerate() { let shares = dealings - .iter() + .values() .map(|dealing| decrypt_share(dk, i, &dealing.ciphertexts, None).unwrap()) .collect(); derived_secrets.push( @@ -512,9 +530,12 @@ mod tests { let dealings = node_indices .iter() .map(|&dealer_index| { - Dealing::create(&mut rng, ¶ms, dealer_index, threshold, &receivers, None).0 + ( + dealer_index, + Dealing::create(&mut rng, ¶ms, dealer_index, threshold, &receivers, None).0, + ) }) - .collect::>(); + .collect::>(); let RecoveredVerificationKeys { recovered_master, @@ -530,6 +551,66 @@ mod tests { .is_ok()) } + #[test] + #[ignore] // expensive test + fn verifying_partial_verification_keys_with_different_dealers_and_receivers() { + let dummy_seed = [42u8; 32]; + let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed); + let params = setup(); + + let dealer_indices = [1, 2, 3, 8]; + let receiver_indices = [3, 4, 5, 6, 7]; + let threshold = 3; + + let mut receivers = BTreeMap::new(); + let mut full_keys = Vec::new(); + for index in &receiver_indices { + let (dk, pk) = keygen(¶ms, &mut rng); + receivers.insert(*index, *pk.public_key()); + full_keys.push((dk, pk)) + } + + let dealings = dealer_indices + .iter() + .map(|&dealer_index| { + ( + dealer_index, + Dealing::create(&mut rng, ¶ms, dealer_index, threshold, &receivers, None).0, + ) + }) + .collect::>(); + + let RecoveredVerificationKeys { + recovered_master, + recovered_partials, + } = try_recover_verification_keys(&dealings, threshold, &receivers).unwrap(); + + let g2 = G2Projective::generator(); + + let mut derived_secrets = Vec::new(); + for (i, (dk, _)) in full_keys.iter().enumerate() { + let shares = dealings + .values() + .map(|dealing| decrypt_share(dk, i, &dealing.ciphertexts, None).unwrap()) + .collect(); + + let recovered_secret = combine_shares(shares, &dealer_indices).unwrap(); + + // make sure it matches the associated vk + assert_eq!(recovered_partials[i], g2 * recovered_secret); + + derived_secrets.push(recovered_secret) + } + + assert!(verify_verification_keys( + &recovered_master, + &recovered_partials, + &receivers, + threshold + ) + .is_ok()) + } + #[test] #[ignore] // expensive test fn dealing_roundtrip() { diff --git a/common/dkg/tests/integration.rs b/common/dkg/tests/integration.rs index 2e7ffcda07..81c861425d 100644 --- a/common/dkg/tests/integration.rs +++ b/common/dkg/tests/integration.rs @@ -52,7 +52,7 @@ fn single_sender() { .unwrap(); // make sure each share is actually decryptable (even though proofs say they must be, perform this sanity check) - for (i, (ref mut dk, _)) in full_keys.iter_mut().enumerate() { + for (i, (ref dk, _)) in full_keys.iter().enumerate() { let _recovered = decrypt_share(dk, i, &dealing.ciphertexts, None).unwrap(); } @@ -91,10 +91,13 @@ fn full_threshold_secret_sharing() { let dealings = node_indices .iter() .map(|&dealer_index| { - Dealing::create(&mut rng, ¶ms, dealer_index, threshold, &receivers, None).0 + ( + dealer_index, + Dealing::create(&mut rng, ¶ms, dealer_index, threshold, &receivers, None).0, + ) }) - .collect::>(); - for dealing in dealings.iter() { + .collect::>(); + for dealing in dealings.values() { dealing .verify(¶ms, threshold, &receivers, None) .unwrap(); @@ -109,9 +112,9 @@ fn full_threshold_secret_sharing() { let g2 = G2Projective::generator(); let mut derived_secrets = Vec::new(); - for (i, (ref mut dk, _)) in full_keys.iter_mut().enumerate() { + for (i, (ref dk, _)) in full_keys.iter().enumerate() { let shares = dealings - .iter() + .values() .map(|dealing| decrypt_share(dk, i, &dealing.ciphertexts, None).unwrap()) .collect(); @@ -169,9 +172,12 @@ fn full_threshold_secret_resharing() { let first_dealings = node_indices .iter() .map(|&dealer_index| { - Dealing::create(&mut rng, ¶ms, dealer_index, threshold, &receivers, None).0 + ( + dealer_index, + Dealing::create(&mut rng, ¶ms, dealer_index, threshold, &receivers, None).0, + ) }) - .collect::>(); + .collect::>(); // recover verification keys let RecoveredVerificationKeys { @@ -180,9 +186,9 @@ fn full_threshold_secret_resharing() { } = try_recover_verification_keys(&first_dealings, threshold, &receivers).unwrap(); let mut derived_secrets = Vec::new(); - for (i, (ref mut dk, _)) in full_keys.iter_mut().enumerate() { + for (i, (ref dk, _)) in full_keys.iter().enumerate() { let shares = first_dealings - .iter() + .values() .map(|dealing| decrypt_share(dk, i, &dealing.ciphertexts, None).unwrap()) .collect(); @@ -203,19 +209,22 @@ fn full_threshold_secret_resharing() { .iter() .zip(derived_secrets.iter()) .map(|(&dealer_index, prior_secret)| { - Dealing::create( - &mut rng, - ¶ms, + ( dealer_index, - threshold, - &receivers, - Some(*prior_secret), + Dealing::create( + &mut rng, + ¶ms, + dealer_index, + threshold, + &receivers, + Some(*prior_secret), + ) + .0, ) - .0 }) - .collect::>(); + .collect::>(); - for (reshared_dealing, prior_vk) in resharing_dealings.iter().zip(recovered_partials.iter()) { + for (reshared_dealing, prior_vk) in resharing_dealings.values().zip(recovered_partials.iter()) { reshared_dealing .verify(¶ms, threshold, &receivers, Some(*prior_vk)) .unwrap(); @@ -228,9 +237,9 @@ fn full_threshold_secret_resharing() { } = try_recover_verification_keys(&resharing_dealings, threshold, &receivers).unwrap(); let mut reshared_secrets = Vec::new(); - for (i, (ref mut dk, _)) in full_keys.iter_mut().enumerate() { + for (i, (ref dk, _)) in full_keys.iter().enumerate() { let shares = resharing_dealings - .iter() + .values() .map(|dealing| decrypt_share(dk, i, &dealing.ciphertexts, None).unwrap()) .collect(); @@ -279,9 +288,12 @@ fn full_threshold_secret_resharing_left_party() { let first_dealings = node_indices .iter() .map(|&dealer_index| { - Dealing::create(&mut rng, ¶ms, dealer_index, threshold, &receivers, None).0 + ( + dealer_index, + Dealing::create(&mut rng, ¶ms, dealer_index, threshold, &receivers, None).0, + ) }) - .collect::>(); + .collect::>(); // recover verification keys let RecoveredVerificationKeys { @@ -290,9 +302,9 @@ fn full_threshold_secret_resharing_left_party() { } = try_recover_verification_keys(&first_dealings, threshold, &receivers).unwrap(); let mut derived_secrets = Vec::new(); - for (i, (ref mut dk, _)) in full_keys.iter_mut().enumerate() { + for (i, (ref dk, _)) in full_keys.iter().enumerate() { let shares = first_dealings - .iter() + .values() .map(|dealing| decrypt_share(dk, i, &dealing.ciphertexts, None).unwrap()) .collect(); @@ -323,20 +335,23 @@ fn full_threshold_secret_resharing_left_party() { .iter() .zip(derived_secrets.iter().take(2)) .map(|(&dealer_index, prior_secret)| { - Dealing::create( - &mut rng, - ¶ms, + ( dealer_index, - threshold, - &receivers, - Some(*prior_secret), + Dealing::create( + &mut rng, + ¶ms, + dealer_index, + threshold, + &receivers, + Some(*prior_secret), + ) + .0, ) - .0 }) - .collect::>(); + .collect::>(); for (reshared_dealing, prior_vk) in resharing_dealings - .iter() + .values() .zip(recovered_partials.iter().take(2)) { reshared_dealing @@ -351,9 +366,9 @@ fn full_threshold_secret_resharing_left_party() { } = try_recover_verification_keys(&resharing_dealings, threshold, &receivers).unwrap(); let mut reshared_secrets = Vec::new(); - for (i, (ref mut dk, _)) in full_keys.iter_mut().enumerate() { + for (i, (ref dk, _)) in full_keys.iter().enumerate() { let shares = resharing_dealings - .iter() + .values() .map(|dealing| decrypt_share(dk, i, &dealing.ciphertexts, None).unwrap()) .collect();