Setup with 1 epoch and full test that skips key update (#1647)

* Setup with 1 epoch and full test that skips key update

* Remove a bunch of epoch code

* Remove unnecessary map from one element vector

* Remove tau, epoch and lambda_t

* Removed lambda_t completely

Co-authored-by: Jędrzej Stuczyński <jedrzej.stuczynski@gmail.com>
This commit is contained in:
Bogdan-Ștefan Neacşu
2022-11-21 16:34:50 +02:00
committed by GitHub
parent 9337821712
commit c4ee964557
6 changed files with 132 additions and 1426 deletions
+15 -52
View File
@@ -9,7 +9,7 @@ use dkg::bte::proof_discrete_log::ProofOfDiscreteLog;
use dkg::bte::proof_sharing::ProofOfSecretSharing;
use dkg::bte::{
decrypt_share, encrypt_shares, keygen, proof_chunking, proof_sharing, setup, DecryptionKey,
Epoch, PublicKey,
PublicKey,
};
use dkg::interpolation::polynomial::Polynomial;
use dkg::{Dealing, NodeIndex, Share};
@@ -54,7 +54,6 @@ pub fn creating_dealing_for_3_parties(c: &mut Criterion) {
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
let params = setup();
let threshold = 2;
let epoch = Epoch::new(2);
let (receivers, _) = prepare_keys(&mut rng, 3);
@@ -66,7 +65,6 @@ pub fn creating_dealing_for_3_parties(c: &mut Criterion) {
&params,
receivers.keys().next().copied().unwrap(),
threshold,
epoch,
&receivers,
None,
)
@@ -80,7 +78,6 @@ pub fn verifying_dealing_made_for_3_parties_and_recovering_share(c: &mut Criteri
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
let params = setup();
let threshold = 2;
let epoch = Epoch::new(2);
let (receivers, mut dks) = prepare_keys(&mut rng, 3);
let (dealing, _) = Dealing::create(
@@ -88,22 +85,18 @@ pub fn verifying_dealing_made_for_3_parties_and_recovering_share(c: &mut Criteri
&params,
receivers.keys().next().copied().unwrap(),
threshold,
epoch,
&receivers,
None,
);
let first_key = dks.get_mut(0).unwrap();
first_key.try_update_to(epoch, &params, &mut rng).unwrap();
c.bench_function(
"verifying single dealing made for 3 parties (threshold 2) and recovering share",
|b| {
b.iter(|| {
assert!(dealing
.verify(&params, epoch, threshold, &receivers, None)
.is_ok());
black_box(decrypt_share(first_key, 0, &dealing.ciphertexts, epoch, None).unwrap());
assert!(dealing.verify(&params, threshold, &receivers, None).is_ok());
black_box(decrypt_share(first_key, 0, &dealing.ciphertexts, None).unwrap());
})
},
);
@@ -114,7 +107,6 @@ pub fn creating_dealing_for_20_parties(c: &mut Criterion) {
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
let params = setup();
let threshold = 14;
let epoch = Epoch::new(2);
let (receivers, _) = prepare_keys(&mut rng, 20);
@@ -128,7 +120,6 @@ pub fn creating_dealing_for_20_parties(c: &mut Criterion) {
&params,
receivers.keys().next().copied().unwrap(),
threshold,
epoch,
&receivers,
None,
)
@@ -143,7 +134,6 @@ pub fn verifying_dealing_made_for_20_parties_and_recovering_share(c: &mut Criter
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
let params = setup();
let threshold = 14;
let epoch = Epoch::new(2);
let (receivers, mut dks) = prepare_keys(&mut rng, 20);
let (dealing, _) = Dealing::create(
@@ -151,22 +141,18 @@ pub fn verifying_dealing_made_for_20_parties_and_recovering_share(c: &mut Criter
&params,
receivers.keys().next().copied().unwrap(),
threshold,
epoch,
&receivers,
None,
);
let first_key = dks.get_mut(0).unwrap();
first_key.try_update_to(epoch, &params, &mut rng).unwrap();
c.bench_function(
"verifying single dealing made for 20 parties (threshold 14) and recovering share",
|b| {
b.iter(|| {
assert!(dealing
.verify(&params, epoch, threshold, &receivers, None)
.is_ok());
black_box(decrypt_share(first_key, 0, &dealing.ciphertexts, epoch, None).unwrap());
assert!(dealing.verify(&params, threshold, &receivers, None).is_ok());
black_box(decrypt_share(first_key, 0, &dealing.ciphertexts, None).unwrap());
})
},
);
@@ -177,7 +163,6 @@ pub fn creating_dealing_for_100_parties(c: &mut Criterion) {
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
let params = setup();
let threshold = 67;
let epoch = Epoch::new(2);
let (receivers, _) = prepare_keys(&mut rng, 100);
@@ -191,7 +176,6 @@ pub fn creating_dealing_for_100_parties(c: &mut Criterion) {
&params,
receivers.keys().next().copied().unwrap(),
threshold,
epoch,
&receivers,
None,
)
@@ -206,7 +190,6 @@ pub fn verifying_dealing_made_for_100_parties_and_recovering_share(c: &mut Crite
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
let params = setup();
let threshold = 67;
let epoch = Epoch::new(2);
let (receivers, mut dks) = prepare_keys(&mut rng, 100);
let (dealing, _) = Dealing::create(
@@ -214,22 +197,18 @@ pub fn verifying_dealing_made_for_100_parties_and_recovering_share(c: &mut Crite
&params,
receivers.keys().next().copied().unwrap(),
threshold,
epoch,
&receivers,
None,
);
let first_key = dks.get_mut(0).unwrap();
first_key.try_update_to(epoch, &params, &mut rng).unwrap();
c.bench_function(
"verifying single dealing made for 100 parties (threshold 67) and recovering share",
|b| {
b.iter(|| {
assert!(dealing
.verify(&params, epoch, threshold, &receivers, None)
.is_ok());
black_box(decrypt_share(first_key, 0, &dealing.ciphertexts, epoch, None).unwrap());
assert!(dealing.verify(&params, threshold, &receivers, None).is_ok());
black_box(decrypt_share(first_key, 0, &dealing.ciphertexts, None).unwrap());
})
},
);
@@ -266,7 +245,6 @@ pub fn creating_proof_of_chunking_for_100_parties(c: &mut Criterion) {
let dummy_seed = [42u8; 32];
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
let params = setup();
let epoch = Epoch::new(2);
let (receivers, _) = prepare_keys(&mut rng, 100);
@@ -283,7 +261,7 @@ pub fn creating_proof_of_chunking_for_100_parties(c: &mut Criterion) {
.collect::<Vec<_>>();
let ordered_public_keys = receivers.values().copied().collect::<Vec<_>>();
let (ciphertexts, hazmat) = encrypt_shares(&remote_share_key_pairs, epoch, &params, &mut rng);
let (ciphertexts, hazmat) = encrypt_shares(&remote_share_key_pairs, &params, &mut rng);
c.bench_function("creating proof of chunking for 100 parties", |b| {
b.iter(|| {
@@ -301,7 +279,6 @@ pub fn verifying_proof_of_chunking_for_100_parties(c: &mut Criterion) {
let dummy_seed = [42u8; 32];
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
let params = setup();
let epoch = Epoch::new(2);
let (receivers, _) = prepare_keys(&mut rng, 100);
@@ -318,7 +295,7 @@ pub fn verifying_proof_of_chunking_for_100_parties(c: &mut Criterion) {
.collect::<Vec<_>>();
let ordered_public_keys = receivers.values().copied().collect::<Vec<_>>();
let (ciphertexts, hazmat) = encrypt_shares(&remote_share_key_pairs, epoch, &params, &mut rng);
let (ciphertexts, hazmat) = encrypt_shares(&remote_share_key_pairs, &params, &mut rng);
let chunking_instance = proof_chunking::Instance::new(&ordered_public_keys, &ciphertexts);
let proof_of_chunking =
@@ -338,7 +315,6 @@ pub fn creating_proof_of_secret_sharing_for_100_parties(c: &mut Criterion) {
let dummy_seed = [42u8; 32];
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
let params = setup();
let epoch = Epoch::new(2);
let (receivers, _) = prepare_keys(&mut rng, 100);
@@ -354,7 +330,7 @@ pub fn creating_proof_of_secret_sharing_for_100_parties(c: &mut Criterion) {
.map(|(share, key)| (share, key))
.collect::<Vec<_>>();
let (ciphertexts, hazmat) = encrypt_shares(&remote_share_key_pairs, epoch, &params, &mut rng);
let (ciphertexts, hazmat) = encrypt_shares(&remote_share_key_pairs, &params, &mut rng);
let combined_ciphertexts = ciphertexts.combine_ciphertexts();
let combined_r = hazmat.combine_rs();
@@ -381,7 +357,6 @@ pub fn verifying_proof_of_secret_sharing_for_100_parties(c: &mut Criterion) {
let dummy_seed = [42u8; 32];
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
let params = setup();
let epoch = Epoch::new(2);
let (receivers, _) = prepare_keys(&mut rng, 100);
@@ -397,7 +372,7 @@ pub fn verifying_proof_of_secret_sharing_for_100_parties(c: &mut Criterion) {
.map(|(share, key)| (share, key))
.collect::<Vec<_>>();
let (ciphertexts, hazmat) = encrypt_shares(&remote_share_key_pairs, epoch, &params, &mut rng);
let (ciphertexts, hazmat) = encrypt_shares(&remote_share_key_pairs, &params, &mut rng);
let combined_ciphertexts = ciphertexts.combine_ciphertexts();
let combined_r = hazmat.combine_rs();
@@ -430,7 +405,6 @@ pub fn single_share_encryption(c: &mut Criterion) {
let dummy_seed = [42u8; 32];
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
let params = setup();
let epoch = Epoch::new(2);
let (_, pk) = keygen(&params, &mut rng);
let polynomial = Polynomial::new_random(&mut rng, 3);
@@ -440,7 +414,6 @@ pub fn single_share_encryption(c: &mut Criterion) {
b.iter(|| {
black_box(encrypt_shares(
&[(&share, pk.public_key())],
epoch,
&params,
&mut rng,
))
@@ -452,7 +425,6 @@ pub fn share_encryption_100(c: &mut Criterion) {
let dummy_seed = [42u8; 32];
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
let params = setup();
let epoch = Epoch::new(2);
let (receivers, _) = prepare_keys(&mut rng, 100);
let polynomial = Polynomial::new_random(&mut rng, 3);
@@ -468,14 +440,7 @@ pub fn share_encryption_100(c: &mut Criterion) {
.collect::<Vec<_>>();
c.bench_function("100 shares encryption", |b| {
b.iter(|| {
black_box(encrypt_shares(
&remote_share_key_pairs,
epoch,
&params,
&mut rng,
))
})
b.iter(|| black_box(encrypt_shares(&remote_share_key_pairs, &params, &mut rng)))
});
}
@@ -483,16 +448,14 @@ pub fn share_decryption(c: &mut Criterion) {
let dummy_seed = [42u8; 32];
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
let params = setup();
let epoch = Epoch::new(2);
let (mut dk, pk) = keygen(&params, &mut rng);
let (dk, pk) = keygen(&params, &mut rng);
let polynomial = Polynomial::new_random(&mut rng, 3);
let share: Share = polynomial.evaluate_at(&Scalar::from(42)).into();
let (ciphertexts, _) = encrypt_shares(&[(&share, pk.public_key())], epoch, &params, &mut rng);
dk.try_update_to(epoch, &params, &mut rng).unwrap();
let (ciphertexts, _) = encrypt_shares(&[(&share, pk.public_key())], &params, &mut rng);
c.bench_function("single share decryption", |b| {
b.iter(|| black_box(decrypt_share(&dk, 0, &ciphertexts, epoch, None)))
b.iter(|| black_box(decrypt_share(&dk, 0, &ciphertexts, None)))
});
}
+26 -179
View File
@@ -2,7 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
use crate::bte::keys::{DecryptionKey, PublicKey};
use crate::bte::{Epoch, Params, CHUNK_SIZE, G2_GENERATOR_PREPARED, NUM_CHUNKS, PAIRING_BASE};
use crate::bte::{evaluate_f, Params, CHUNK_SIZE, G2_GENERATOR_PREPARED, NUM_CHUNKS, PAIRING_BASE};
use crate::error::DkgError;
use crate::utils::{combine_g1_chunks, combine_scalar_chunks, deserialize_g1, deserialize_g2};
use crate::{Chunk, ChunkedShare, Share};
@@ -24,7 +24,7 @@ pub struct Ciphertexts {
}
impl Ciphertexts {
pub fn verify_integrity(&self, params: &Params, epoch: Epoch) -> bool {
pub fn verify_integrity(&self, params: &Params) -> bool {
// if this checks fails it means the ciphertext is undefined as values
// in `r`, `s` and `z` are meaningless since technically this ciphertext
// has been created for 0 parties
@@ -33,9 +33,7 @@ impl Ciphertexts {
}
let g1_neg = G1Affine::generator().neg();
let f = epoch
.as_extended_tau(&self.rr, &self.ss, &self.ciphertext_chunks)
.evaluate_f(params);
let f = evaluate_f(params);
// we have to use `f` in up to `NUM_CHUNKS` pairings (if everything is valid),
// so perform some precomputation on it
@@ -192,7 +190,6 @@ impl HazmatRandomness {
pub fn encrypt_shares(
shares: &[(&Share, &PublicKey)],
epoch: Epoch,
params: &Params,
mut rng: impl RngCore,
) -> (Ciphertexts, HazmatRandomness) {
@@ -242,7 +239,7 @@ pub fn encrypt_shares(
let rr = rr.try_into().unwrap();
let ss = ss.try_into().unwrap();
let f = epoch.as_extended_tau(&rr, &ss, &cc).evaluate_f(params);
let f = evaluate_f(params);
let mut zz = Vec::with_capacity(NUM_CHUNKS);
for i in 0..NUM_CHUNKS {
@@ -269,35 +266,22 @@ pub fn decrypt_share(
// in the case of multiple receivers, specifies which index of ciphertext chunks should be used
i: usize,
ciphertext: &Ciphertexts,
epoch: Epoch,
lookup_table: Option<&BabyStepGiantStepLookup>,
) -> Result<Share, DkgError> {
let mut plaintext = ChunkedShare::default();
let decryption_node = dk.try_get_compatible_node(epoch)?;
let extended_tau = epoch.as_extended_tau(
&ciphertext.rr,
&ciphertext.ss,
&ciphertext.ciphertext_chunks,
);
if i >= ciphertext.ciphertext_chunks.len() {
return Err(DkgError::UnavailableCiphertext(i));
}
let height = decryption_node.tau.height();
let b_neg = decryption_node
.ds
let b_neg = dk
.dh
.iter()
.chain(decryption_node.dh.iter())
.zip(extended_tau.0.iter().by_vals().skip(height))
.filter(|(_, i)| *i)
.map(|(d_i, _)| d_i)
.fold(decryption_node.b, |acc, d_i| acc + d_i)
.fold(dk.b, |acc, d_i| acc + d_i)
.neg()
.to_affine();
let e_neg = decryption_node.e.neg().to_affine();
let e_neg = dk.e.neg().to_affine();
for j in 0..NUM_CHUNKS {
let rr_j = &ciphertext.rr[j];
@@ -308,7 +292,7 @@ pub fn decrypt_share(
let miller = bls12_381::multi_miller_loop(&[
(&cc_ij.to_affine(), &G2_GENERATOR_PREPARED),
(&rr_j.to_affine(), &G2Prepared::from(b_neg)),
(&decryption_node.a.to_affine(), &G2Prepared::from(zz_j)),
(&dk.a.to_affine(), &G2Prepared::from(zz_j)),
(&ss_j.to_affine(), &G2Prepared::from(e_neg)),
]);
let m = miller.final_exponentiation();
@@ -466,7 +450,6 @@ mod tests {
let (decryption_key1, public_key1) = keygen(&params, &mut rng);
let (decryption_key2, public_key2) = keygen(&params, &mut rng);
let epoch = Epoch::new(0);
let lookup_table = &DEFAULT_BSGS_TABLE;
@@ -475,13 +458,13 @@ mod tests {
let m2 = Share::random(&mut rng);
let shares = &[(&m1, &public_key1.key), (&m2, &public_key2.key)];
let (ciphertext, hazmat) = encrypt_shares(shares, epoch, &params, &mut rng);
let (ciphertext, hazmat) = encrypt_shares(shares, &params, &mut rng);
verify_hazmat_rand(&ciphertext, &hazmat);
let recovered1 =
decrypt_share(&decryption_key1, 0, &ciphertext, epoch, Some(lookup_table)).unwrap();
decrypt_share(&decryption_key1, 0, &ciphertext, Some(lookup_table)).unwrap();
let recovered2 =
decrypt_share(&decryption_key2, 1, &ciphertext, epoch, Some(lookup_table)).unwrap();
decrypt_share(&decryption_key2, 1, &ciphertext, Some(lookup_table)).unwrap();
assert_eq!(m1, recovered1);
assert_eq!(m2, recovered2);
}
@@ -494,15 +477,8 @@ mod tests {
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
let params = setup();
let (mut decryption_key1, public_key1) = keygen(&params, &mut rng);
let (mut decryption_key2, public_key2) = keygen(&params, &mut rng);
let epoch = Epoch::new(12345);
decryption_key1
.try_update_to(epoch, &params, &mut rng)
.unwrap();
decryption_key2
.try_update_to(epoch, &params, &mut rng)
.unwrap();
let (decryption_key1, public_key1) = keygen(&params, &mut rng);
let (decryption_key2, public_key2) = keygen(&params, &mut rng);
let lookup_table = &DEFAULT_BSGS_TABLE;
@@ -511,121 +487,18 @@ mod tests {
let m2 = Share::random(&mut rng);
let shares = &[(&m1, &public_key1.key), (&m2, &public_key2.key)];
let (ciphertext, hazmat) = encrypt_shares(shares, epoch, &params, &mut rng);
let (ciphertext, hazmat) = encrypt_shares(shares, &params, &mut rng);
verify_hazmat_rand(&ciphertext, &hazmat);
let recovered1 =
decrypt_share(&decryption_key1, 0, &ciphertext, epoch, Some(lookup_table)).unwrap();
decrypt_share(&decryption_key1, 0, &ciphertext, Some(lookup_table)).unwrap();
let recovered2 =
decrypt_share(&decryption_key2, 1, &ciphertext, epoch, Some(lookup_table)).unwrap();
decrypt_share(&decryption_key2, 1, &ciphertext, Some(lookup_table)).unwrap();
assert_eq!(m1, recovered1);
assert_eq!(m2, recovered2);
}
}
#[test]
fn decryption_with_root_key() {
let dummy_seed = [42u8; 32];
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
let params = setup();
let (root_key, public_key) = keygen(&params, &mut rng);
let share = Share::random(&mut rng);
let epoch0 = Epoch::new(0);
let epoch42 = Epoch::new(42);
let epoch_big = Epoch::new(3292547435);
let (ciphertext1, hazmat1) =
encrypt_shares(&[(&share, &public_key.key)], epoch0, &params, &mut rng);
verify_hazmat_rand(&ciphertext1, &hazmat1);
let (ciphertext2, hazmat2) =
encrypt_shares(&[(&share, &public_key.key)], epoch42, &params, &mut rng);
verify_hazmat_rand(&ciphertext2, &hazmat2);
let (ciphertext3, hazmat3) =
encrypt_shares(&[(&share, &public_key.key)], epoch_big, &params, &mut rng);
verify_hazmat_rand(&ciphertext3, &hazmat3);
let recovered1 = decrypt_share(&root_key, 0, &ciphertext1, epoch0, None).unwrap();
let recovered2 = decrypt_share(&root_key, 0, &ciphertext2, epoch42, None).unwrap();
let recovered3 = decrypt_share(&root_key, 0, &ciphertext3, epoch_big, None).unwrap();
assert_eq!(share, recovered1);
assert_eq!(share, recovered2);
assert_eq!(share, recovered3);
}
#[test]
#[ignore] // expensive test
fn update_and_decrypt_10() {
let dummy_seed = [1u8; 32];
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
let params = setup();
let (mut decryption_key, public_key) = keygen(&params, &mut rng);
for epoch_value in 0..10 {
let epoch = Epoch::new(epoch_value);
let share = Share::random(&mut rng);
decryption_key
.try_update_to(epoch, &params, &mut rng)
.unwrap();
let (ciphertext, hazmat) =
encrypt_shares(&[(&share, &public_key.key)], epoch, &params, &mut rng);
verify_hazmat_rand(&ciphertext, &hazmat);
let recovered = decrypt_share(&decryption_key, 0, &ciphertext, epoch, None).unwrap();
assert_eq!(share, recovered);
}
}
#[test]
#[ignore] // expensive test
fn reblinding_node_doesnt_affect_decryption() {
let dummy_seed = [1u8; 32];
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
let params = setup();
let (mut decryption_key, public_key) = keygen(&params, &mut rng);
let epoch = Epoch::new(12345);
decryption_key
.try_update_to(epoch, &params, &mut rng)
.unwrap();
for node in decryption_key.nodes.iter_mut() {
node.reblind(&params, &mut rng);
}
let share = Share::random(&mut rng);
let (ciphertext, hazmat) =
encrypt_shares(&[(&share, &public_key.key)], epoch, &params, &mut rng);
verify_hazmat_rand(&ciphertext, &hazmat);
let recovered = decrypt_share(&decryption_key, 0, &ciphertext, epoch, None).unwrap();
assert_eq!(share, recovered);
// attempt to update the key again so we have to derive fresh nodes using previous reblinded results
let epoch2 = Epoch::new(67890);
decryption_key
.try_update_to(epoch2, &params, &mut rng)
.unwrap();
for node in decryption_key.nodes.iter_mut() {
node.reblind(&params, &mut rng);
}
let share2 = Share::random(&mut rng);
let (ciphertext, hazmat) =
encrypt_shares(&[(&share2, &public_key.key)], epoch2, &params, &mut rng);
verify_hazmat_rand(&ciphertext, &hazmat);
let recovered = decrypt_share(&decryption_key, 0, &ciphertext, epoch2, None).unwrap();
assert_eq!(share2, recovered);
}
#[test]
#[ignore] // expensive test
fn ciphertext_integrity_check_passes_for_valid_data() {
@@ -634,14 +507,11 @@ mod tests {
let dummy_seed = [1u8; 32];
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
let (mut dk, public_key) = keygen(&params, &mut rng);
let epoch = Epoch::new(1);
let (_, public_key) = keygen(&params, &mut rng);
dk.try_update_to(epoch, &params, &mut rng).unwrap();
let share = Share::random(&mut rng);
let (ciphertext, _) =
encrypt_shares(&[(&share, &public_key.key)], epoch, &params, &mut rng);
assert!(ciphertext.verify_integrity(&params, epoch))
let (ciphertext, _) = encrypt_shares(&[(&share, &public_key.key)], &params, &mut rng);
assert!(ciphertext.verify_integrity(&params))
}
#[test]
@@ -652,45 +522,22 @@ mod tests {
let dummy_seed = [1u8; 32];
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
let (mut dk, public_key) = keygen(&params, &mut rng);
let epoch = Epoch::new(1);
let (_, public_key) = keygen(&params, &mut rng);
dk.try_update_to(epoch, &params, &mut rng).unwrap();
let share = Share::random(&mut rng);
let (ciphertext, _) =
encrypt_shares(&[(&share, &public_key.key)], epoch, &params, &mut rng);
let (ciphertext, _) = encrypt_shares(&[(&share, &public_key.key)], &params, &mut rng);
let mut bad_cipher1 = ciphertext.clone();
bad_cipher1.rr[4] = G1Projective::generator();
assert!(!bad_cipher1.verify_integrity(&params, epoch));
assert!(!bad_cipher1.verify_integrity(&params));
let mut bad_cipher2 = ciphertext.clone();
bad_cipher2.ss[4] = G1Projective::generator();
assert!(!bad_cipher2.verify_integrity(&params, epoch));
assert!(!bad_cipher2.verify_integrity(&params));
let mut bad_cipher3 = ciphertext;
bad_cipher3.zz[4] = G2Projective::generator();
assert!(!bad_cipher3.verify_integrity(&params, epoch));
}
#[test]
#[ignore] // expensive test
fn ciphertext_integrity_check_passes_fails_for_wrong_epoch() {
let params = setup();
let dummy_seed = [1u8; 32];
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
let (mut dk, public_key) = keygen(&params, &mut rng);
let epoch = Epoch::new(1);
dk.try_update_to(epoch, &params, &mut rng).unwrap();
let share = Share::random(&mut rng);
let (ciphertext, _) =
encrypt_shares(&[(&share, &public_key.key)], epoch, &params, &mut rng);
let another_epoch = Epoch::new(2);
assert!(!ciphertext.verify_integrity(&params, another_epoch))
assert!(!bad_cipher3.verify_integrity(&params));
}
#[test]
@@ -711,7 +558,7 @@ mod tests {
}
let refs = shares.iter().zip(public_keys.iter()).collect::<Vec<_>>();
let (ciphertext, hazmat) = encrypt_shares(&refs, Epoch::new(42), &params, &mut rng);
let (ciphertext, hazmat) = encrypt_shares(&refs, &params, &mut rng);
let combined_r = combine_scalar_chunks(hazmat.r());
let combined_rr = ciphertext.combine_rs();
+68 -712
View File
@@ -2,7 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
use crate::bte::proof_discrete_log::ProofOfDiscreteLog;
use crate::bte::{Epoch, Params, Tau};
use crate::bte::Params;
use crate::error::DkgError;
use crate::utils::{deserialize_g1, deserialize_g2, deserialize_scalar};
use bls12_381::{G1Projective, G2Projective, Scalar};
@@ -11,297 +11,6 @@ use group::GroupEncoding;
use rand_core::RngCore;
use zeroize::Zeroize;
#[derive(Debug, Zeroize)]
#[zeroize(drop)]
#[cfg_attr(test, derive(Clone, PartialEq))]
pub(crate) struct Node {
pub(crate) tau: Tau,
// g1^rho
pub(crate) a: G1Projective,
// g2^x
pub(crate) b: G2Projective,
// f_i^rho, up to lambda_t elements
pub(crate) ds: Vec<G2Projective>,
// fh_i^rho, always lambda_h elements
pub(crate) dh: Vec<G2Projective>,
// h^rho
pub(crate) e: G2Projective,
}
impl Node {
fn new_root(
a: G1Projective,
b: G2Projective,
ds: Vec<G2Projective>,
dh: Vec<G2Projective>,
e: G2Projective,
) -> Self {
Node {
tau: Tau::new_root(),
a,
b,
ds,
dh,
e,
}
}
fn is_root(&self) -> bool {
self.tau.0.is_empty()
}
pub(crate) fn reblind(&mut self, params: &Params, mut rng: impl RngCore) {
let delta = Scalar::random(&mut rng);
self.a += G1Projective::generator() * delta;
// TODO: or do we have to do full tau evaluation here?
self.b += self.tau.evaluate_partial_f(params) * delta;
self.ds
.iter_mut()
.zip(params.fs.iter().skip(self.tau.height()))
.for_each(|(d_i, f_i)| *d_i += f_i * delta);
self.dh
.iter_mut()
.zip(params.fh.iter())
.for_each(|(d_i, f_i)| *d_i += f_i * delta);
self.e += params.h * delta;
}
// note: it's unsafe to use this method outside `try_update_to` as
// we have guaranteed there that `self` is parent of the target
// and that `self.tau != target_tau`
/// Given `self` with `Tau1` and `target_tau` with `Tau2`, such that `Tau1` prefixes `Tau2`,
/// i.e. `Tau2 == Tau1 || SUFFIX`, and `Tau2` is a leaf node, derive all required crypto material
/// for its construction.
fn derive_target_child_with_partials(
&self,
params: &Params,
target_tau: Tau,
partial_b: &G2Projective,
partial_f: &G2Projective,
mut rng: impl RngCore,
) -> Self {
debug_assert!(self.tau.is_parent_of(&target_tau));
debug_assert_ne!(self.tau, target_tau);
let delta = Scalar::random(&mut rng);
let a = self.a + G1Projective::generator() * delta;
let b = partial_b + partial_f * delta;
let ds = self
.ds
.iter()
.zip(params.fs.iter())
.skip(target_tau.height())
.map(|(d_i, f_i)| d_i + f_i * delta)
.collect();
let dh = self
.dh
.iter()
.zip(params.fh.iter())
.map(|(dh_i, fh_i)| dh_i + fh_i * delta)
.collect();
let e = self.e + params.h * delta;
Node {
tau: target_tau,
a,
b,
ds,
dh,
e,
}
}
// note: it's unsafe to use this method outside `try_update_to` as
// we have guaranteed there that `self` is parent of the target
// and that `self.tau != target_tau`
/// Given `self` with `Tau1` and `most_direct_parent` with `Tau2`, such that `Tau1` prefixes `Tau2`,
/// i.e. `Tau2 == Tau1 || SUFFIX`, derive node with `Tau3 = Tau2 || 1`
fn derive_right_nonfinal_child_of_with_partials(
&self,
params: &Params,
most_direct_parent: Tau,
partial_b: &G2Projective,
partial_f: &G2Projective,
mut rng: impl RngCore,
) -> Self {
let right_branch = most_direct_parent.right_child();
debug_assert!(self.tau.is_parent_of(&most_direct_parent));
debug_assert!(self.tau.is_parent_of(&right_branch));
debug_assert_ne!(self.tau, right_branch);
// n is height difference between self and the child
let n = right_branch.height() - self.tau.height();
// i is the index of the last bit we just added
let i = right_branch.height() - 1;
let delta = Scalar::random(&mut rng);
let a = self.a + G1Projective::generator() * delta;
let d0 = self.ds[n - 1];
let b = partial_b + d0 + (partial_f + params.fs[i]) * delta;
let ds = self
.ds
.iter()
.skip(n)
.zip(params.fs.iter().skip(right_branch.height()))
.map(|(d_i, f_i)| d_i + f_i * delta)
.collect();
let dh = self
.dh
.iter()
.zip(params.fh.iter())
.map(|(dh_i, fh_i)| dh_i + fh_i * delta)
.collect();
let e = self.e + params.h * delta;
Node {
tau: right_branch,
a,
b,
ds,
dh,
e,
}
}
// tau_bytes_len || tau || a || b || len_ds || ds || len_dh || dh || e
pub(crate) fn to_bytes(&self) -> Vec<u8> {
let g1_elements = 1;
let g2_elements = self.ds.len() + self.dh.len() + 2;
let tau_bytes = self.tau.to_bytes();
// the extra 12 comes from the triple u32 we use for encoding lengths of tau, ds and dh
let mut bytes =
Vec::with_capacity(tau_bytes.len() + g1_elements * 48 + g2_elements * 96 + 12);
bytes.extend_from_slice(&((tau_bytes.len() as u32).to_be_bytes()));
bytes.extend_from_slice(&tau_bytes);
bytes.extend_from_slice(self.a.to_bytes().as_ref());
bytes.extend_from_slice(self.b.to_bytes().as_ref());
bytes.extend_from_slice(&((self.ds.len() as u32).to_be_bytes()));
for d_i in &self.ds {
bytes.extend_from_slice(d_i.to_bytes().as_ref());
}
bytes.extend_from_slice(&((self.dh.len() as u32).to_be_bytes()));
for dh_i in &self.dh {
bytes.extend_from_slice(dh_i.to_bytes().as_ref());
}
bytes.extend_from_slice(self.e.to_bytes().as_ref());
bytes
}
pub fn try_from_bytes(bytes: &[u8]) -> Result<Self, DkgError> {
// at the very least we require bytes for:
// - tau_len ( 4 )
// - tau ( could be 0 for root node )
// - a ( 48 )
// - b ( 96 )
// - length indication of ds ( 4 )
// - length indication of dh ( 4 )
// - e ( 96 )
if bytes.len() < 4 + 48 + 96 + 4 + 4 + 96 {
return Err(DkgError::new_deserialization_failure(
"Node",
"insufficient number of bytes provided",
));
}
let tau_len = u32::from_be_bytes((&bytes[..4]).try_into().unwrap()) as usize;
let mut i = 4;
let tau = Tau::try_from_bytes(&bytes[i..i + tau_len])?;
i += tau_len;
// perform another length check to account for bytes consumed by tau
if bytes[i..].len() < 48 + 96 + 4 + 4 + 96 {
return Err(DkgError::new_deserialization_failure(
"Node",
"insufficient number of bytes provided",
));
}
let a = deserialize_g1(&bytes[i..i + 48]).ok_or_else(|| {
DkgError::new_deserialization_failure("Node.a", "invalid curve point")
})?;
i += 48;
let b = deserialize_g2(&bytes[i..i + 96]).ok_or_else(|| {
DkgError::new_deserialization_failure("Node.b", "invalid curve point")
})?;
i += 96;
let ds_len = u32::from_be_bytes((&bytes[i..i + 4]).try_into().unwrap()) as usize;
i += 4;
if bytes[i..].len() < ds_len * 96 + 4 {
return Err(DkgError::new_deserialization_failure(
"Node",
"insufficient number of bytes provided (ds)",
));
}
let mut ds = Vec::with_capacity(ds_len);
for j in 0..ds_len {
let d_i = deserialize_g2(&bytes[i..i + 96]).ok_or_else(|| {
DkgError::new_deserialization_failure(
format!("Node.ds_{}", j),
"invalid curve point",
)
})?;
ds.push(d_i);
i += 96;
}
let dh_len = u32::from_be_bytes((&bytes[i..i + 4]).try_into().unwrap()) as usize;
i += 4;
if bytes[i..].len() != (dh_len + 1) * 96 {
return Err(DkgError::new_deserialization_failure(
"Node",
"insufficient number of bytes provided (dh)",
));
}
let mut dh = Vec::with_capacity(dh_len);
for j in 0..dh_len {
let dh_i = deserialize_g2(&bytes[i..i + 96]).ok_or_else(|| {
DkgError::new_deserialization_failure(
format!("Node.dh_{}", j),
"invalid curve point",
)
})?;
dh.push(dh_i);
i += 96;
}
let e = deserialize_g2(&bytes[i..]).ok_or_else(|| {
DkgError::new_deserialization_failure("Node.h", "invalid curve point")
})?;
Ok(Node {
tau,
a,
b,
ds,
dh,
e,
})
}
}
// produces public key and a decryption key for the root of the tree
pub fn keygen(params: &Params, mut rng: impl RngCore) -> (DecryptionKey, PublicKeyWithProof) {
let g1 = G1Projective::generator();
@@ -317,11 +26,10 @@ pub fn keygen(params: &Params, mut rng: impl RngCore) -> (DecryptionKey, PublicK
let a = g1 * rho;
let b = g2 * x + params.f0 * rho;
let ds = params.fs.iter().map(|f_i| f_i * rho).collect();
let dh = params.fh.iter().map(|fh_i| fh_i * rho).collect();
let e = params.h * rho;
let dk = DecryptionKey::new_root(Node::new_root(a, b, ds, dh, e));
let dk = DecryptionKey::new_root(a, b, dh, e);
let public_key = PublicKey(y);
let key_with_proof = PublicKeyWithProof {
@@ -414,231 +122,94 @@ impl PublicKeyWithProof {
#[zeroize(drop)]
#[cfg_attr(test, derive(PartialEq))]
pub struct DecryptionKey {
// note that the nodes are ordered from "right" to "left"
pub(crate) nodes: Vec<Node>,
// g1^rho
pub(crate) a: G1Projective,
// g2^x * f0^rho
pub(crate) b: G2Projective,
// fh_i^rho, always lambda_h elements
pub(crate) dh: Vec<G2Projective>,
// h^rho
pub(crate) e: G2Projective,
}
impl DecryptionKey {
fn new_root(root_node: Node) -> Self {
DecryptionKey {
nodes: vec![root_node],
}
}
fn current(&self) -> Result<&Node, DkgError> {
// we must have at least a single node, otherwise we have a malformed key
self.nodes.last().ok_or(DkgError::MalformedDecryptionKey)
}
pub fn current_epoch(&self, params: &Params) -> Result<Option<Epoch>, DkgError> {
let current_node = self.current()?;
if current_node.is_root() {
Ok(None)
} else {
Epoch::try_from_tau(&current_node.tau, params).map(Option::Some)
}
}
pub(crate) fn try_get_compatible_node(&self, epoch: Epoch) -> Result<&Node, DkgError> {
let tau = epoch.as_tau();
self.nodes
.iter()
.rev()
.find(|node| node.tau.is_parent_of(&tau))
.ok_or(DkgError::ExpiredKey)
}
pub fn try_update_to_next_epoch(
&mut self,
params: &Params,
mut rng: impl RngCore,
) -> Result<(), DkgError> {
if self.nodes.is_empty() {
return Err(DkgError::MalformedDecryptionKey);
}
let mut target_epoch = Epoch::new(0);
if self.nodes.len() == 1 && self.nodes[0].is_root() {
return self.try_update_to(target_epoch, params, &mut rng);
}
// unwrap is fine as we have asserted self.nodes is not empty
self.nodes.pop().unwrap();
if let Some(tail) = self.nodes.last() {
target_epoch = tail.tau.lowest_valid_epoch_child(params)?;
} else {
// essentially our key consisted of only a single node and it wasn't a root,
// so either it was malformed or we somehow reached the final epoch and wanted to update
// beyond that. Either way, update to l + 1 is impossible
return Err(DkgError::MalformedDecryptionKey);
}
self.try_update_to(target_epoch, params, &mut rng)
}
/// Attempts to update `self` to the provided `epoch`. If the update is not possible,
/// because the target was in the past or the key is malformed, an error is returned.
///
/// Note that this method mutates the key in place and if the original key was malformed,
/// there are no guarantees about its internal state post-call.
pub fn try_update_to(
&mut self,
target_epoch: Epoch,
params: &Params,
mut rng: impl RngCore,
) -> Result<(), DkgError> {
if self.nodes.is_empty() {
// somehow we have an empty decryption key
return Err(DkgError::MalformedDecryptionKey);
}
// makes it easier to work with since we will be generating non-leaf nodes
let target_tau = target_epoch.as_tau();
let current_tau = &self.current()?.tau;
if current_tau == &target_tau {
// our key is already updated to the target
return Ok(());
}
if current_tau > &target_tau {
// we cannot derive keys for past epochs
return Err(DkgError::TargetEpochUpdateInThePast);
}
// drop the nodes that are no longer required and get the most direct parent for the target epoch available
let mut parent = loop {
// if pop() fails the key is malformed since we checked that the target_epoch > current_epoch,
// hence the update should have been possible
let tail = self.nodes.pop().ok_or(DkgError::MalformedDecryptionKey)?;
if tail.tau.is_parent_of(&target_tau) {
break tail;
}
};
// essentially the case of updating epoch n to n + 1, where n is even;
// in that case the last two nodes are [..., epoch_{n+1}, epoch_n]
// so we just have to reblind the n+1 node and we're done
if parent.tau == target_tau {
parent.reblind(params, &mut rng);
self.nodes.push(parent);
return Ok(());
}
// accumulators, note that the previous elements have already been included by the parent,
// i.e. for example for parent at height l <= n, b = g2^x * f0^rho * d1^{tau_1} * ... * dl^{tau_l}
// new_b_accumulator = b * d1^{tau_1} * d2^{tau_2} * ... * dn^{tau_n}
// new_f_accumulator = f0 * f1^{tau_1} * f2^{tau_2} * ... * fn^{tau_n} (up to lambda_t)
let mut new_b_accumulator = parent.b;
let mut new_f_accumulator = parent.tau.evaluate_partial_f(params);
let parent_height = parent.tau.height();
// path from the parent to the child
for (n, bit) in target_tau
.0
.iter()
.by_vals()
.skip(parent.tau.height())
.enumerate()
{
// ith bit of the [child] epoch
// note that n represents height difference between parent and the current bit
let i = n + parent_height;
// if the bit is NOT set, push the right '1' subtree (for future keys)
// so for example if given parent with some `PREFIX` tau and target_epoch being `PREFIX || 010`,
// in the first loop iteration we're going to look at bit `0` and
// derive child node `PREFIX || 1` so that in the future we could derive keys for all other epochs starting with `PREFIX || 1`
// in the next loop iteration we're going to look at bit `1` and simply update the accumulators,
// as we don't need to generate any "left" nodes as all of them would have constructed epochs that are already in the past
// finally, in the last iteration, we look at the bit `0` and derive node `PREFIX || 011`,
// i.e. the one that FOLLOWS the target node.
if !bit {
let direct_parent = target_tau.try_get_parent_at_height(i)?;
self.nodes
.push(parent.derive_right_nonfinal_child_of_with_partials(
params,
direct_parent,
&new_b_accumulator,
&new_f_accumulator,
&mut rng,
));
} else {
// only update the accumulators when the bit is set, as d^0 == identity, so there's
// no point in doing anything else;
// note that we don't have to generate any new nodes when going into the right branch
// of the tree as everything on the left would have been in the past, so we don't care about them
new_b_accumulator += parent.ds[n]; // add d0
new_f_accumulator += params.fs[i]; // f_i
}
}
self.nodes.push(parent.derive_target_child_with_partials(
params,
target_epoch.as_tau(),
&new_b_accumulator,
&new_f_accumulator,
&mut rng,
));
Ok(())
fn new_root(a: G1Projective, b: G2Projective, dh: Vec<G2Projective>, e: G2Projective) -> Self {
DecryptionKey { a, b, dh, e }
}
pub fn to_bytes(&self) -> Vec<u8> {
let num_nodes = self.nodes.len() as u32;
let g1_elements = 1;
let g2_elements = self.dh.len() + 2;
// unfortunately we're not going to know the expected capacity
let mut bytes = Vec::new();
bytes.extend_from_slice(&num_nodes.to_be_bytes());
// the extra 8 comes from the triple u32 we use for encoding lengths of ds and dh
let mut bytes = Vec::with_capacity(g1_elements * 48 + g2_elements * 96 + 8);
for node in &self.nodes {
let mut node_bytes = node.to_bytes();
bytes.extend_from_slice(&((node_bytes.len() as u32).to_be_bytes()));
bytes.append(&mut node_bytes)
bytes.extend_from_slice(self.a.to_bytes().as_ref());
bytes.extend_from_slice(self.b.to_bytes().as_ref());
bytes.extend_from_slice(&((self.dh.len() as u32).to_be_bytes()));
for dh_i in &self.dh {
bytes.extend_from_slice(dh_i.to_bytes().as_ref());
}
bytes.extend_from_slice(self.e.to_bytes().as_ref());
bytes
}
pub fn try_from_bytes(b: &[u8]) -> Result<Self, DkgError> {
// we have to be able to read the length of nodes
if b.len() < 4 {
pub fn try_from_bytes(bytes: &[u8]) -> Result<Self, DkgError> {
// at the very least we require bytes for:
// - a ( 48 )
// - b ( 96 )
// - length indication of dh ( 4 )
// - e ( 96 )
if bytes.len() < 48 + 96 + 4 + 96 {
return Err(DkgError::new_deserialization_failure(
"DecryptionKey",
"Node",
"insufficient number of bytes provided",
));
}
let nodes_len = u32::from_be_bytes([b[0], b[1], b[2], b[3]]) as usize;
let mut nodes = Vec::with_capacity(nodes_len);
let mut i = 4;
for _ in 0..nodes_len {
// check if we can actually read the length...
if b[i..].len() < 4 {
return Err(DkgError::new_deserialization_failure(
"DecryptionKey.Node",
"insufficient number of bytes provided for BTE Node recovery",
));
}
let mut i = 0;
let a = deserialize_g1(&bytes[i..i + 48]).ok_or_else(|| {
DkgError::new_deserialization_failure("Node.a", "invalid curve point")
})?;
i += 48;
let node_bytes = u32::from_be_bytes([b[i], b[i + 1], b[i + 2], b[i + 3]]) as usize;
if b[i + 4..].len() < node_bytes {
return Err(DkgError::new_deserialization_failure(
"DecryptionKey.Node",
"insufficient number of bytes provided for BTE Node recovery",
));
}
i += 4;
let b = deserialize_g2(&bytes[i..i + 96]).ok_or_else(|| {
DkgError::new_deserialization_failure("Node.b", "invalid curve point")
})?;
i += 96;
let node = Node::try_from_bytes(&b[i..i + node_bytes])?;
nodes.push(node);
i += node_bytes;
let dh_len = u32::from_be_bytes((&bytes[i..i + 4]).try_into().unwrap()) as usize;
i += 4;
if bytes[i..].len() != (dh_len + 1) * 96 {
return Err(DkgError::new_deserialization_failure(
"Node",
"insufficient number of bytes provided (dh)",
));
}
Ok(DecryptionKey { nodes })
let mut dh = Vec::with_capacity(dh_len);
for j in 0..dh_len {
let dh_i = deserialize_g2(&bytes[i..i + 96]).ok_or_else(|| {
DkgError::new_deserialization_failure(
format!("Node.dh_{}", j),
"invalid curve point",
)
})?;
dh.push(dh_i);
i += 96;
}
let e = deserialize_g2(&bytes[i..]).ok_or_else(|| {
DkgError::new_deserialization_failure("Node.h", "invalid curve point")
})?;
Ok(Self { a, b, dh, e })
}
}
@@ -646,163 +217,8 @@ impl DecryptionKey {
mod tests {
use super::*;
use crate::bte::setup;
use bitvec::bitvec;
use bitvec::order::Msb0;
use rand_core::SeedableRng;
#[test]
#[ignore] // expensive test
fn basic_coverage_nodes() {
// it's some basic test I've been performing when writing the update function, but figured
// might as well put it into a unit test. note that it doesn't check the entire structure,
// but just the few last nodes of low height
let params = setup();
let dummy_seed = [1u8; 32];
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
let (mut dk, _) = keygen(&params, &mut rng);
let root_node_copy = dk.nodes.clone();
// this is a root node
assert_eq!(dk.nodes.len(), 1);
assert!(dk.nodes[0].is_root());
// we have to have a node for right branch on each height (1, 01, 001, ... etc)
// plus an additional one for the two left-most leaves (epochs "0" and "1")
dk.try_update_to(Epoch::new(0), &params, &mut rng).unwrap();
assert_eq!(dk.nodes.len(), 33);
let expected_last = Tau::new(0);
// (and yes, I had to look up those names in a thesaurus)
let expected_penultimate = Tau::new(1);
// note that this value is 31bit long
let expected_antepenultimate = Tau(bitvec![u32, Msb0;
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 1
]);
let mut nodes_iter = dk.nodes.iter().rev();
assert_eq!(expected_last, nodes_iter.next().unwrap().tau);
assert_eq!(expected_penultimate, nodes_iter.next().unwrap().tau);
assert_eq!(expected_antepenultimate, nodes_iter.next().unwrap().tau);
let mut epoch_zero_nodes = dk.nodes.clone();
// nodes for epoch1 should be identical for those for epoch0 minus the 00..00 leaf
dk.try_update_to(Epoch::new(1), &params, &mut rng).unwrap();
assert_eq!(dk.nodes.len(), 32);
epoch_zero_nodes.pop().unwrap();
assert_eq!(
epoch_zero_nodes
.iter()
.map(|node| node.tau.clone())
.collect::<Vec<_>>(),
dk.nodes
.iter()
.map(|node| node.tau.clone())
.collect::<Vec<_>>()
);
dk.try_update_to(Epoch::new(2), &params, &mut rng).unwrap();
dk.try_update_to(Epoch::new(3), &params, &mut rng).unwrap();
dk.try_update_to(Epoch::new(4), &params, &mut rng).unwrap();
let expected_last = Tau::new(4);
let expected_penultimate = Tau::new(5);
let expected_antepenultimate = Tau(bitvec![u32, Msb0;
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1
]);
let expected_preantepenultimate = Tau(bitvec![u32, Msb0;
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1
]);
assert_eq!(dk.nodes.len(), 32);
let mut nodes_iter = dk.nodes.iter().rev();
assert_eq!(expected_last, nodes_iter.next().unwrap().tau);
assert_eq!(expected_penultimate, nodes_iter.next().unwrap().tau);
assert_eq!(expected_antepenultimate, nodes_iter.next().unwrap().tau);
assert_eq!(expected_preantepenultimate, nodes_iter.next().unwrap().tau);
// the result should be the same of regardless if we update incrementally or go to the target immediately
let mut new_root = DecryptionKey {
nodes: root_node_copy,
};
new_root
.try_update_to(Epoch::new(4), &params, &mut rng)
.unwrap();
assert_eq!(
dk.nodes
.iter()
.map(|node| node.tau.clone())
.collect::<Vec<_>>(),
new_root
.nodes
.iter()
.map(|node| node.tau.clone())
.collect::<Vec<_>>()
);
// getting expected nodes for those epochs is non-trivial for test purposes, but the last node
// should ALWAYS be equal to the target epoch
dk.try_update_to(Epoch::new(42), &params, &mut rng).unwrap();
assert_eq!(dk.nodes.last().unwrap().tau, Tau::new(42));
dk.try_update_to(Epoch::new(123456), &params, &mut rng)
.unwrap();
assert_eq!(dk.nodes.last().unwrap().tau, Tau::new(123456));
dk.try_update_to(Epoch::new(3292547435), &params, &mut rng)
.unwrap();
assert_eq!(dk.nodes.last().unwrap().tau, Tau::new(3292547435));
// trying to go to past epochs fails
assert!(dk
.try_update_to(Epoch::new(531), &params, &mut rng)
.is_err())
}
#[test]
#[ignore] // expensive test
fn updating_to_next_epoch() {
let params = setup();
let dummy_seed = [1u8; 32];
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
let (mut dk, _) = keygen(&params, &mut rng);
// for root node current epoch is `None`
assert_eq!(None, dk.current_epoch(&params).unwrap());
// for root node it should result in epoch 0
dk.try_update_to_next_epoch(&params, &mut rng).unwrap();
assert_eq!(Some(Epoch::new(0)), dk.current_epoch(&params).unwrap());
dk.try_update_to_next_epoch(&params, &mut rng).unwrap();
assert_eq!(Some(Epoch::new(1)), dk.current_epoch(&params).unwrap());
dk.try_update_to_next_epoch(&params, &mut rng).unwrap();
assert_eq!(Some(Epoch::new(2)), dk.current_epoch(&params).unwrap());
// if we start from some non-root epoch, it should result in l + 1
dk.try_update_to(Epoch::new(42), &params, &mut rng).unwrap();
dk.try_update_to_next_epoch(&params, &mut rng).unwrap();
assert_eq!(Some(Epoch::new(43)), dk.current_epoch(&params).unwrap());
dk.try_update_to(Epoch::new(12345), &params, &mut rng)
.unwrap();
dk.try_update_to_next_epoch(&params, &mut rng).unwrap();
assert_eq!(Some(Epoch::new(12346)), dk.current_epoch(&params).unwrap());
dk.try_update_to(Epoch::new(3292547435), &params, &mut rng)
.unwrap();
dk.try_update_to_next_epoch(&params, &mut rng).unwrap();
assert_eq!(
Some(Epoch::new(3292547436)),
dk.current_epoch(&params).unwrap()
);
}
#[test]
fn public_key_with_proof_roundtrip() {
let params = setup();
@@ -816,64 +232,4 @@ mod tests {
assert_eq!(pk, recovered)
}
#[test]
#[ignore] // expensive test
fn bte_node_roundtrip() {
let params = setup();
let dummy_seed = [1u8; 32];
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
let (mut dk, _) = keygen(&params, &mut rng);
let root_node = dk.nodes[0].clone();
let bytes = root_node.to_bytes();
let recovered = Node::try_from_bytes(&bytes).unwrap();
assert_eq!(root_node, recovered);
dk.try_update_to(Epoch::new(3292547435), &params, &mut rng)
.unwrap();
for node in &dk.nodes {
let bytes = node.to_bytes();
let recovered = Node::try_from_bytes(&bytes).unwrap();
assert_eq!(node, &recovered);
}
}
#[test]
#[ignore] // expensive test
fn decryption_key_node_roundtrip() {
let params = setup();
let dummy_seed = [1u8; 32];
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
let (mut dk, _) = keygen(&params, &mut rng);
let bytes = dk.to_bytes();
let recovered = DecryptionKey::try_from_bytes(&bytes).unwrap();
assert_eq!(dk, recovered);
dk.try_update_to(Epoch::new(0), &params, &mut rng).unwrap();
let bytes = dk.to_bytes();
let recovered = DecryptionKey::try_from_bytes(&bytes).unwrap();
assert_eq!(dk, recovered);
dk.try_update_to(Epoch::new(1), &params, &mut rng).unwrap();
let bytes = dk.to_bytes();
let recovered = DecryptionKey::try_from_bytes(&bytes).unwrap();
assert_eq!(dk, recovered);
dk.try_update_to(Epoch::new(42), &params, &mut rng).unwrap();
let bytes = dk.to_bytes();
let recovered = DecryptionKey::try_from_bytes(&bytes).unwrap();
assert_eq!(dk, recovered);
dk.try_update_to(Epoch::new(3292547435), &params, &mut rng)
.unwrap();
let bytes = dk.to_bytes();
let recovered = DecryptionKey::try_from_bytes(&bytes).unwrap();
assert_eq!(dk, recovered);
}
}
+6 -394
View File
@@ -1,17 +1,11 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::error::DkgError;
use crate::utils::{hash_g2, RandomOracleBuilder};
use crate::utils::hash_g2;
use crate::{Chunk, Share};
use bitvec::field::BitField;
use bitvec::order::Msb0;
use bitvec::vec::BitVec;
use bitvec::view::BitView;
use bls12_381::{G1Affine, G1Projective, G2Affine, G2Prepared, G2Projective, Gt};
use bls12_381::{G1Affine, G2Affine, G2Prepared, G2Projective, Gt};
use group::Curve;
use lazy_static::lazy_static;
use zeroize::Zeroize;
pub mod encryption;
pub mod keys;
@@ -35,10 +29,6 @@ lazy_static! {
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-3.1
const SETUP_DOMAIN: &[u8] = b"NYM_COCONUT_NIDKG_V01_CS01_WITH_BLS12381G2_XMD:SHA-256_SSWU_RO_SETUP";
// this particular domain is not for curve hashing, but might as well also follow the same naming pattern
const TREE_TAU_EXTENSION_DOMAIN: &[u8] = b"NYM_COCONUT_NIDKG_V01_CS01_SHA-256_TREE_EXTENSION";
const MAX_EPOCHS_EXP: usize = 32;
const HASH_SECURITY_PARAM: usize = 256;
// note: CHUNK_BYTES * NUM_CHUNKS must equal to SCALAR_SIZE
@@ -49,253 +39,17 @@ pub const SCALAR_SIZE: usize = 32;
/// In paper B; number of distinct chunks
pub const CHUNK_SIZE: usize = 1 << (CHUNK_BYTES << 3);
pub(crate) type EpochStore = u32;
#[derive(Clone, Debug, PartialEq, PartialOrd)]
// None empty bitvec implies this is a root node
pub(crate) struct Tau(BitVec<EpochStore, Msb0>);
impl Tau {
pub fn new_root() -> Self {
Tau(BitVec::new())
}
// TODO: perhaps this should be explicitly moved to some test module
#[cfg(test)]
pub(crate) fn new(epoch: EpochStore) -> Self {
Tau(epoch.view_bits().to_bitvec())
}
#[allow(unused)]
pub fn left_child(&self) -> Self {
let mut child = self.0.clone();
child.push(false);
Tau(child)
}
pub fn right_child(&self) -> Self {
let mut child = self.0.clone();
child.push(true);
Tau(child)
}
pub fn is_leaf(&self, params: &Params) -> bool {
self.height() == params.lambda_t
}
pub fn try_get_parent_at_height(&self, height: usize) -> Result<Self, DkgError> {
if height > self.0.len() {
return Err(DkgError::NotAValidParent);
}
Ok(Tau(self.0[..height].to_bitvec()))
}
// essentially is this tau prefixing the other
pub fn is_parent_of(&self, other: &Tau) -> bool {
if self.0.len() > other.0.len() {
return false;
}
for (i, b) in self.0.iter().enumerate() {
if b != other.0[i] {
return false;
}
}
true
}
pub fn lowest_valid_epoch_child(&self, params: &Params) -> Result<Epoch, DkgError> {
if self.0.len() > params.lambda_t {
// this node is already BELOW a valid leaf-epoch node. it can only happen
// if either some invariant was broken or additional data was pushed to `tau`
// in order compute some intermediate results, but in that case this method should have
// never been called anyway. tl;dr: if this is called, the underlying key is malformed
return Err(DkgError::NotAValidParent);
}
let mut child = self.0.clone();
for _ in 0..(params.lambda_t - self.0.len()) {
child.push(false)
}
// the unwrap here is fine as we ensure we have exactly `params.tree_height` bits here
// (we could just propagate the error instead of unwraping and putting it behind an `Ok` anyway
// but I'd prefer to just blow up since this would be a serious error
Ok(Epoch::try_from_tau(&Tau(child), params).unwrap())
}
pub fn height(&self) -> usize {
self.0.len()
}
fn extend(
&self,
rr: &[G1Projective; NUM_CHUNKS],
ss: &[G1Projective; NUM_CHUNKS],
cc: &[[G1Projective; NUM_CHUNKS]],
) -> Self {
let mut random_oracle_builder = RandomOracleBuilder::new(TREE_TAU_EXTENSION_DOMAIN);
random_oracle_builder.update_with_g1_elements(rr.iter());
random_oracle_builder.update_with_g1_elements(ss.iter());
for ciphertext_chunks in cc {
random_oracle_builder.update_with_g1_elements(ciphertext_chunks.iter());
}
let tau_mem = self.0.as_raw_slice();
assert_eq!(tau_mem.len(), 1, "tau length invariant was broken");
random_oracle_builder.update(tau_mem[0].to_be_bytes());
let oracle_output = random_oracle_builder.finalize();
debug_assert_eq!(oracle_output.len() * 8, HASH_SECURITY_PARAM);
let mut extended_tau = self.clone();
for byte in oracle_output {
extended_tau
.0
.extend_from_bitslice(byte.view_bits::<Msb0>())
}
extended_tau
}
// considers all lambda_t + lambda_h bits
fn evaluate_f(&self, params: &Params) -> G2Projective {
self.0
.iter()
.by_vals()
.zip(params.fs.iter().chain(params.fh.iter()))
.filter(|(i, _)| *i)
.map(|(_, f_i)| f_i)
.fold(params.f0, |acc, f_i| acc + f_i)
}
// only considers up to lambda_t bits
fn evaluate_partial_f(&self, params: &Params) -> G2Projective {
self.0
.iter()
.by_vals()
.zip(params.fs.iter())
.filter(|(i, _)| *i)
.map(|(_, f_i)| f_i)
.fold(params.f0, |acc, f_i| acc + f_i)
}
pub(crate) fn to_bytes(&self) -> Vec<u8> {
let len_bytes = (self.0.len() as u32).to_be_bytes();
len_bytes
.into_iter()
.chain(self.0.chunks(8).map(BitField::load_be))
.collect()
}
pub(crate) fn try_from_bytes(b: &[u8]) -> Result<Self, DkgError> {
if b.len() < 4 {
return Err(DkgError::new_deserialization_failure(
"Tau",
"insufficient number of bytes provided",
));
}
let tau_len = u32::from_be_bytes([b[0], b[1], b[2], b[3]]) as usize;
// maximum theoretical length
if tau_len > MAX_EPOCHS_EXP + HASH_SECURITY_PARAM {
return Err(DkgError::new_deserialization_failure(
"Tau",
format!(
"malformed length {} is greater than maximum {}",
tau_len,
MAX_EPOCHS_EXP + HASH_SECURITY_PARAM
),
));
}
if tau_len == 0 {
if b.len() != 4 {
Err(DkgError::new_deserialization_failure(
"Tau",
"malformed bytes",
))
} else {
Ok(Tau::new_root())
}
} else if b.len() == 4 {
Err(DkgError::new_deserialization_failure(
"Tau",
"insufficient number of bytes provided",
))
} else {
let mut inner = BitVec::repeat(false, tau_len);
for (slot, &byte) in inner.chunks_mut(8).zip(b[4..].iter()) {
slot.store_be(byte);
}
Ok(Tau(inner))
}
}
}
impl Zeroize for Tau {
fn zeroize(&mut self) {
for v in self.0.as_raw_mut_slice() {
v.zeroize()
}
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd)]
pub struct Epoch(EpochStore);
impl Epoch {
pub fn new(value: EpochStore) -> Self {
Epoch(value)
}
pub(crate) fn as_tau(&self) -> Tau {
(*self).into()
}
pub(crate) fn as_extended_tau(
&self,
rr: &[G1Projective; NUM_CHUNKS],
ss: &[G1Projective; NUM_CHUNKS],
cc: &[[G1Projective; NUM_CHUNKS]],
) -> Tau {
self.as_tau().extend(rr, ss, cc)
}
pub(crate) fn try_from_tau(tau: &Tau, params: &Params) -> Result<Self, DkgError> {
if !tau.is_leaf(params) {
Err(DkgError::MalformedEpoch)
} else {
Ok(Epoch(tau.0.load_be()))
}
}
}
impl From<Epoch> for Tau {
fn from(epoch: Epoch) -> Self {
Tau(epoch.0.view_bits().to_bitvec())
}
}
impl From<EpochStore> for Epoch {
fn from(epoch: EpochStore) -> Self {
Epoch(epoch)
}
// considers all lambda_h bits
pub fn evaluate_f(params: &Params) -> G2Projective {
params.fh.iter().fold(params.f0, |acc, f_i| acc + f_i)
}
pub struct Params {
/// Maximum size of an epoch, in bits.
pub lambda_t: usize,
/// Security parameter of our $H_{\Lamda_H}$ hash function
pub lambda_h: usize,
// keeping f0 separate from the rest of the curve points makes it easier to work with tau
f0: G2Projective,
fs: Vec<G2Projective>, // f_1, f_2, .... f_{lambda_t} in the paper
fh: Vec<G2Projective>, // f_{lambda_t+1}, f_{lambda_t+1}, .... f_{lambda_t+lambda_h} in the paper
fh: Vec<G2Projective>, // f_{lambda_h}, f_{lambda_h+1}, .... f_{lambda_h} in the paper
h: G2Projective,
/// Precomputed `h` used for the miller loop
@@ -305,10 +59,6 @@ pub struct Params {
pub fn setup() -> Params {
let f0 = hash_g2(b"f0", SETUP_DOMAIN);
let fs = (1..=MAX_EPOCHS_EXP)
.map(|i| hash_g2(format!("f{}", i), SETUP_DOMAIN))
.collect();
let fh = (0..HASH_SECURITY_PARAM)
.map(|i| hash_g2(format!("fh{}", i), SETUP_DOMAIN))
.collect();
@@ -316,148 +66,10 @@ pub fn setup() -> Params {
let h = hash_g2(b"h", SETUP_DOMAIN);
Params {
lambda_t: MAX_EPOCHS_EXP,
lambda_h: HASH_SECURITY_PARAM,
f0,
fs,
fh,
h,
_h_prepared: G2Prepared::from(h.to_affine()),
}
}
#[cfg(test)]
mod tests {
use super::*;
use bitvec::bitvec;
use bitvec::order::Msb0;
#[test]
fn creating_tau_from_epoch() {
assert!(Tau::new_root().0.is_empty());
let zero = Tau::new(0);
assert!(zero.0.iter().by_vals().all(|b| !b));
let one = Tau::new(1);
let mut iter = one.0.iter().by_vals();
// first 31 bits are 0, the last one is 1
for _ in 0..31 {
assert!(!iter.next().unwrap())
}
assert!(iter.next().unwrap());
// 101010 in binary
let forty_two = Tau::new(42);
// first 26 bits are not set
let mut iter = forty_two.0.iter().by_vals();
for _ in 0..26 {
assert!(!iter.next().unwrap())
}
assert!(iter.next().unwrap());
assert!(!iter.next().unwrap());
assert!(iter.next().unwrap());
assert!(!iter.next().unwrap());
assert!(iter.next().unwrap());
assert!(!iter.next().unwrap());
// value that requires an actual u32 (i.e. takes 4 bytes to represent)
// 11000100_01000000_01001001_01101011 in binary
let big_val = Tau::new(3292547435);
let expected = bitvec![u32, Msb0;
1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1,
0, 1, 1
];
assert_eq!(expected, big_val.0)
}
#[test]
fn getting_parent_at_height() {
let tau = Tau(bitvec![u32, Msb0; 1,0,1,1,0,0,1]);
let expected_0 = Tau(BitVec::new());
let expected_1 = Tau(bitvec![u32, Msb0; 1]);
let expected_5 = Tau(bitvec![u32, Msb0; 1,0,1,1,0]);
assert_eq!(expected_0, tau.try_get_parent_at_height(0).unwrap());
assert_eq!(expected_1, tau.try_get_parent_at_height(1).unwrap());
assert_eq!(expected_5, tau.try_get_parent_at_height(5).unwrap());
assert_eq!(tau, tau.try_get_parent_at_height(7).unwrap());
assert!(tau.try_get_parent_at_height(8).is_err())
}
#[test]
fn converting_tau_to_epoch() {
let params = setup();
let tau0: Tau = Epoch::new(0).into();
let tau1: Tau = Epoch::new(1).into();
let tau42: Tau = Epoch::new(42).into();
let tau_big: Tau = Epoch::new(3292547435).into();
assert_eq!(Epoch::new(0), Epoch::try_from_tau(&tau0, &params).unwrap());
assert_eq!(Epoch::new(1), Epoch::try_from_tau(&tau1, &params).unwrap());
assert_eq!(
Epoch::new(42),
Epoch::try_from_tau(&tau42, &params).unwrap()
);
assert_eq!(
Epoch::new(3292547435),
Epoch::try_from_tau(&tau_big, &params).unwrap()
);
assert!(Epoch::try_from_tau(&Tau(BitVec::new()), &params).is_err());
assert!(Epoch::try_from_tau(&Tau(bitvec![u32, Msb0; 1,0,1,1,0]), &params).is_err());
let _31bit_tau = Tau(bitvec![u32, Msb0;
1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1,
0, 1
]);
assert!(Epoch::try_from_tau(&_31bit_tau, &params).is_err());
let _33bit_tau = Tau(bitvec![u32, Msb0;
1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1,
0, 1, 1, 0
]);
assert!(Epoch::try_from_tau(&_33bit_tau, &params).is_err());
}
#[test]
fn tau_roundtrip() {
let good_taus = vec![
Tau::new_root(),
Tau::new(0),
Tau::new(1),
Tau::new(2),
Tau::new(42),
Tau::new(123456),
Tau::new(3292547435),
Tau::new(u32::MAX),
];
for tau in good_taus {
let bytes = tau.to_bytes();
let recovered = Tau::try_from_bytes(&bytes).unwrap();
assert_eq!(tau, recovered);
}
// more valid variants
let mut another_tau = Tau::new(u32::MAX);
another_tau.0.push(true);
another_tau.0.push(false);
another_tau.0.push(true);
let bytes = another_tau.to_bytes();
let recovered = Tau::try_from_bytes(&bytes).unwrap();
assert_eq!(another_tau, recovered);
// ensure there are no panics
let big_length_bytes = [255, 255, 255, 255, 42];
assert!(Tau::try_from_bytes(&big_length_bytes).is_err());
assert!(Tau::try_from_bytes(&[]).is_err());
assert!(Tau::try_from_bytes(&[1, 1, 1, 1]).is_err());
assert!(Tau::try_from_bytes(&[0, 0, 0, 1]).is_err());
assert!(Tau::try_from_bytes(&[1, 0, 0, 0]).is_err());
assert!(Tau::try_from_bytes(&[1, 0, 0]).is_err());
}
}
+6 -39
View File
@@ -3,9 +3,7 @@
use crate::bte::proof_chunking::ProofOfChunking;
use crate::bte::proof_sharing::ProofOfSecretSharing;
use crate::bte::{
encrypt_shares, proof_chunking, proof_sharing, Ciphertexts, Epoch, Params, PublicKey,
};
use crate::bte::{encrypt_shares, proof_chunking, proof_sharing, Ciphertexts, Params, PublicKey};
use crate::error::DkgError;
use crate::interpolation::polynomial::{Polynomial, PublicCoefficients};
use crate::interpolation::{
@@ -34,7 +32,6 @@ impl Dealing {
params: &Params,
dealer_index: NodeIndex,
threshold: Threshold,
epoch: Epoch,
// BTreeMap ensures the keys are sorted by their indices
receivers: &BTreeMap<NodeIndex, PublicKey>,
prior_resharing_secret: Option<Scalar>,
@@ -58,8 +55,7 @@ impl Dealing {
.collect::<Vec<_>>();
let ordered_public_keys = receivers.values().copied().collect::<Vec<_>>();
let (ciphertexts, hazmat) =
encrypt_shares(&remote_share_key_pairs, epoch, params, &mut rng);
let (ciphertexts, hazmat) = encrypt_shares(&remote_share_key_pairs, params, &mut rng);
// create proofs of knowledge
let chunking_instance = proof_chunking::Instance::new(&ordered_public_keys, &ciphertexts);
@@ -108,7 +104,6 @@ impl Dealing {
pub fn verify(
&self,
params: &Params,
epoch: Epoch,
threshold: Threshold,
receivers: &BTreeMap<NodeIndex, PublicKey>,
prior_resharing_public: Option<G2Projective>,
@@ -134,7 +129,7 @@ impl Dealing {
});
}
if !self.ciphertexts.verify_integrity(params, epoch) {
if !self.ciphertexts.verify_integrity(params) {
return Err(DkgError::FailedCiphertextIntegrityCheck);
}
@@ -369,32 +364,18 @@ mod tests {
full_keys.push((dk, pk))
}
// start off in a defined epoch (i.e. not root);
let epoch = Epoch::new(2);
let dealings = node_indices
.iter()
.map(|&dealer_index| {
Dealing::create(
&mut rng,
&params,
dealer_index,
threshold,
epoch,
&receivers,
None,
)
.0
Dealing::create(&mut rng, &params, dealer_index, threshold, &receivers, None).0
})
.collect::<Vec<_>>();
let mut derived_secrets = Vec::new();
for (i, (ref mut dk, _)) in full_keys.iter_mut().enumerate() {
dk.try_update_to(epoch, &params, &mut rng).unwrap();
let shares = dealings
.iter()
.map(|dealing| decrypt_share(dk, i, &dealing.ciphertexts, epoch, None).unwrap())
.map(|dealing| decrypt_share(dk, i, &dealing.ciphertexts, None).unwrap())
.collect();
derived_secrets.push(
combine_shares(shares, &receivers.keys().copied().collect::<Vec<_>>()).unwrap(),
@@ -437,22 +418,10 @@ mod tests {
full_keys.push((dk, pk))
}
// start off in a defined epoch (i.e. not root);
let epoch = Epoch::new(2);
let dealings = node_indices
.iter()
.map(|&dealer_index| {
Dealing::create(
&mut rng,
&params,
dealer_index,
threshold,
epoch,
&receivers,
None,
)
.0
Dealing::create(&mut rng, &params, dealer_index, threshold, &receivers, None).0
})
.collect::<Vec<_>>();
@@ -478,7 +447,6 @@ mod tests {
let parties = 5;
let threshold = ((parties as f32 * 2.) / 3. + 1.) as Threshold;
let node_indices = (1..=parties).collect::<Vec<_>>();
let epoch = Epoch::new(2);
let mut receivers = BTreeMap::new();
for index in &node_indices {
@@ -491,7 +459,6 @@ mod tests {
&params,
node_indices[0],
threshold,
epoch,
&receivers,
None,
);
+11 -50
View File
@@ -2,7 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
use bls12_381::{G2Projective, Scalar};
use dkg::bte::{decrypt_share, keygen, setup, Epoch};
use dkg::bte::{decrypt_share, keygen, setup};
use dkg::interpolation::perform_lagrangian_interpolation_at_origin;
use dkg::{combine_shares, try_recover_verification_keys, Dealing};
use rand_core::SeedableRng;
@@ -32,9 +32,6 @@ fn single_sender() {
full_keys.push((dk, pk))
}
// start off in a defined epoch (i.e. not root);
let epoch = Epoch::new(2);
// TODO: HERE BE SERIALIZATION / DESERIALIZATION THAT'S NOT IMPLEMENTED YET
// verify remote proofs of key possession
for key in full_keys.iter() {
@@ -46,23 +43,20 @@ fn single_sender() {
&params,
node_indices[0],
threshold,
epoch,
&receivers,
None,
);
dealing
.verify(&params, epoch, threshold, &receivers, None)
.verify(&params, 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 mut dk, _)) in full_keys.iter_mut().enumerate() {
dk.try_update_to(epoch, &params, &mut rng).unwrap();
let _recovered = decrypt_share(dk, i, &dealing.ciphertexts, epoch, None).unwrap();
let _recovered = decrypt_share(dk, i, &dealing.ciphertexts, None).unwrap();
}
// and for good measure, check that the dealer's share matches decryption result
let recovered_dealer =
decrypt_share(&full_keys[0].0, 0, &dealing.ciphertexts, epoch, None).unwrap();
let recovered_dealer = decrypt_share(&full_keys[0].0, 0, &dealing.ciphertexts, None).unwrap();
assert_eq!(recovered_dealer, dealer_share.unwrap());
}
@@ -87,9 +81,6 @@ fn full_threshold_secret_sharing() {
full_keys.push((dk, pk))
}
// start off in a defined epoch (i.e. not root);
let epoch = Epoch::new(2);
// TODO: HERE BE SERIALIZATION / DESERIALIZATION THAT'S NOT IMPLEMENTED YET
// verify remote proofs of key possession
for key in full_keys.iter() {
@@ -99,21 +90,12 @@ fn full_threshold_secret_sharing() {
let dealings = node_indices
.iter()
.map(|&dealer_index| {
Dealing::create(
&mut rng,
&params,
dealer_index,
threshold,
epoch,
&receivers,
None,
)
.0
Dealing::create(&mut rng, &params, dealer_index, threshold, &receivers, None).0
})
.collect::<Vec<_>>();
for dealing in dealings.iter() {
dealing
.verify(&params, epoch, threshold, &receivers, None)
.verify(&params, threshold, &receivers, None)
.unwrap();
}
@@ -125,11 +107,9 @@ fn full_threshold_secret_sharing() {
let mut derived_secrets = Vec::new();
for (i, (ref mut dk, _)) in full_keys.iter_mut().enumerate() {
dk.try_update_to(epoch, &params, &mut rng).unwrap();
let shares = dealings
.iter()
.map(|dealing| decrypt_share(dk, i, &dealing.ciphertexts, epoch, None).unwrap())
.map(|dealing| decrypt_share(dk, i, &dealing.ciphertexts, None).unwrap())
.collect();
// we know dealer_share matches, but it would be inconvenient to try to put them in here,
@@ -183,22 +163,10 @@ fn full_threshold_secret_resharing() {
full_keys.push((dk, pk))
}
// start off in a defined epoch (i.e. not root);
let epoch = Epoch::new(2);
let first_dealings = node_indices
.iter()
.map(|&dealer_index| {
Dealing::create(
&mut rng,
&params,
dealer_index,
threshold,
epoch,
&receivers,
None,
)
.0
Dealing::create(&mut rng, &params, dealer_index, threshold, &receivers, None).0
})
.collect::<Vec<_>>();
@@ -208,11 +176,9 @@ fn full_threshold_secret_resharing() {
let mut derived_secrets = Vec::new();
for (i, (ref mut dk, _)) in full_keys.iter_mut().enumerate() {
dk.try_update_to(epoch, &params, &mut rng).unwrap();
let shares = first_dealings
.iter()
.map(|dealing| decrypt_share(dk, i, &dealing.ciphertexts, epoch, None).unwrap())
.map(|dealing| decrypt_share(dk, i, &dealing.ciphertexts, None).unwrap())
.collect();
let recovered_secret =
@@ -227,8 +193,6 @@ fn full_threshold_secret_resharing() {
])
.unwrap();
let next_epoch = Epoch::new(3);
// attempt to create resharing dealings!
let resharing_dealings = node_indices
.iter()
@@ -239,7 +203,6 @@ fn full_threshold_secret_resharing() {
&params,
dealer_index,
threshold,
next_epoch,
&receivers,
Some(*prior_secret),
)
@@ -249,7 +212,7 @@ fn full_threshold_secret_resharing() {
for (reshared_dealing, prior_vk) in resharing_dealings.iter().zip(recovered_partials.iter()) {
reshared_dealing
.verify(&params, next_epoch, threshold, &receivers, Some(*prior_vk))
.verify(&params, threshold, &receivers, Some(*prior_vk))
.unwrap();
}
@@ -259,11 +222,9 @@ fn full_threshold_secret_resharing() {
let mut reshared_secrets = Vec::new();
for (i, (ref mut dk, _)) in full_keys.iter_mut().enumerate() {
dk.try_update_to(next_epoch, &params, &mut rng).unwrap();
let shares = resharing_dealings
.iter()
.map(|dealing| decrypt_share(dk, i, &dealing.ciphertexts, next_epoch, None).unwrap())
.map(|dealing| decrypt_share(dk, i, &dealing.ciphertexts, None).unwrap())
.collect();
let recovered_secret =