From c4ee964557128ced0fe2c572e51d2e8d127359bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bogdan-=C8=98tefan=20Neac=C5=9Fu?= Date: Mon, 21 Nov 2022 16:34:50 +0200 Subject: [PATCH] Setup with 1 epoch and full test that skips key update (#1647) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 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 --- common/crypto/dkg/benches/benchmarks.rs | 67 +- common/crypto/dkg/src/bte/encryption.rs | 205 +------ common/crypto/dkg/src/bte/keys.rs | 780 +++--------------------- common/crypto/dkg/src/bte/mod.rs | 400 +----------- common/crypto/dkg/src/dealing.rs | 45 +- common/crypto/dkg/tests/integration.rs | 61 +- 6 files changed, 132 insertions(+), 1426 deletions(-) diff --git a/common/crypto/dkg/benches/benchmarks.rs b/common/crypto/dkg/benches/benchmarks.rs index 4f226144ab..7b85d70356 100644 --- a/common/crypto/dkg/benches/benchmarks.rs +++ b/common/crypto/dkg/benches/benchmarks.rs @@ -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) { ¶ms, 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 ¶ms, receivers.keys().next().copied().unwrap(), threshold, - epoch, &receivers, None, ); let first_key = dks.get_mut(0).unwrap(); - first_key.try_update_to(epoch, ¶ms, &mut rng).unwrap(); c.bench_function( "verifying single dealing made for 3 parties (threshold 2) and recovering share", |b| { b.iter(|| { - assert!(dealing - .verify(¶ms, epoch, threshold, &receivers, None) - .is_ok()); - black_box(decrypt_share(first_key, 0, &dealing.ciphertexts, epoch, None).unwrap()); + assert!(dealing.verify(¶ms, 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) { ¶ms, 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 ¶ms, receivers.keys().next().copied().unwrap(), threshold, - epoch, &receivers, None, ); let first_key = dks.get_mut(0).unwrap(); - first_key.try_update_to(epoch, ¶ms, &mut rng).unwrap(); c.bench_function( "verifying single dealing made for 20 parties (threshold 14) and recovering share", |b| { b.iter(|| { - assert!(dealing - .verify(¶ms, epoch, threshold, &receivers, None) - .is_ok()); - black_box(decrypt_share(first_key, 0, &dealing.ciphertexts, epoch, None).unwrap()); + assert!(dealing.verify(¶ms, 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) { ¶ms, 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 ¶ms, receivers.keys().next().copied().unwrap(), threshold, - epoch, &receivers, None, ); let first_key = dks.get_mut(0).unwrap(); - first_key.try_update_to(epoch, ¶ms, &mut rng).unwrap(); c.bench_function( "verifying single dealing made for 100 parties (threshold 67) and recovering share", |b| { b.iter(|| { - assert!(dealing - .verify(¶ms, epoch, threshold, &receivers, None) - .is_ok()); - black_box(decrypt_share(first_key, 0, &dealing.ciphertexts, epoch, None).unwrap()); + assert!(dealing.verify(¶ms, 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::>(); let ordered_public_keys = receivers.values().copied().collect::>(); - let (ciphertexts, hazmat) = encrypt_shares(&remote_share_key_pairs, epoch, ¶ms, &mut rng); + let (ciphertexts, hazmat) = encrypt_shares(&remote_share_key_pairs, ¶ms, &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::>(); let ordered_public_keys = receivers.values().copied().collect::>(); - let (ciphertexts, hazmat) = encrypt_shares(&remote_share_key_pairs, epoch, ¶ms, &mut rng); + let (ciphertexts, hazmat) = encrypt_shares(&remote_share_key_pairs, ¶ms, &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::>(); - let (ciphertexts, hazmat) = encrypt_shares(&remote_share_key_pairs, epoch, ¶ms, &mut rng); + let (ciphertexts, hazmat) = encrypt_shares(&remote_share_key_pairs, ¶ms, &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::>(); - let (ciphertexts, hazmat) = encrypt_shares(&remote_share_key_pairs, epoch, ¶ms, &mut rng); + let (ciphertexts, hazmat) = encrypt_shares(&remote_share_key_pairs, ¶ms, &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(¶ms, &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, ¶ms, &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::>(); c.bench_function("100 shares encryption", |b| { - b.iter(|| { - black_box(encrypt_shares( - &remote_share_key_pairs, - epoch, - ¶ms, - &mut rng, - )) - }) + b.iter(|| black_box(encrypt_shares(&remote_share_key_pairs, ¶ms, &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(¶ms, &mut rng); + let (dk, pk) = keygen(¶ms, &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, ¶ms, &mut rng); - dk.try_update_to(epoch, ¶ms, &mut rng).unwrap(); + let (ciphertexts, _) = encrypt_shares(&[(&share, pk.public_key())], ¶ms, &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))) }); } diff --git a/common/crypto/dkg/src/bte/encryption.rs b/common/crypto/dkg/src/bte/encryption.rs index 7d7f4d4819..22fd509677 100644 --- a/common/crypto/dkg/src/bte/encryption.rs +++ b/common/crypto/dkg/src/bte/encryption.rs @@ -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 { 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(¶ms, &mut rng); let (decryption_key2, public_key2) = keygen(¶ms, &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, ¶ms, &mut rng); + let (ciphertext, hazmat) = encrypt_shares(shares, ¶ms, &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(¶ms, &mut rng); - let (mut decryption_key2, public_key2) = keygen(¶ms, &mut rng); - let epoch = Epoch::new(12345); - decryption_key1 - .try_update_to(epoch, ¶ms, &mut rng) - .unwrap(); - decryption_key2 - .try_update_to(epoch, ¶ms, &mut rng) - .unwrap(); + let (decryption_key1, public_key1) = keygen(¶ms, &mut rng); + let (decryption_key2, public_key2) = keygen(¶ms, &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, ¶ms, &mut rng); + let (ciphertext, hazmat) = encrypt_shares(shares, ¶ms, &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(¶ms, &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, ¶ms, &mut rng); - verify_hazmat_rand(&ciphertext1, &hazmat1); - - let (ciphertext2, hazmat2) = - encrypt_shares(&[(&share, &public_key.key)], epoch42, ¶ms, &mut rng); - verify_hazmat_rand(&ciphertext2, &hazmat2); - - let (ciphertext3, hazmat3) = - encrypt_shares(&[(&share, &public_key.key)], epoch_big, ¶ms, &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(¶ms, &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, ¶ms, &mut rng) - .unwrap(); - - let (ciphertext, hazmat) = - encrypt_shares(&[(&share, &public_key.key)], epoch, ¶ms, &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(¶ms, &mut rng); - - let epoch = Epoch::new(12345); - decryption_key - .try_update_to(epoch, ¶ms, &mut rng) - .unwrap(); - for node in decryption_key.nodes.iter_mut() { - node.reblind(¶ms, &mut rng); - } - let share = Share::random(&mut rng); - - let (ciphertext, hazmat) = - encrypt_shares(&[(&share, &public_key.key)], epoch, ¶ms, &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, ¶ms, &mut rng) - .unwrap(); - for node in decryption_key.nodes.iter_mut() { - node.reblind(¶ms, &mut rng); - } - let share2 = Share::random(&mut rng); - - let (ciphertext, hazmat) = - encrypt_shares(&[(&share2, &public_key.key)], epoch2, ¶ms, &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(¶ms, &mut rng); - let epoch = Epoch::new(1); + let (_, public_key) = keygen(¶ms, &mut rng); - dk.try_update_to(epoch, ¶ms, &mut rng).unwrap(); let share = Share::random(&mut rng); - let (ciphertext, _) = - encrypt_shares(&[(&share, &public_key.key)], epoch, ¶ms, &mut rng); - assert!(ciphertext.verify_integrity(¶ms, epoch)) + let (ciphertext, _) = encrypt_shares(&[(&share, &public_key.key)], ¶ms, &mut rng); + assert!(ciphertext.verify_integrity(¶ms)) } #[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(¶ms, &mut rng); - let epoch = Epoch::new(1); + let (_, public_key) = keygen(¶ms, &mut rng); - dk.try_update_to(epoch, ¶ms, &mut rng).unwrap(); let share = Share::random(&mut rng); - let (ciphertext, _) = - encrypt_shares(&[(&share, &public_key.key)], epoch, ¶ms, &mut rng); + let (ciphertext, _) = encrypt_shares(&[(&share, &public_key.key)], ¶ms, &mut rng); let mut bad_cipher1 = ciphertext.clone(); bad_cipher1.rr[4] = G1Projective::generator(); - assert!(!bad_cipher1.verify_integrity(¶ms, epoch)); + assert!(!bad_cipher1.verify_integrity(¶ms)); let mut bad_cipher2 = ciphertext.clone(); bad_cipher2.ss[4] = G1Projective::generator(); - assert!(!bad_cipher2.verify_integrity(¶ms, epoch)); + assert!(!bad_cipher2.verify_integrity(¶ms)); let mut bad_cipher3 = ciphertext; bad_cipher3.zz[4] = G2Projective::generator(); - assert!(!bad_cipher3.verify_integrity(¶ms, 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(¶ms, &mut rng); - let epoch = Epoch::new(1); - - dk.try_update_to(epoch, ¶ms, &mut rng).unwrap(); - let share = Share::random(&mut rng); - let (ciphertext, _) = - encrypt_shares(&[(&share, &public_key.key)], epoch, ¶ms, &mut rng); - - let another_epoch = Epoch::new(2); - assert!(!ciphertext.verify_integrity(¶ms, another_epoch)) + assert!(!bad_cipher3.verify_integrity(¶ms)); } #[test] @@ -711,7 +558,7 @@ mod tests { } let refs = shares.iter().zip(public_keys.iter()).collect::>(); - let (ciphertext, hazmat) = encrypt_shares(&refs, Epoch::new(42), ¶ms, &mut rng); + let (ciphertext, hazmat) = encrypt_shares(&refs, ¶ms, &mut rng); let combined_r = combine_scalar_chunks(hazmat.r()); let combined_rr = ciphertext.combine_rs(); diff --git a/common/crypto/dkg/src/bte/keys.rs b/common/crypto/dkg/src/bte/keys.rs index 857b01c5b0..c2c0e91a33 100644 --- a/common/crypto/dkg/src/bte/keys.rs +++ b/common/crypto/dkg/src/bte/keys.rs @@ -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, - - // fh_i^rho, always lambda_h elements - pub(crate) dh: Vec, - - // h^rho - pub(crate) e: G2Projective, -} - -impl Node { - fn new_root( - a: G1Projective, - b: G2Projective, - ds: Vec, - dh: Vec, - 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 { - 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 { - // 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, + // 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, + + // 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, DkgError> { - let current_node = self.current()?; - if current_node.is_root() { - Ok(None) - } else { - Epoch::try_from_tau(¤t_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, e: G2Projective) -> Self { + DecryptionKey { a, b, dh, e } } pub fn to_bytes(&self) -> Vec { - 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 { - // we have to be able to read the length of nodes - if b.len() < 4 { + pub fn try_from_bytes(bytes: &[u8]) -> Result { + // 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(¶ms, &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), ¶ms, &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), ¶ms, &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::>(), - dk.nodes - .iter() - .map(|node| node.tau.clone()) - .collect::>() - ); - - dk.try_update_to(Epoch::new(2), ¶ms, &mut rng).unwrap(); - dk.try_update_to(Epoch::new(3), ¶ms, &mut rng).unwrap(); - dk.try_update_to(Epoch::new(4), ¶ms, &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), ¶ms, &mut rng) - .unwrap(); - assert_eq!( - dk.nodes - .iter() - .map(|node| node.tau.clone()) - .collect::>(), - new_root - .nodes - .iter() - .map(|node| node.tau.clone()) - .collect::>() - ); - - // 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), ¶ms, &mut rng).unwrap(); - assert_eq!(dk.nodes.last().unwrap().tau, Tau::new(42)); - dk.try_update_to(Epoch::new(123456), ¶ms, &mut rng) - .unwrap(); - assert_eq!(dk.nodes.last().unwrap().tau, Tau::new(123456)); - dk.try_update_to(Epoch::new(3292547435), ¶ms, &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), ¶ms, &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(¶ms, &mut rng); - - // for root node current epoch is `None` - assert_eq!(None, dk.current_epoch(¶ms).unwrap()); - - // for root node it should result in epoch 0 - dk.try_update_to_next_epoch(¶ms, &mut rng).unwrap(); - assert_eq!(Some(Epoch::new(0)), dk.current_epoch(¶ms).unwrap()); - - dk.try_update_to_next_epoch(¶ms, &mut rng).unwrap(); - assert_eq!(Some(Epoch::new(1)), dk.current_epoch(¶ms).unwrap()); - - dk.try_update_to_next_epoch(¶ms, &mut rng).unwrap(); - assert_eq!(Some(Epoch::new(2)), dk.current_epoch(¶ms).unwrap()); - - // if we start from some non-root epoch, it should result in l + 1 - dk.try_update_to(Epoch::new(42), ¶ms, &mut rng).unwrap(); - dk.try_update_to_next_epoch(¶ms, &mut rng).unwrap(); - assert_eq!(Some(Epoch::new(43)), dk.current_epoch(¶ms).unwrap()); - - dk.try_update_to(Epoch::new(12345), ¶ms, &mut rng) - .unwrap(); - dk.try_update_to_next_epoch(¶ms, &mut rng).unwrap(); - assert_eq!(Some(Epoch::new(12346)), dk.current_epoch(¶ms).unwrap()); - - dk.try_update_to(Epoch::new(3292547435), ¶ms, &mut rng) - .unwrap(); - dk.try_update_to_next_epoch(¶ms, &mut rng).unwrap(); - assert_eq!( - Some(Epoch::new(3292547436)), - dk.current_epoch(¶ms).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(¶ms, &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), ¶ms, &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(¶ms, &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), ¶ms, &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), ¶ms, &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), ¶ms, &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), ¶ms, &mut rng) - .unwrap(); - let bytes = dk.to_bytes(); - let recovered = DecryptionKey::try_from_bytes(&bytes).unwrap(); - assert_eq!(dk, recovered); - } } diff --git a/common/crypto/dkg/src/bte/mod.rs b/common/crypto/dkg/src/bte/mod.rs index cb3dd07fa4..2eca744cf1 100644 --- a/common/crypto/dkg/src/bte/mod.rs +++ b/common/crypto/dkg/src/bte/mod.rs @@ -1,17 +1,11 @@ // Copyright 2022 - Nym Technologies SA // 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); - -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 { - 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 { - 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::()) - } - - 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 { - 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 { - 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 { - if !tau.is_leaf(params) { - Err(DkgError::MalformedEpoch) - } else { - Ok(Epoch(tau.0.load_be())) - } - } -} - -impl From for Tau { - fn from(epoch: Epoch) -> Self { - Tau(epoch.0.view_bits().to_bitvec()) - } -} - -impl From 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, // f_1, f_2, .... f_{lambda_t} in the paper - fh: Vec, // f_{lambda_t+1}, f_{lambda_t+1}, .... f_{lambda_t+lambda_h} in the paper + fh: Vec, // 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, ¶ms).unwrap()); - assert_eq!(Epoch::new(1), Epoch::try_from_tau(&tau1, ¶ms).unwrap()); - assert_eq!( - Epoch::new(42), - Epoch::try_from_tau(&tau42, ¶ms).unwrap() - ); - assert_eq!( - Epoch::new(3292547435), - Epoch::try_from_tau(&tau_big, ¶ms).unwrap() - ); - - assert!(Epoch::try_from_tau(&Tau(BitVec::new()), ¶ms).is_err()); - assert!(Epoch::try_from_tau(&Tau(bitvec![u32, Msb0; 1,0,1,1,0]), ¶ms).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, ¶ms).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, ¶ms).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()); - } -} diff --git a/common/crypto/dkg/src/dealing.rs b/common/crypto/dkg/src/dealing.rs index bf471d7e37..ead51b7f80 100644 --- a/common/crypto/dkg/src/dealing.rs +++ b/common/crypto/dkg/src/dealing.rs @@ -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, prior_resharing_secret: Option, @@ -58,8 +55,7 @@ impl Dealing { .collect::>(); let ordered_public_keys = receivers.values().copied().collect::>(); - 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, prior_resharing_public: Option, @@ -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, - ¶ms, - dealer_index, - threshold, - epoch, - &receivers, - None, - ) - .0 + Dealing::create(&mut rng, ¶ms, dealer_index, threshold, &receivers, None).0 }) .collect::>(); let mut derived_secrets = Vec::new(); for (i, (ref mut dk, _)) in full_keys.iter_mut().enumerate() { - dk.try_update_to(epoch, ¶ms, &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::>()).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, - ¶ms, - dealer_index, - threshold, - epoch, - &receivers, - None, - ) - .0 + Dealing::create(&mut rng, ¶ms, dealer_index, threshold, &receivers, None).0 }) .collect::>(); @@ -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::>(); - let epoch = Epoch::new(2); let mut receivers = BTreeMap::new(); for index in &node_indices { @@ -491,7 +459,6 @@ mod tests { ¶ms, node_indices[0], threshold, - epoch, &receivers, None, ); diff --git a/common/crypto/dkg/tests/integration.rs b/common/crypto/dkg/tests/integration.rs index 8e6c001385..24518dd789 100644 --- a/common/crypto/dkg/tests/integration.rs +++ b/common/crypto/dkg/tests/integration.rs @@ -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() { ¶ms, node_indices[0], threshold, - epoch, &receivers, None, ); dealing - .verify(¶ms, epoch, threshold, &receivers, None) + .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 mut dk, _)) in full_keys.iter_mut().enumerate() { - dk.try_update_to(epoch, ¶ms, &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, - ¶ms, - dealer_index, - threshold, - epoch, - &receivers, - None, - ) - .0 + Dealing::create(&mut rng, ¶ms, dealer_index, threshold, &receivers, None).0 }) .collect::>(); for dealing in dealings.iter() { dealing - .verify(¶ms, epoch, threshold, &receivers, None) + .verify(¶ms, 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, ¶ms, &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, - ¶ms, - dealer_index, - threshold, - epoch, - &receivers, - None, - ) - .0 + Dealing::create(&mut rng, ¶ms, dealer_index, threshold, &receivers, None).0 }) .collect::>(); @@ -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, ¶ms, &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() { ¶ms, 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(¶ms, next_epoch, threshold, &receivers, Some(*prior_vk)) + .verify(¶ms, 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, ¶ms, &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 =