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:
committed by
GitHub
parent
9337821712
commit
c4ee964557
@@ -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::<Vec<_>>();
|
||||
let ordered_public_keys = receivers.values().copied().collect::<Vec<_>>();
|
||||
|
||||
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::<Vec<_>>();
|
||||
let ordered_public_keys = receivers.values().copied().collect::<Vec<_>>();
|
||||
|
||||
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::<Vec<_>>();
|
||||
|
||||
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::<Vec<_>>();
|
||||
|
||||
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::<Vec<_>>();
|
||||
|
||||
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)))
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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(¶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::<Vec<_>>();
|
||||
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();
|
||||
|
||||
@@ -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(¤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<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(¶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::<Vec<_>>(),
|
||||
dk.nodes
|
||||
.iter()
|
||||
.map(|node| node.tau.clone())
|
||||
.collect::<Vec<_>>()
|
||||
);
|
||||
|
||||
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::<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), ¶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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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, ¶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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
¶ms,
|
||||
dealer_index,
|
||||
threshold,
|
||||
epoch,
|
||||
&receivers,
|
||||
None,
|
||||
)
|
||||
.0
|
||||
Dealing::create(&mut rng, ¶ms, 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, ¶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::<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,
|
||||
¶ms,
|
||||
dealer_index,
|
||||
threshold,
|
||||
epoch,
|
||||
&receivers,
|
||||
None,
|
||||
)
|
||||
.0
|
||||
Dealing::create(&mut rng, ¶ms, 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 {
|
||||
¶ms,
|
||||
node_indices[0],
|
||||
threshold,
|
||||
epoch,
|
||||
&receivers,
|
||||
None,
|
||||
);
|
||||
|
||||
@@ -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::<Vec<_>>();
|
||||
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::<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, ¶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 =
|
||||
|
||||
Reference in New Issue
Block a user