diff --git a/common/crypto/dkg/src/dealing.rs b/common/crypto/dkg/src/dealing.rs index b3633ec82d..d92a9ba6b4 100644 --- a/common/crypto/dkg/src/dealing.rs +++ b/common/crypto/dkg/src/dealing.rs @@ -326,11 +326,6 @@ pub fn try_recover_verification_keys( return Err(DkgError::MismatchedDealings); } - // currently we expect every dealer to also be a receiver. This restriction might be relaxed in the future - if dealings.len() != receivers.len() { - return Err(DkgError::MismatchedDealings); - } - let indices = receivers.keys().collect::>(); // Compute A0, ..., A_{t-1} diff --git a/common/crypto/dkg/tests/integration.rs b/common/crypto/dkg/tests/integration.rs index 2d25c31d40..4ac55df1c7 100644 --- a/common/crypto/dkg/tests/integration.rs +++ b/common/crypto/dkg/tests/integration.rs @@ -254,3 +254,125 @@ fn full_threshold_secret_resharing() { assert_ne!(derived_secrets, reshared_secrets); assert_ne!(recovered_partials, reshared_partials); } + +#[test] +#[ignore] // expensive test +fn full_threshold_secret_resharing_left_party() { + let dummy_seed = [42u8; 32]; + let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed); + let params = setup(); + + // the simplest possible case + let threshold = 2; + + // the indices are going to get assigned externally, so for test sake, use non-consecutive ones + let mut node_indices = vec![15u64, 248, 33521]; + + let mut receivers = BTreeMap::new(); + let mut full_keys = Vec::new(); + for index in &node_indices { + let (dk, pk) = keygen(¶ms, &mut rng); + receivers.insert(*index, *pk.public_key()); + full_keys.push((dk, pk)) + } + + let first_dealings = node_indices + .iter() + .map(|&dealer_index| { + Dealing::create(&mut rng, ¶ms, dealer_index, threshold, &receivers, None).0 + }) + .collect::>(); + + // recover verification keys + let RecoveredVerificationKeys { + recovered_master: public_original_master, + recovered_partials, + } = 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() { + let shares = first_dealings + .iter() + .map(|dealing| decrypt_share(dk, i, &dealing.ciphertexts, None).unwrap()) + .collect(); + + let recovered_secret = + combine_shares(shares, &receivers.keys().copied().collect::>()).unwrap(); + + derived_secrets.push(recovered_secret) + } + + let original_master = perform_lagrangian_interpolation_at_origin(&[ + (Scalar::from(node_indices[0]), derived_secrets[0]), + (Scalar::from(node_indices[1]), derived_secrets[1]), + ]) + .unwrap(); + + // one party leaves the process + let left_party_index = node_indices.pop().unwrap(); + receivers.remove(&left_party_index); + full_keys.pop(); + // and another one joins, but we're still over the threshold value of initial parties + let join_party_index = 100000; + let (dk, pk) = keygen(¶ms, &mut rng); + receivers.insert(join_party_index, *pk.public_key()); + full_keys.push((dk, pk)); + + // only initial parties attempt to create resharing dealings! + let resharing_dealings = node_indices + .iter() + .zip(derived_secrets.iter().take(2)) + .map(|(&dealer_index, prior_secret)| { + Dealing::create( + &mut rng, + ¶ms, + dealer_index, + threshold, + &receivers, + Some(*prior_secret), + ) + .0 + }) + .collect::>(); + + for (reshared_dealing, prior_vk) in resharing_dealings + .iter() + .zip(recovered_partials.iter().take(2)) + { + reshared_dealing + .verify(¶ms, threshold, &receivers, Some(*prior_vk)) + .unwrap(); + } + + // recover verification keys + let RecoveredVerificationKeys { + recovered_master: public_reshared_master, + recovered_partials: reshared_partials, + } = 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() { + let shares = resharing_dealings + .iter() + .map(|dealing| decrypt_share(dk, i, &dealing.ciphertexts, None).unwrap()) + .collect(); + + let recovered_secret = combine_shares(shares, &node_indices).unwrap(); + + reshared_secrets.push(recovered_secret) + } + + let reshared_master = perform_lagrangian_interpolation_at_origin(&[ + (Scalar::from(node_indices[0]), reshared_secrets[0]), + (Scalar::from(join_party_index), reshared_secrets[2]), + ]) + .unwrap(); + + // the master secret and public values didn't change + assert_eq!(original_master, reshared_master); + assert_eq!(public_original_master, public_reshared_master); + + // but partials did + assert_ne!(derived_secrets, reshared_secrets); + assert_ne!(recovered_partials, reshared_partials); +}