From ecc1d5a0a6aae34f8d7cbcb2b211aa80fb71ebb8 Mon Sep 17 00:00:00 2001 From: Jack Lloyd Date: Thu, 19 Jan 2023 16:33:03 +0000 Subject: [PATCH] refactor(crypto): CRP-1798 Rework NIDKG handling of epochs --- .../src/ni_dkg/fs_ni_dkg/forward_secure.rs | 359 ++++++++++-------- .../ni_dkg/groth20_bls12_381/encryption.rs | 64 ++-- .../encryption/conversions.rs | 23 -- .../groth20_bls12_381/encryption/tests.rs | 6 +- .../threshold_sig/bls12_381/tests/epoch.rs | 48 +-- .../bls12_381/tests/forward_secure.rs | 38 +- .../bls12_381/tests/integration_tests.rs | 14 +- 7 files changed, 267 insertions(+), 285 deletions(-) delete mode 100644 rs/crypto/internal/crypto_lib/threshold_sig/bls12_381/src/ni_dkg/groth20_bls12_381/encryption/conversions.rs diff --git a/rs/crypto/internal/crypto_lib/threshold_sig/bls12_381/src/ni_dkg/fs_ni_dkg/forward_secure.rs b/rs/crypto/internal/crypto_lib/threshold_sig/bls12_381/src/ni_dkg/fs_ni_dkg/forward_secure.rs index 29f3a1f19b6..14e499860d1 100644 --- a/rs/crypto/internal/crypto_lib/threshold_sig/bls12_381/src/ni_dkg/fs_ni_dkg/forward_secure.rs +++ b/rs/crypto/internal/crypto_lib/threshold_sig/bls12_381/src/ni_dkg/fs_ni_dkg/forward_secure.rs @@ -53,7 +53,7 @@ const DOMAIN_CIPHERTEXT_NODE: &str = "ic-fs-encryption/binary-tree-node"; /// Type for a single bit #[derive(Copy, Clone, Debug, Eq, PartialEq, Zeroize)] -pub enum Bit { +pub(crate) enum Bit { Zero = 0, One = 1, } @@ -77,8 +77,8 @@ impl From<&Bit> for u8 { } } -impl From<&Bit> for i32 { - fn from(b: &Bit) -> i32 { +impl From<&Bit> for u32 { + fn from(b: &Bit) -> u32 { match &b { Bit::Zero => 0, Bit::One => 1, @@ -86,36 +86,67 @@ impl From<&Bit> for i32 { } } -/// Generates tau (a vector of bits) from an epoch. -pub fn tau_from_epoch(epoch: Epoch) -> Vec { - (0..LAMBDA_T) - .rev() - .map(|index| { - if (epoch.get() >> index) & 1 == 0 { - Bit::Zero - } else { - Bit::One +/// Represents a prefix of an epoch. +/// +/// The bits are the encoding (in big-endian ordering) of an +/// integer which represents a prefix of an epoch. +#[derive(Debug, Clone, Zeroize)] +pub(crate) struct Tau(pub Vec); + +impl Tau { + pub fn empty() -> Self { + Self(vec![]) + } + + pub fn extended_by(&self, bit: Bit) -> Self { + let mut ext = self.clone(); + ext.push(bit); + ext + } + + pub fn push(&mut self, bit: Bit) { + self.0.push(bit); + } + + pub fn len(&self) -> usize { + self.0.len() + } + + fn is_prefix_of_epoch(&self, epoch: Epoch) -> bool { + fn is_prefix(xs: &[Bit], ys: &[Bit]) -> bool { + if xs.len() > ys.len() { + return false; + } + for i in 0..xs.len() { + if xs[i] != ys[i] { + return false; + } } - }) - .collect() + true + } + + is_prefix(&self.0, &Tau::from(epoch).0) + } } -/// Converts an epoch prefix to an epoch by filling in remaining bits with -/// zeros. -pub fn epoch_from_tau_vec(tau: &[Bit]) -> Epoch { - let num_bits = ::std::mem::size_of::() * 8; - Epoch::from( - (0..num_bits) +impl std::ops::Index for Tau { + type Output = Bit; + + fn index(&self, index: usize) -> &Self::Output { + &self.0[index] + } +} + +impl From for Tau { + /// Gets the leaf node tau for a given epoch + fn from(epoch: Epoch) -> Tau { + let epoch = epoch.get(); + let num_bits = std::mem::size_of::() * 8; + Tau((0..num_bits) .rev() - .zip(tau) - .fold(0u32, |epoch, (shift, tau)| { - epoch - | ((match *tau { - Bit::One => 1, - Bit::Zero => 0, - }) << shift) - }), - ) + .map(|shift| Bit::from(((epoch >> shift) & 1) as u8)) + .collect()) + } } /// A node of a Binary Tree Encryption scheme. @@ -123,7 +154,7 @@ pub fn epoch_from_tau_vec(tau: &[Bit]) -> Epoch { /// Notation from section 7.2. pub(crate) struct BTENode { // Bit-vector, indicating a path in a binary tree. - pub tau: Vec, + pub tau: Tau, pub a: G1Affine, pub b: G2Affine, @@ -162,7 +193,7 @@ impl Drop for BTENode { impl BTENode { pub(crate) fn serialize(&self) -> BTENodeBytes { BTENodeBytes { - tau: self.tau.iter().map(|i| *i as u8).collect(), + tau: self.tau.0.iter().map(|i| *i as u8).collect(), a: self.a.serialize_to::(), b: self.b.serialize_to::(), d_t: self @@ -184,7 +215,7 @@ impl BTENode { /// Assumes inputs are trusted points. Panics if deserialization fails. pub(crate) fn deserialize_unchecked(node: &BTENodeBytes) -> Self { Self { - tau: node.tau.iter().copied().map(Bit::from).collect(), + tau: Tau(node.tau.iter().copied().map(Bit::from).collect()), a: G1Affine::deserialize_unchecked(&node.a).expect("Malformed secret key at BTENode.a"), b: G2Affine::deserialize_unchecked(&node.b).expect("Malformed secret key at BTENode.b"), d_t: node @@ -315,7 +346,7 @@ pub fn kgen( let e = G2Affine::from(&sys.h * &rho); let bte_root = BTENode { - tau: Vec::new(), + tau: Tau::empty(), a, b, d_t, @@ -344,48 +375,6 @@ pub fn kgen( } impl SecretKey { - /// The current key (the end of list of BTENodes) of a `SecretKey` should - /// always correspond to an epoch described by LAMBDA_T bits. Some - /// internal operations break this invariant, leaving less than LAMBDA_T - /// bits in the current key. This function should be called when this - /// happens; it modifies the list so the current key corresponds to the - /// first epoch of the subtree described by the current key. - /// - /// For example, if LAMBDA_T = 5, then [..., 011] will change to - /// [..., 0111, 01101, 01100]. - /// The current key's `tau` now has 5 bits, and the other entries cover the - /// rest of the 011 subtree after we delete the current key. - /// - /// Another example: during the very first epoch the private key is - /// [1, 01, 001, 0001, 00001, 00000]. - /// - /// This makes key update easy: pop off the current key, then call this - /// function. - /// - /// An alternative is to only store the root nodes of the subtrees that - /// cover the remaining valid keys. Thus the first epoch, the private - /// key would simply be \[0\], and would only change to [1, 01, 001, 0001, - /// 00001] after the first update. Generally, some computations - /// happen one epoch later than they would with our current scheme. However, - /// key update is a bit fiddlier. - /// - /// No-op if `self` is empty. - pub(crate) fn fast_derive(&mut self, sys: &SysParam, rng: &mut R) { - let mut epoch = Vec::new(); - if self.bte_nodes.is_empty() { - return; - } - let now = self.current().expect("bte_nodes unexpectedly empty"); - for i in 0..LAMBDA_T { - if i < now.tau.len() { - epoch.push(now.tau[i]); - } else { - epoch.push(Bit::Zero); - } - } - self.update_to(&epoch, sys, rng); - } - fn new(bte_root: BTENode) -> SecretKey { let mut bte_nodes = LinkedList::new(); bte_nodes.push_back(bte_root); @@ -399,49 +388,87 @@ impl SecretKey { /// Gets the current epoch for a secret key. /// - /// # Panics - /// This will panic if the secret key has expired; in this case it has no - /// current epoch. - pub(crate) fn epoch(&self) -> Epoch { - epoch_from_tau_vec(&self.current().expect("No more secret keys left").tau) - } - - /// Return if this key has been exhausted and can no longer be used - pub fn is_exhausted(&self) -> bool { - self.current().is_none() + /// Returns None if the key has been exhausted + pub fn current_epoch(&self) -> Option { + if let Some(node) = self.current() { + let mut epoch = 0u32; + for i in 0..LAMBDA_T { + let x = if let Some(t) = node.tau.0.get(i) { + t.into() + } else { + 0 + }; + + epoch |= x << (LAMBDA_T - 1 - i); + } + Some(Epoch::from(epoch)) + } else { + None + } } /// Updates this key to the next epoch. After an update, /// the decryption keys for previous epochs are not accessible any more. /// (KUpd(dk, 1) from Sect. 9.1) + /// + /// The current key (the end of list of BTENodes) of a `SecretKey` should + /// always correspond to an epoch described by LAMBDA_T bits. However this + /// invariant is broken when we pop the final node. The second call to + /// update_to modifies the list so the current key corresponds to the + /// first epoch of the subtree described by the current key. + /// + /// For example, if LAMBDA_T = 5, then [..., 011] will change to + /// [..., 0111, 01101, 01100]. + /// The current key's `tau` now has 5 bits, and the other entries cover the + /// rest of the 011 subtree after we delete the current key. + /// + /// Another example: during the very first epoch the private key is + /// [1, 01, 001, 0001, 00001, 00000]. + /// + /// This makes key update easy: pop off the current key, then update again + /// + /// An alternative is to only store the root nodes of the subtrees that + /// cover the remaining valid keys. Thus the first epoch, the private + /// key would simply be \[0\], and would only change to [1, 01, 001, 0001, + /// 00001] after the first update. Generally, some computations + /// happen one epoch later than they would with our current scheme. However, + /// key update is a bit fiddlier. pub fn update(&mut self, sys: &SysParam, rng: &mut R) { - self.fast_derive(sys, rng); + if let Some(current_epoch) = self.current_epoch() { + self.update_to(current_epoch, sys, rng); + } match self.bte_nodes.pop_back() { None => {} Some(mut dk) => { dk.zeroize(); - self.fast_derive(sys, rng); + if let Some(current_epoch) = self.current_epoch() { + self.update_to(current_epoch, sys, rng); + } } } } /// Updates `self` to the given `epoch`. /// - /// If `epoch` is in the past, then disables `self`. + /// If `epoch` is in the past, with respect to the current key, then nothing happens + /// If `epoch` is the current epoch of the key, then nothing happens /// /// A key update can take up to 2*LAMBDA_T*LAMBDA_H G2 multiplications - pub fn update_to( - &mut self, - epoch: &[Bit], - sys: &SysParam, - rng: &mut R, - ) { - // dropWhileEnd (\node -> not $ tau node `isPrefixOf` epoch) bte_nodes + pub fn update_to(&mut self, epoch: Epoch, sys: &SysParam, rng: &mut R) { + if let Some(current_epoch) = self.current_epoch() { + if current_epoch > epoch { + return; + } + } + + // Drop nodes from the end of bte_nodes until we either run out of nodes + // (in which case return, with the key disabled) or until we arrive at a + // node whose tau value is a prefix of the target epoch. loop { match self.bte_nodes.back() { None => return, Some(cur) => { - if is_prefix(&cur.tau, epoch) { + if cur.tau.is_prefix_of_epoch(epoch) { break; } } @@ -452,7 +479,7 @@ impl SecretKey { .zeroize(); } - let g1 = G1Affine::generator(); + let epoch = Tau::from(epoch); // At this point, bte_nodes.back() is a prefix of `epoch`. // Replace it with the nodes for `epoch` and later (in the subtree). @@ -463,9 +490,8 @@ impl SecretKey { // * We can still derive the keys for 01110 and 01111 from 0111. // * We can no longer decrypt 01100. let mut node = self.bte_nodes.pop_back().expect("self.bte_nodes was empty"); - let mut n = node.tau.len(); // Nothing to do if `node.tau` is already `epoch`. - if n == epoch.len() { + if node.tau.len() == epoch.len() { self.bte_nodes.push_back(node); return; } @@ -476,14 +502,14 @@ impl SecretKey { let mut b_acc = G2Projective::from(&node.b); let mut f_acc = ftau_partial(&node.tau, sys).expect("node.tau not the expected size"); let mut tau = node.tau.clone(); - while n < epoch.len() { + + for n in node.tau.len()..epoch.len() { if epoch[n] == Bit::Zero { // Save the root of the right subtree for later. - let mut tau_1 = tau.clone(); - tau_1.push(Bit::One); + let tau_1 = tau.extended_by(Bit::One); let delta = Scalar::random(rng); - let a_blind = (g1 * &delta) + &node.a; + let a_blind = (G1Affine::generator() * &delta) + &node.a; let mut b_blind = G2Projective::from(d_t.pop_front().expect("d_t not sufficiently large")); b_blind += &b_acc; @@ -519,18 +545,17 @@ impl SecretKey { b_acc += d_t.pop_front().expect("d_t not sufficiently large"); } tau.push(epoch[n]); - n += 1; } let delta = Scalar::random(rng); - let a = g1 * &delta + &node.a; + let a = G1Affine::generator() * &delta + &node.a; let e = &sys.h * &delta + &node.e; b_acc += f_acc * δ let mut d_t_blind = LinkedList::new(); // Typically `d_t_blind` remains empty. // It is only nontrivial if `epoch` is less than LAMBDA_T bits. - let mut k = n; + let mut k = epoch.len(); d_t.iter().for_each(|d| { let tmp = (&sys.f[k] * &delta) + d; d_t_blind.push_back(tmp.to_affine()); @@ -682,7 +707,7 @@ pub struct EncryptionWitness { /// in the NIZK proofs. pub fn enc_chunks( recipient_and_message: &[(G1Affine, PlaintextChunks)], - tau: &[Bit], + epoch: Epoch, associated_data: &[u8], sys: &SysParam, rng: &mut R, @@ -720,8 +745,7 @@ pub fn enc_chunks( cc }; - let extended_tau = extend_tau(&cc, &rr, &ss, tau, associated_data); - let id = ftau(&extended_tau, sys).expect("extended_tau not the correct size"); + let id = ftau_extended(&cc, &rr, &ss, sys, epoch, associated_data); let mut zz = Vec::with_capacity(NUM_CHUNKS); let id_h_tbl = G2Projective::compute_mul2_tbl(&id, &G2Projective::from(&sys.h)); @@ -737,20 +761,10 @@ pub fn enc_chunks( (crsz, witness) } -fn is_prefix(xs: &[Bit], ys: &[Bit]) -> bool { - if xs.len() > ys.len() { - return false; - } - for i in 0..xs.len() { - if xs[i] != ys[i] { - return false; - } - } - true -} - -fn find_prefix<'a>(dks: &'a SecretKey, tau: &[Bit]) -> Option<&'a BTENode> { - dks.bte_nodes.iter().find(|&node| is_prefix(&node.tau, tau)) +fn find_prefix(dks: &SecretKey, epoch: Epoch) -> Option<&BTENode> { + dks.bte_nodes + .iter() + .find(|&node| node.tau.is_prefix_of_epoch(epoch)) } /// Error while decrypting @@ -776,7 +790,7 @@ pub fn dec_chunks( dks: &SecretKey, i: usize, crsz: &FsEncryptionCiphertext, - tau: &[Bit], + epoch: Epoch, associated_data: &[u8], ) -> Result { let n = crsz.cc.len(); @@ -786,25 +800,25 @@ pub fn dec_chunks( return Err(DecErr::InvalidCiphertext); } - let extended_tau = extend_tau(&crsz.cc, &crsz.rr, &crsz.ss, tau, associated_data); - let dk = match find_prefix(dks, tau) { - None => return Err(DecErr::ExpiredKey), - Some(node) => node, - }; - let mut bneg = G2Projective::from(&dk.b); - let mut l = dk.tau.len(); - for t in dk.d_t.iter() { - if extended_tau[l] == Bit::One { - bneg += t; + let dk = find_prefix(dks, epoch).ok_or(DecErr::ExpiredKey)?; + + let bneg = { + let extended_tau = extend_tau(&crsz.cc, &crsz.rr, &crsz.ss, epoch, associated_data); + + let mut bneg = G2Projective::from(&dk.b); + + for (i, t) in dk.d_t.iter().enumerate() { + if extended_tau[dk.tau.len() + i] == Bit::One { + bneg += t; + } } - l += 1 - } - for k in 0..LAMBDA_H { - if extended_tau[LAMBDA_T + k] == Bit::One { - bneg += &dk.d_h[k]; + for k in 0..LAMBDA_H { + if extended_tau[LAMBDA_T + k] == Bit::One { + bneg += &dk.d_h[k]; + } } - } - bneg = bneg.neg(); + bneg.neg() + }; let cj = &crsz.cc[i]; @@ -868,7 +882,7 @@ pub fn dec_chunks( /// we must also verify ciphertext integrity. pub fn verify_ciphertext_integrity( crsz: &FsEncryptionCiphertext, - tau: &[Bit], + epoch: Epoch, associated_data: &[u8], sys: &SysParam, ) -> Result<(), ()> { @@ -884,8 +898,7 @@ pub fn verify_ciphertext_integrity( return Err(()); } - let extended_tau = extend_tau(&crsz.cc, &crsz.rr, &crsz.ss, tau, associated_data); - let id = ftau(&extended_tau, sys).expect("extended_tau not the correct size"); + let id = ftau_extended(&crsz.cc, &crsz.rr, &crsz.ss, sys, epoch, associated_data); let g1_neg = G1Affine::generator().neg(); let precomp_id = G2Prepared::from(&id); @@ -911,58 +924,70 @@ pub fn verify_ciphertext_integrity( /// Returns (tau || RO(cc, rr, ss, tau, associated_data)). /// /// See the description of Deal in Section 7.1. -pub fn extend_tau( +pub(crate) fn extend_tau( cc: &[Vec], rr: &[G1Affine], ss: &[G1Affine], - tau: &[Bit], + epoch: Epoch, associated_data: &[u8], -) -> Vec { +) -> Tau { let mut map = HashedMap::new(); map.insert_hashed("ciphertext-chunks", &cc.to_vec()); map.insert_hashed("randomizers-r", &rr.to_vec()); map.insert_hashed("randomizers-s", &ss.to_vec()); - map.insert_hashed("epoch", &(epoch_from_tau_vec(tau).get() as usize)); + map.insert_hashed("epoch", &(epoch.get() as usize)); map.insert_hashed("associated-data", &associated_data.to_vec()); let hash = random_oracle(DOMAIN_CIPHERTEXT_NODE, &map); - let mut extended_tau: Vec = tau.to_vec(); + let tau = Tau::from(epoch); + + let mut extended_tau: Vec = tau.0; hash.iter().for_each(|byte| { for b in 0..8 { extended_tau.push(Bit::from((byte >> b) & 1)); } }); - extended_tau + Tau(extended_tau) } -/// Computes the function f of the paper. +/// Extends tau using the random oracle, and computes the function f /// -/// The bit vector tau must have length LAMBDA_T + LAMBDA_H. -pub fn ftau(tau: &[Bit], sys: &SysParam) -> Option { - if tau.len() != LAMBDA_T + LAMBDA_H { - return None; - } +/// See the description of Deal in Section 7.1. +pub fn ftau_extended( + cc: &[Vec], + rr: &[G1Affine], + ss: &[G1Affine], + sys: &SysParam, + epoch: Epoch, + associated_data: &[u8], +) -> G2Projective { + let extended_tau = extend_tau(cc, rr, ss, epoch, associated_data); + let mut id = G2Projective::from(&sys.f0); - for (n, t) in tau.iter().enumerate() { - if *t == Bit::One { - if n < LAMBDA_T { - id += &sys.f[n]; - } else { - id += &sys.f_h[n - LAMBDA_T]; - } + + for i in 0..LAMBDA_T { + if extended_tau.0[i] == Bit::One { + id += &sys.f[i]; } } - Some(id) + + for i in 0..LAMBDA_H { + if extended_tau.0[LAMBDA_T + i] == Bit::One { + id += &sys.f_h[i]; + } + } + + id } /// Computes f for bit vectors tau <= LAMBDA_T. -fn ftau_partial(tau: &[Bit], sys: &SysParam) -> Option { - if tau.len() > LAMBDA_T { +pub(crate) fn ftau_partial(tau: &Tau, sys: &SysParam) -> Option { + if tau.0.len() > LAMBDA_T { return None; } let mut id = G2Projective::from(&sys.f0); - tau.iter().zip(sys.f.iter()).for_each(|(t, f)| { + tau.0.iter().zip(sys.f.iter()).for_each(|(t, f)| { if *t == Bit::One { id += f; } diff --git a/rs/crypto/internal/crypto_lib/threshold_sig/bls12_381/src/ni_dkg/groth20_bls12_381/encryption.rs b/rs/crypto/internal/crypto_lib/threshold_sig/bls12_381/src/ni_dkg/groth20_bls12_381/encryption.rs index f2279cb4800..4999d3c799e 100644 --- a/rs/crypto/internal/crypto_lib/threshold_sig/bls12_381/src/ni_dkg/groth20_bls12_381/encryption.rs +++ b/rs/crypto/internal/crypto_lib/threshold_sig/bls12_381/src/ni_dkg/groth20_bls12_381/encryption.rs @@ -7,36 +7,29 @@ use super::types::FsEncryptionKeySetWithPop; use super::ALGORITHM_ID; -use crate::api::ni_dkg_errors::CspDkgVerifyDealingError; use crate::api::ni_dkg_errors::{ - DecryptError, EncryptAndZKProveError, MalformedPublicKeyError, SizeError, + CspDkgVerifyDealingError, DecryptError, EncryptAndZKProveError, MalformedPublicKeyError, + SizeError, }; -use conversions::Tau; use ic_crypto_internal_bls12_381_type::{G1Affine, G2Affine, Scalar}; use ic_crypto_internal_seed::Seed; -use ic_crypto_internal_types::sign::threshold_sig::ni_dkg::ni_dkg_groth20_bls12_381::{ - FsEncryptionCiphertextBytes, FsEncryptionPublicKey, NodeIndex, +use ic_crypto_internal_types::sign::threshold_sig::{ + ni_dkg::ni_dkg_groth20_bls12_381::{ + FsEncryptionCiphertextBytes, FsEncryptionPublicKey, NodeIndex, ZKProofDec, ZKProofShare, + }, + ni_dkg::Epoch, + public_coefficients::bls12_381::PublicCoefficientsBytes, }; -use ic_crypto_internal_types::sign::threshold_sig::ni_dkg::ni_dkg_groth20_bls12_381::{ - ZKProofDec, ZKProofShare, -}; -use ic_crypto_internal_types::sign::threshold_sig::ni_dkg::Epoch; -use ic_crypto_internal_types::sign::threshold_sig::public_coefficients::bls12_381::PublicCoefficientsBytes; -use ic_types::crypto::error::InvalidArgumentError; -use ic_types::crypto::AlgorithmId; -use ic_types::NumberOfNodes; +use ic_types::{crypto::error::InvalidArgumentError, crypto::AlgorithmId, NumberOfNodes}; use rand::{CryptoRng, RngCore}; use std::collections::BTreeMap; use std::convert::TryFrom; -pub(crate) mod conversions; - mod crypto { pub use crate::ni_dkg::fs_ni_dkg::encryption_key_pop::EncryptionKeyPop; pub use crate::ni_dkg::fs_ni_dkg::forward_secure::{ - dec_chunks, enc_chunks, epoch_from_tau_vec, kgen, verify_ciphertext_integrity, Bit, - EncryptionWitness, FsEncryptionCiphertext, PlaintextChunks, PublicKeyWithPop, SecretKey, - SysParam, + dec_chunks, enc_chunks, kgen, verify_ciphertext_integrity, EncryptionWitness, + FsEncryptionCiphertext, PlaintextChunks, PublicKeyWithPop, SecretKey, SysParam, }; pub use crate::ni_dkg::fs_ni_dkg::nizk_chunking::{ prove_chunking, verify_chunking, ChunkingInstance, ChunkingWitness, ProofChunking, @@ -88,9 +81,10 @@ pub fn create_forward_secure_key_pair( /// * `seed` - Randomness used in updating the secret key to the given `epoch`. pub fn update_key_inplace_to_epoch(secret_key: &mut crypto::SecretKey, epoch: Epoch, seed: Seed) { let mut rng = seed.into_rng(); - let tau = Tau::from(epoch); - if secret_key.epoch() < epoch { - secret_key.update_to(&tau.0, crypto::SysParam::global(), &mut rng); + if let Some(current_epoch) = secret_key.current_epoch() { + if current_epoch < epoch { + secret_key.update_to(epoch, crypto::SysParam::global(), &mut rng); + } } } @@ -143,11 +137,10 @@ pub fn encrypt_and_prove( v }; - let tau = Tau::from(epoch); let mut rng = seed.into_rng(); let (ciphertext, encryption_witness) = crypto::enc_chunks( &keys_and_messages, - &tau.0[..], + epoch, associated_data, crypto::SysParam::global(), &mut rng, @@ -161,11 +154,7 @@ pub fn encrypt_and_prove( &mut rng, ); - let public_coefficients = public_coefficients - .coefficients - .iter() - .map(G2Affine::deserialize) - .collect::, _>>() + let public_coefficients = G2Affine::batch_deserialize(&public_coefficients.coefficients) .map_err(|_| EncryptAndZKProveError::MalformedPublicCoefficients)?; let sharing_proof = prove_sharing( @@ -244,17 +233,17 @@ pub fn decrypt( node_index, }); } - let current_epoch = secret_key.epoch(); - if epoch < current_epoch { - return Err(DecryptError::EpochTooOld { - ciphertext_epoch: epoch, - secret_key_epoch: current_epoch, - }); + if let Some(current_epoch) = secret_key.current_epoch() { + if epoch < current_epoch { + return Err(DecryptError::EpochTooOld { + ciphertext_epoch: epoch, + secret_key_epoch: current_epoch, + }); + } } let ciphertext = crypto::FsEncryptionCiphertext::deserialize(ciphertext) .map_err(DecryptError::MalformedCiphertext)?; - let tau = Tau::from(epoch); - crypto::dec_chunks(secret_key, index, &ciphertext, &tau.0[..], associated_data) + crypto::dec_chunks(secret_key, index, &ciphertext, epoch, associated_data) .map_err(|_| DecryptError::InvalidChunk) } @@ -377,10 +366,9 @@ pub fn verify_zk_proofs( }) })?; - let tau = Tau::from(epoch); crypto::verify_ciphertext_integrity( &ciphertext, - &tau.0[..], + epoch, associated_data, crypto::SysParam::global(), ) diff --git a/rs/crypto/internal/crypto_lib/threshold_sig/bls12_381/src/ni_dkg/groth20_bls12_381/encryption/conversions.rs b/rs/crypto/internal/crypto_lib/threshold_sig/bls12_381/src/ni_dkg/groth20_bls12_381/encryption/conversions.rs deleted file mode 100644 index a36428d61e8..00000000000 --- a/rs/crypto/internal/crypto_lib/threshold_sig/bls12_381/src/ni_dkg/groth20_bls12_381/encryption/conversions.rs +++ /dev/null @@ -1,23 +0,0 @@ -//! Type conversion for using the Miracl-based FS library. -use super::crypto; -use ic_crypto_internal_types::sign::threshold_sig::ni_dkg::Epoch; - -/// Represents a prefix of an epoch. -pub struct Tau(pub Vec); -impl From for Tau { - /// Gets the leaf node tau for a given epoch - fn from(epoch: Epoch) -> Tau { - let epoch = epoch.get(); - let num_bits = ::std::mem::size_of::() * 8; - Tau((0..num_bits) - .rev() - .map(|shift| crypto::Bit::from(((epoch >> shift) & 1) as u8)) - .collect()) - } -} - -impl From<&Tau> for Epoch { - fn from(tau: &Tau) -> Epoch { - crypto::epoch_from_tau_vec(&tau.0) - } -} diff --git a/rs/crypto/internal/crypto_lib/threshold_sig/bls12_381/src/ni_dkg/groth20_bls12_381/encryption/tests.rs b/rs/crypto/internal/crypto_lib/threshold_sig/bls12_381/src/ni_dkg/groth20_bls12_381/encryption/tests.rs index e9937152e92..fc8d02fce61 100644 --- a/rs/crypto/internal/crypto_lib/threshold_sig/bls12_381/src/ni_dkg/groth20_bls12_381/encryption/tests.rs +++ b/rs/crypto/internal/crypto_lib/threshold_sig/bls12_381/src/ni_dkg/groth20_bls12_381/encryption/tests.rs @@ -47,7 +47,9 @@ fn epoch_of_a_new_key_should_be_zero() { const KEY_GEN_ASSOCIATED_DATA: &[u8] = &[2u8, 3u8, 0u8, 6u8]; let key_set = create_forward_secure_key_pair(Seed::from_bytes(&[12u8; 32]), KEY_GEN_ASSOCIATED_DATA); - let epoch = SecretKey::deserialize(&key_set.secret_key).epoch(); + let epoch = SecretKey::deserialize(&key_set.secret_key) + .current_epoch() + .unwrap(); assert_eq!(epoch.get(), 0); } @@ -65,7 +67,7 @@ fn single_stepping_a_key_should_increment_current_epoch() { secret_key_epoch, Seed::from_bytes(&[9u8; 32]), ); - let key_epoch = secret_key.epoch().get(); + let key_epoch = secret_key.current_epoch().unwrap().get(); assert_eq!( key_epoch, epoch, "Deleted epoch {} but key epoch is {}\n", diff --git a/rs/crypto/internal/crypto_lib/threshold_sig/bls12_381/tests/epoch.rs b/rs/crypto/internal/crypto_lib/threshold_sig/bls12_381/tests/epoch.rs index 14881ed70fe..864bbf9d17b 100644 --- a/rs/crypto/internal/crypto_lib/threshold_sig/bls12_381/tests/epoch.rs +++ b/rs/crypto/internal/crypto_lib/threshold_sig/bls12_381/tests/epoch.rs @@ -18,12 +18,15 @@ proptest! { let mut rng = rand_chacha::ChaCha20Rng::from_seed(seed); let (_pk, mut sk) = kgen(&associated_data, sys, &mut rng); - assert!(!sk.is_exhausted()); + assert_eq!(sk.current_epoch(), Some(Epoch::from(0))); for i in 0..100 { - let next_epoch = tau_from_epoch(Epoch::from(i)); - sk.update_to(&next_epoch, sys, &mut rng); - assert!(!sk.is_exhausted()); + sk.update_to(Epoch::from(i), sys, &mut rng); + assert_eq!(sk.current_epoch(), Some(Epoch::from(i))); + + // no-op: + sk.update_to(Epoch::from(i), sys, &mut rng); + assert_eq!(sk.current_epoch(), Some(Epoch::from(i))); } } #[test] @@ -36,14 +39,17 @@ proptest! { let mut rng = rand_chacha::ChaCha20Rng::from_seed(seed); let (_pk, mut sk) = kgen(&associated_data, sys, &mut rng); - assert!(!sk.is_exhausted()); + assert_eq!(sk.current_epoch(), Some(Epoch::from(0))); let mut sorted_epochs : Vec= epochs; sorted_epochs.sort_unstable(); for epoch in sorted_epochs{ - let tau = tau_from_epoch(Epoch::from(epoch)); - sk.update_to(&tau, sys, &mut rng); - assert!(!sk.is_exhausted()); + sk.update_to(Epoch::from(epoch), sys, &mut rng); + assert_eq!(sk.current_epoch(), Some(Epoch::from(epoch))); + + // no-op: + sk.update_to(Epoch::from(epoch), sys, &mut rng); + assert_eq!(sk.current_epoch(), Some(Epoch::from(epoch))); } } #[test] @@ -53,26 +59,20 @@ proptest! { let mut rng = rand_chacha::ChaCha20Rng::from_seed(seed); let (_pk, mut sk) = kgen(&associated_data, sys, &mut rng); - - assert!(!sk.is_exhausted()); + assert_eq!(sk.current_epoch(), Some(Epoch::from(0))); for i in (0..100).rev() { - let next_epoch = tau_from_epoch(Epoch::from(MAXIMUM_EPOCH - i)); - sk.update_to(&next_epoch, sys, &mut rng); - assert!(!sk.is_exhausted()); + let next_epoch = Epoch::from(MAXIMUM_EPOCH - i); + sk.update_to(next_epoch, sys, &mut rng); + assert_eq!(sk.current_epoch(), Some(next_epoch)); + + // no-op: + sk.update_to(next_epoch, sys, &mut rng); + assert_eq!(sk.current_epoch(), Some(next_epoch)); } // The key should be at the last epoch, the next update should erase the secret key. sk.update(sys, &mut rng); - assert!(sk.is_exhausted()); - } + assert_eq!(sk.current_epoch(), None); -} - -proptest! { - #[test] - fn should_convert_tau_to_epoch(epoch: u32) { - let epoch = Epoch::from(epoch); - let tau = tau_from_epoch(epoch); - - assert_eq!(epoch, epoch_from_tau_vec(&tau)); } + } diff --git a/rs/crypto/internal/crypto_lib/threshold_sig/bls12_381/tests/forward_secure.rs b/rs/crypto/internal/crypto_lib/threshold_sig/bls12_381/tests/forward_secure.rs index 46f41697403..25a7acec351 100644 --- a/rs/crypto/internal/crypto_lib/threshold_sig/bls12_381/tests/forward_secure.rs +++ b/rs/crypto/internal/crypto_lib/threshold_sig/bls12_381/tests/forward_secure.rs @@ -88,8 +88,7 @@ fn keys_and_ciphertext_for( let pks_and_scalars = pks.iter().cloned().zip(ptext_chunks).collect::>(); - let tau = tau_from_epoch(epoch); - let (crsz, _witness) = enc_chunks(&pks_and_scalars, &tau, associated_data, sys, rng); + let (crsz, _witness) = enc_chunks(&pks_and_scalars, epoch, associated_data, sys, rng); (keys, ptext, crsz) } @@ -107,9 +106,8 @@ fn integrity_check_should_return_error_on_wrong_associated_data() { }; let (_keys, _message, crsz) = keys_and_ciphertext_for(epoch, &associated_data, &mut rng); - let tau = tau_from_epoch(epoch); - assert!(verify_ciphertext_integrity(&crsz, &tau, &wrong_associated_data, sys).is_err()); + assert!(verify_ciphertext_integrity(&crsz, epoch, &wrong_associated_data, sys).is_err()); } #[test] @@ -120,11 +118,10 @@ fn should_encrypt_with_empty_associated_data() { let associated_data = []; let (keys, message, crsz) = keys_and_ciphertext_for(epoch, &associated_data, &mut rng); - let tau = tau_from_epoch(epoch); - assert!(verify_ciphertext_integrity(&crsz, &tau, &associated_data, sys).is_ok()); + assert!(verify_ciphertext_integrity(&crsz, epoch, &associated_data, sys).is_ok()); for i in 0..keys.len() { - let out = dec_chunks(&keys[i].1, i, &crsz, &tau, &associated_data); + let out = dec_chunks(&keys[i].1, i, &crsz, epoch, &associated_data); assert_eq!(out.unwrap(), message[i], "Message decrypted wrongly"); } } @@ -188,15 +185,14 @@ fn should_decrypt_correctly_for_cheating_dealer() { .zip(cheating_chunks.iter().cloned()) .collect::>(); - let tau = tau_from_epoch(epoch); - let (crsz, _witness) = enc_chunks(&pks_and_chunks, &tau, &associated_data, sys, &mut rng); + let (crsz, _witness) = enc_chunks(&pks_and_chunks, epoch, &associated_data, sys, &mut rng); // still a valid ciphertext - assert!(verify_ciphertext_integrity(&crsz, &tau, &associated_data, sys).is_ok()); + assert!(verify_ciphertext_integrity(&crsz, epoch, &associated_data, sys).is_ok()); for i in 0..keys.len() { let secret_key = &keys[i].1; - let out = dec_chunks(secret_key, i, &crsz, &tau, &associated_data); + let out = dec_chunks(secret_key, i, &crsz, epoch, &associated_data); assert_eq!( out.unwrap(), cheating_chunks[i].recombine_to_scalar(), @@ -213,12 +209,11 @@ fn should_decrypt_correctly_for_epoch_0() { let associated_data = rng.gen::<[u8; 10]>(); let (keys, message, crsz) = keys_and_ciphertext_for(epoch, &associated_data, &mut rng); - let tau = tau_from_epoch(epoch); - assert!(verify_ciphertext_integrity(&crsz, &tau, &associated_data, sys).is_ok()); + assert!(verify_ciphertext_integrity(&crsz, epoch, &associated_data, sys).is_ok()); for i in 0..keys.len() { let secret_key = &keys[i].1; - let out = dec_chunks(secret_key, i, &crsz, &tau, &associated_data); + let out = dec_chunks(secret_key, i, &crsz, epoch, &associated_data); assert_eq!(out.unwrap(), message[i], "Message decrypted wrongly"); } } @@ -231,12 +226,11 @@ fn should_decrypt_correctly_for_epoch_1() { let associated_data = rng.gen::<[u8; 10]>(); let (keys, message, crsz) = keys_and_ciphertext_for(epoch, &associated_data, &mut rng); - let tau = tau_from_epoch(epoch); - assert!(verify_ciphertext_integrity(&crsz, &tau, &associated_data, sys).is_ok()); + assert!(verify_ciphertext_integrity(&crsz, epoch, &associated_data, sys).is_ok()); for i in 0..keys.len() { let secret_key = &keys[i].1; - let out = dec_chunks(secret_key, i, &crsz, &tau, &associated_data); + let out = dec_chunks(secret_key, i, &crsz, epoch, &associated_data); assert_eq!(out.unwrap(), message[i], "Message decrypted wrongly"); } } @@ -248,12 +242,11 @@ fn should_decrypt_correctly_for_epoch_5() { let associated_data = rng.gen::<[u8; 10]>(); let (keys, message, crsz) = keys_and_ciphertext_for(epoch, &associated_data, &mut rng); - let tau = tau_from_epoch(epoch); - assert!(verify_ciphertext_integrity(&crsz, &tau, &associated_data, sys).is_ok()); + assert!(verify_ciphertext_integrity(&crsz, epoch, &associated_data, sys).is_ok()); for i in 0..keys.len() { let secret_key = &keys[i].1; - let out = dec_chunks(secret_key, i, &crsz, &tau, &associated_data); + let out = dec_chunks(secret_key, i, &crsz, epoch, &associated_data); assert_eq!(out.unwrap(), message[i], "Message decrypted wrongly"); } } @@ -265,12 +258,11 @@ fn should_decrypt_correctly_for_epoch_10() { let associated_data = rng.gen::<[u8; 10]>(); let (keys, message, crsz) = keys_and_ciphertext_for(epoch, &associated_data, &mut rng); - let tau = tau_from_epoch(epoch); - assert!(verify_ciphertext_integrity(&crsz, &tau, &associated_data, sys).is_ok()); + assert!(verify_ciphertext_integrity(&crsz, epoch, &associated_data, sys).is_ok()); for i in 0..keys.len() { let secret_key = &keys[i].1; - let out = dec_chunks(secret_key, i, &crsz, &tau, &associated_data); + let out = dec_chunks(secret_key, i, &crsz, epoch, &associated_data); assert_eq!(out.unwrap(), message[i], "Message decrypted wrongly"); } } diff --git a/rs/crypto/internal/crypto_lib/threshold_sig/bls12_381/tests/integration_tests.rs b/rs/crypto/internal/crypto_lib/threshold_sig/bls12_381/tests/integration_tests.rs index bae4111f7c3..3a46762aa66 100644 --- a/rs/crypto/internal/crypto_lib/threshold_sig/bls12_381/tests/integration_tests.rs +++ b/rs/crypto/internal/crypto_lib/threshold_sig/bls12_381/tests/integration_tests.rs @@ -27,7 +27,6 @@ fn potpourri() { dk.update(sys, &mut rng); } let epoch10 = Epoch::from(10); - let tau10 = tau_from_epoch(epoch10); let associated_data = rng.gen::<[u8; 32]>(); @@ -54,7 +53,7 @@ fn potpourri() { .zip(ptext_chunks.iter().cloned()) .collect::>(); - let (crsz, _toxic) = enc_chunks(&pk_and_chunks, &tau10, &associated_data, sys, &mut rng); + let (crsz, _toxic) = enc_chunks(&pk_and_chunks, epoch10, &associated_data, sys, &mut rng); let dk = &mut keys[1].1; for _i in 0..3 { @@ -62,10 +61,10 @@ fn potpourri() { dk.update(sys, &mut rng); } - verify_ciphertext_integrity(&crsz, &tau10, &associated_data, sys) + verify_ciphertext_integrity(&crsz, epoch10, &associated_data, sys) .expect("ciphertext integrity check failed"); - let out = dec_chunks(dk, 1, &crsz, &tau10, &associated_data) + let out = dec_chunks(dk, 1, &crsz, epoch10, &associated_data) .expect("It should be possible to decrypt"); assert_eq!(out, ptext[1]); @@ -75,7 +74,7 @@ fn potpourri() { dk.update(sys, &mut rng); } // Should be impossible to decrypt now. - let out = dec_chunks(dk, 1, &crsz, &tau10, &associated_data); + let out = dec_chunks(dk, 1, &crsz, epoch10, &associated_data); match out { Err(DecErr::ExpiredKey) => (), _ => panic!("old ciphertexts should be lost forever"), @@ -148,14 +147,13 @@ fn encrypted_chunks_should_validate(epoch: Epoch) { .collect::>(); // Encrypt - let tau = tau_from_epoch(epoch); let associated_data = rng.gen::<[u8; 10]>(); let (crsz, encryption_witness) = - enc_chunks(&keys_and_chunks, &tau, &associated_data, sys, &mut rng); + enc_chunks(&keys_and_chunks, epoch, &associated_data, sys, &mut rng); // Check that decryption succeeds let dk = &receiver_fs_keys[1].1; - let out = dec_chunks(dk, 1, &crsz, &tau, &associated_data); + let out = dec_chunks(dk, 1, &crsz, epoch, &associated_data); println!("decrypted: {:?}", out); assert_eq!(out.unwrap(), plaintext_chunks[1].recombine_to_scalar(),);