cf3fd00350
* - standardise versions for all nym-sdk workspace dependencies - prepend sqlx-pool-guard with 'nym-' * Test remove nym-api from deps * Add oneliner to client_pool doc comments * Add note to commented out docs.rs link in sdk * remove nym-api from script * add publishing file * bring non-binary / contract / tools into workspace version * added more info to publishing.md * make deps workspace version * remove uploaded sphinx-types crate from script * remove erroueously included ignore-defaults * add zeroise to feature * chore: Release * add topology to batch * more cargo versioning * more cargo versioning - wasm utils * more cargo versioning - wasm utils * Add publish=false to manifest for cargo workspaces / crates.io publishing exclusion * remove script now switched to manifest based exclusion * rename import based on rename of contracts-common dep * Making workspace versions for publication + removing unnecessary crates from publication * Remove OOD info from publishing sdk guide * rename contract imports + remove package * temp commit: continuing with removal of path from cargo manifest and replacing with workspace version import for publication * continuing with cargo.toml updates * dryrun only erroring on known version problem crates * remove old published-crates file * Minor comment change * remove default features warning * Additional info on workspace dep comment re publish list * Add missing description to cargo.toml * Fix missing feature flags * Add missing descriptions * Fix remaining path import * Add workspace repo / homepage / documentation links to cargo.toml files * remove workspace version from excluded crate * Remove todo descriptions * Minor comment change * add homepage etc * move from bls git import to nym_bls_fork crate * Modify rest of imports from path to workspace import, excluding binaries * add directory/homepage info * fix cargo fmt * add notes to gitignore * better solution to contracts/ experiment * wasm -> nym_wasm crate renaming * fix fatfinger * add metadata to ecash cargo.toml * stub publishing guide * fix misrevolved netlink- version * Fixes and block publication of rebase re: LP * first pass @ workflows
395 lines
13 KiB
Rust
395 lines
13 KiB
Rust
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
use nym_bls12_381_fork::{G2Projective, Scalar};
|
|
use nym_dkg::bte::{decrypt_share, keygen, setup};
|
|
use nym_dkg::dealing::RecoveredVerificationKeys;
|
|
use nym_dkg::interpolation::perform_lagrangian_interpolation_at_origin;
|
|
use nym_dkg::{combine_shares, try_recover_verification_keys, Dealing};
|
|
use rand_core::SeedableRng;
|
|
use std::collections::BTreeMap;
|
|
|
|
#[test]
|
|
#[ignore] // expensive test
|
|
fn single_sender() {
|
|
// makes it easier to understand than `full_threshold_secret_sharing`
|
|
// and is a good stepping stone, because its everything each node will have to perform (from one point of view)
|
|
|
|
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 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))
|
|
}
|
|
|
|
// TODO: HERE BE SERIALIZATION / DESERIALIZATION THAT'S NOT IMPLEMENTED YET
|
|
// verify remote proofs of key possession
|
|
for key in full_keys.iter() {
|
|
assert!(key.1.verify());
|
|
}
|
|
|
|
let (dealing, dealer_share) = Dealing::create(
|
|
&mut rng,
|
|
¶ms,
|
|
node_indices[0],
|
|
threshold,
|
|
&receivers,
|
|
None,
|
|
);
|
|
dealing
|
|
.verify(¶ms, threshold, &receivers, None)
|
|
.unwrap();
|
|
|
|
// make sure each share is actually decryptable (even though proofs say they must be, perform this sanity check)
|
|
for (i, (ref dk, _)) in full_keys.iter().enumerate() {
|
|
let _recovered = decrypt_share(¶ms, dk, i, &dealing.ciphertexts, None).unwrap();
|
|
}
|
|
|
|
// and for good measure, check that the dealer's share matches decryption result
|
|
let recovered_dealer =
|
|
decrypt_share(¶ms, &full_keys[0].0, 0, &dealing.ciphertexts, None).unwrap();
|
|
assert_eq!(recovered_dealer, dealer_share.unwrap());
|
|
}
|
|
|
|
#[test]
|
|
#[ignore] // expensive test
|
|
fn full_threshold_secret_sharing() {
|
|
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 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))
|
|
}
|
|
|
|
// TODO: HERE BE SERIALIZATION / DESERIALIZATION THAT'S NOT IMPLEMENTED YET
|
|
// verify remote proofs of key possession
|
|
for key in full_keys.iter() {
|
|
assert!(key.1.verify());
|
|
}
|
|
|
|
let dealings = node_indices
|
|
.iter()
|
|
.map(|&dealer_index| {
|
|
(
|
|
dealer_index,
|
|
Dealing::create(&mut rng, ¶ms, dealer_index, threshold, &receivers, None).0,
|
|
)
|
|
})
|
|
.collect::<BTreeMap<_, _>>();
|
|
for dealing in dealings.values() {
|
|
dealing
|
|
.verify(¶ms, threshold, &receivers, None)
|
|
.unwrap();
|
|
}
|
|
|
|
// recover verification keys
|
|
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, (ref dk, _)) in full_keys.iter().enumerate() {
|
|
let shares = dealings
|
|
.values()
|
|
.map(|dealing| decrypt_share(¶ms, dk, i, &dealing.ciphertexts, None).unwrap())
|
|
.collect();
|
|
|
|
// we know dealer_share matches, but it would be inconvenient to try to put them in here,
|
|
// so for ease of use (IN A TEST SETTING), just decrypt one's own share
|
|
let recovered_secret =
|
|
combine_shares(shares, &receivers.keys().copied().collect::<Vec<_>>()).unwrap();
|
|
|
|
// make sure it matches the associated vk
|
|
assert_eq!(recovered_partials[i], g2 * recovered_secret);
|
|
|
|
derived_secrets.push(recovered_secret)
|
|
}
|
|
|
|
// sanity check that the shares were combined correctly and if we take threshold number of them,
|
|
// we end up with the same master secret, note: those are NEVER explicitly recovered in actual system
|
|
// (remember threshold was 2)
|
|
let master1 = perform_lagrangian_interpolation_at_origin(&[
|
|
(Scalar::from(node_indices[0]), derived_secrets[0]),
|
|
(Scalar::from(node_indices[1]), derived_secrets[1]),
|
|
])
|
|
.unwrap();
|
|
|
|
let master2 = perform_lagrangian_interpolation_at_origin(&[
|
|
(Scalar::from(node_indices[1]), derived_secrets[1]),
|
|
(Scalar::from(node_indices[2]), derived_secrets[2]),
|
|
])
|
|
.unwrap();
|
|
|
|
assert_eq!(master1, master2);
|
|
assert_eq!(recovered_master, g2 * master1);
|
|
}
|
|
|
|
#[test]
|
|
#[ignore] // expensive test
|
|
fn full_threshold_secret_resharing() {
|
|
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 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| {
|
|
(
|
|
dealer_index,
|
|
Dealing::create(&mut rng, ¶ms, dealer_index, threshold, &receivers, None).0,
|
|
)
|
|
})
|
|
.collect::<BTreeMap<_, _>>();
|
|
|
|
// 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 dk, _)) in full_keys.iter().enumerate() {
|
|
let shares = first_dealings
|
|
.values()
|
|
.map(|dealing| decrypt_share(¶ms, dk, i, &dealing.ciphertexts, None).unwrap())
|
|
.collect();
|
|
|
|
let recovered_secret =
|
|
combine_shares(shares, &receivers.keys().copied().collect::<Vec<_>>()).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();
|
|
|
|
// attempt to create resharing dealings!
|
|
let resharing_dealings = node_indices
|
|
.iter()
|
|
.zip(derived_secrets.iter())
|
|
.map(|(&dealer_index, prior_secret)| {
|
|
(
|
|
dealer_index,
|
|
Dealing::create(
|
|
&mut rng,
|
|
¶ms,
|
|
dealer_index,
|
|
threshold,
|
|
&receivers,
|
|
Some(*prior_secret),
|
|
)
|
|
.0,
|
|
)
|
|
})
|
|
.collect::<BTreeMap<_, _>>();
|
|
|
|
for (reshared_dealing, prior_vk) in resharing_dealings.values().zip(recovered_partials.iter()) {
|
|
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 dk, _)) in full_keys.iter().enumerate() {
|
|
let shares = resharing_dealings
|
|
.values()
|
|
.map(|dealing| decrypt_share(¶ms, dk, i, &dealing.ciphertexts, None).unwrap())
|
|
.collect();
|
|
|
|
let recovered_secret =
|
|
combine_shares(shares, &receivers.keys().copied().collect::<Vec<_>>()).unwrap();
|
|
|
|
reshared_secrets.push(recovered_secret)
|
|
}
|
|
|
|
let reshared_master = perform_lagrangian_interpolation_at_origin(&[
|
|
(Scalar::from(node_indices[0]), reshared_secrets[0]),
|
|
(Scalar::from(node_indices[1]), reshared_secrets[1]),
|
|
])
|
|
.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);
|
|
}
|
|
|
|
#[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| {
|
|
(
|
|
dealer_index,
|
|
Dealing::create(&mut rng, ¶ms, dealer_index, threshold, &receivers, None).0,
|
|
)
|
|
})
|
|
.collect::<BTreeMap<_, _>>();
|
|
|
|
// 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 dk, _)) in full_keys.iter().enumerate() {
|
|
let shares = first_dealings
|
|
.values()
|
|
.map(|dealing| decrypt_share(¶ms, dk, i, &dealing.ciphertexts, None).unwrap())
|
|
.collect();
|
|
|
|
let recovered_secret =
|
|
combine_shares(shares, &receivers.keys().copied().collect::<Vec<_>>()).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)| {
|
|
(
|
|
dealer_index,
|
|
Dealing::create(
|
|
&mut rng,
|
|
¶ms,
|
|
dealer_index,
|
|
threshold,
|
|
&receivers,
|
|
Some(*prior_secret),
|
|
)
|
|
.0,
|
|
)
|
|
})
|
|
.collect::<BTreeMap<_, _>>();
|
|
|
|
for (reshared_dealing, prior_vk) in resharing_dealings
|
|
.values()
|
|
.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 dk, _)) in full_keys.iter().enumerate() {
|
|
let shares = resharing_dealings
|
|
.values()
|
|
.map(|dealing| decrypt_share(¶ms, 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);
|
|
}
|