use crate::core_crypto::algorithms::{
encrypt_lwe_compact_ciphertext_list_with_compact_public_key, keyswitch_lwe_ciphertext,
lwe_ciphertext_add_assign,
};
use crate::core_crypto::commons::generators::NoiseRandomGenerator;
use crate::core_crypto::commons::math::random::{DefaultRandomGenerator, XofSeed};
use crate::core_crypto::commons::parameters::{LweCiphertextCount, PlaintextCount};
use crate::core_crypto::commons::traits::*;
use crate::core_crypto::entities::{LweCiphertext, LweCompactCiphertextList, PlaintextList};
use crate::shortint::ciphertext::NoiseLevel;
use crate::shortint::key_switching_key::KeySwitchingKeyMaterialView;
use crate::shortint::{Ciphertext, CompactPublicKey, PBSOrder};
use rayon::prelude::*;
use sha3::digest::{ExtendableOutput, Update};
use std::io::Read;
const RERAND_SEED_BITS: usize = 256;
#[derive(Copy, Clone, Default)]
pub enum ReRandomizationHashAlgo {
Shake256,
#[default]
Blake3,
}
#[derive(Clone)]
#[allow(clippy::large_enum_variant)]
pub enum ReRandomizationSeedHasher {
Shake256(sha3::Shake256),
Blake3(blake3::Hasher),
}
impl ReRandomizationSeedHasher {
pub fn new(
algo: ReRandomizationHashAlgo,
rerand_root_seed_domain_separator: [u8; XofSeed::DOMAIN_SEP_LEN],
) -> Self {
let mut hasher = match algo {
ReRandomizationHashAlgo::Shake256 => Self::Shake256(sha3::Shake256::default()),
ReRandomizationHashAlgo::Blake3 => Self::Blake3(blake3::Hasher::default()),
};
hasher.update(&rerand_root_seed_domain_separator);
hasher
}
fn update(&mut self, data: &[u8]) {
match self {
Self::Shake256(hasher) => hasher.update(data),
Self::Blake3(hasher) => {
hasher.update(data);
}
}
}
fn finalize(self) -> [u8; RERAND_SEED_BITS / 8] {
let mut res = [0; RERAND_SEED_BITS / 8];
match self {
Self::Shake256(hasher) => {
let mut reader = hasher.finalize_xof();
reader
.read_exact(&mut res)
.expect("XoF reader should not EoF");
}
Self::Blake3(hasher) => {
let mut reader = hasher.finalize_xof();
reader
.read_exact(&mut res)
.expect("XoF reader should not EoF");
}
}
res
}
}
impl From<sha3::Shake256> for ReRandomizationSeedHasher {
fn from(value: sha3::Shake256) -> Self {
Self::Shake256(value)
}
}
impl From<blake3::Hasher> for ReRandomizationSeedHasher {
fn from(value: blake3::Hasher) -> Self {
Self::Blake3(value)
}
}
pub struct ReRandomizationSeed(pub(crate) XofSeed);
pub struct ReRandomizationContext {
hash_state: ReRandomizationSeedHasher,
public_encryption_domain_separator: [u8; XofSeed::DOMAIN_SEP_LEN],
}
impl ReRandomizationContext {
pub fn new(
rerand_seeder_domain_separator: [u8; XofSeed::DOMAIN_SEP_LEN],
public_encryption_domain_separator: [u8; XofSeed::DOMAIN_SEP_LEN],
) -> Self {
let seed_hasher = ReRandomizationSeedHasher::new(
ReRandomizationHashAlgo::default(),
rerand_seeder_domain_separator,
);
Self::new_with_hasher(public_encryption_domain_separator, seed_hasher)
}
pub fn new_with_hasher(
public_encryption_domain_separator: [u8; XofSeed::DOMAIN_SEP_LEN],
seed_hasher: ReRandomizationSeedHasher,
) -> Self {
Self {
hash_state: seed_hasher,
public_encryption_domain_separator,
}
}
pub fn add_ciphertext(&mut self, ciphertext: &Ciphertext) {
self.add_ciphertext_iterator([ciphertext]);
}
pub fn add_bytes(&mut self, data: &[u8]) {
self.hash_state.update(data);
}
pub fn add_ciphertext_iterator<'a, I>(&mut self, iter: I)
where
I: IntoIterator<Item = &'a Ciphertext>,
{
let mut iter = iter.into_iter();
let Some(first) = iter.next() else {
return;
};
let hint = iter.size_hint();
let iter_len = hint.1.unwrap_or(hint.0);
let tot_len = first.ct.as_ref().len() * iter_len;
let mut copied: Vec<u64> = Vec::with_capacity(tot_len);
copied.extend(first.ct.as_ref());
for ciphertext in iter {
copied.extend(ciphertext.ct.as_ref());
}
self.add_ciphertext_data_slice(&copied);
}
pub(crate) fn add_ciphertext_data_slice(&mut self, slice: &[u64]) {
self.hash_state.update(bytemuck::cast_slice(slice));
}
pub fn finalize(self) -> ReRandomizationSeedGen {
let Self {
hash_state,
public_encryption_domain_separator,
} = self;
ReRandomizationSeedGen {
hash_state,
next_seed_index: 0,
public_encryption_domain_separator,
}
}
}
pub struct ReRandomizationSeedGen {
hash_state: ReRandomizationSeedHasher,
next_seed_index: u64,
public_encryption_domain_separator: [u8; XofSeed::DOMAIN_SEP_LEN],
}
impl ReRandomizationSeedGen {
pub fn next_seed(&mut self) -> ReRandomizationSeed {
let current_seed_index = self.next_seed_index;
self.next_seed_index += 1;
let mut hash_state = self.hash_state.clone();
hash_state.update(¤t_seed_index.to_le_bytes());
let seed_256 = hash_state.finalize();
ReRandomizationSeed(XofSeed::new(
seed_256.to_vec(),
self.public_encryption_domain_separator,
))
}
}
impl CompactPublicKey {
pub(crate) fn prepare_cpk_zero_for_rerand(
&self,
seed: ReRandomizationSeed,
zero_count: LweCiphertextCount,
) -> LweCompactCiphertextList<Vec<u64>> {
let mut encryption_generator =
NoiseRandomGenerator::<DefaultRandomGenerator>::new_from_seed(seed.0);
let mut encryption_of_zero = LweCompactCiphertextList::new(
0,
self.parameters().encryption_lwe_dimension.to_lwe_size(),
zero_count,
self.parameters().ciphertext_modulus,
);
let plaintext_list = PlaintextList::new(
0,
PlaintextCount(encryption_of_zero.lwe_ciphertext_count().0),
);
let cpk_encryption_noise_distribution = self.parameters().encryption_noise_distribution;
encrypt_lwe_compact_ciphertext_list_with_compact_public_key(
&self.key,
&mut encryption_of_zero,
&plaintext_list,
cpk_encryption_noise_distribution,
cpk_encryption_noise_distribution,
&mut encryption_generator,
);
encryption_of_zero
}
pub fn re_randomize_ciphertexts(
&self,
cts: &mut [Ciphertext],
key_switching_key_material: &KeySwitchingKeyMaterialView,
seed: ReRandomizationSeed,
) -> crate::Result<()> {
let ksk_pbs_order = key_switching_key_material.destination_key.into_pbs_order();
let ksk_output_lwe_size = key_switching_key_material
.key_switching_key
.output_lwe_size();
if let Some(msg) = cts.iter().find_map(|ct| {
if ct.atomic_pattern.pbs_order() != ksk_pbs_order {
Some(
"Mismatched PBSOrder between Ciphertext being re-randomized and provided \
KeySwitchingKeyMaterialView.",
)
} else if ksk_output_lwe_size != ct.ct.lwe_size() {
Some(
"Mismatched LweSwize between Ciphertext being re-randomized and provided \
KeySwitchingKeyMaterialView.",
)
} else if ct.noise_level() > NoiseLevel::NOMINAL {
Some("Tried to re-randomize a Ciphertext with non-nominal NoiseLevel.")
} else {
None
}
}) {
return Err(crate::error!("{}", msg));
}
if ksk_pbs_order != PBSOrder::KeyswitchBootstrap {
return Err(crate::error!(
"Tried to re-randomize a Ciphertext with unsupported PBSOrder. \
Required PBSOrder::KeyswitchBootstrap.",
));
}
if key_switching_key_material.cast_rshift != 0 {
return Err(crate::error!(
"Tried to re-randomize a Ciphertext using KeySwitchingKeyMaterialView \
with non-zero cast_rshift, this is unsupported.",
));
}
if key_switching_key_material
.key_switching_key
.input_key_lwe_dimension()
!= self.parameters().encryption_lwe_dimension
{
return Err(crate::error!(
"Mismatched LweDimension between provided CompactPublicKey and \
KeySwitchingKeyMaterialView input LweDimension.",
));
}
let encryption_of_zero =
self.prepare_cpk_zero_for_rerand(seed, LweCiphertextCount(cts.len()));
let zero_lwes = encryption_of_zero.expand_into_lwe_ciphertext_list();
cts.par_iter_mut()
.zip(zero_lwes.par_iter())
.for_each(|(ct, lwe_randomizer_cpk)| {
let mut lwe_randomizer_ksed = LweCiphertext::new(
0,
key_switching_key_material
.key_switching_key
.output_lwe_size(),
key_switching_key_material
.key_switching_key
.ciphertext_modulus(),
);
keyswitch_lwe_ciphertext(
key_switching_key_material.key_switching_key,
&lwe_randomizer_cpk,
&mut lwe_randomizer_ksed,
);
lwe_ciphertext_add_assign(&mut ct.ct, &lwe_randomizer_ksed);
ct.set_noise_level_to_nominal();
});
Ok(())
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::shortint::parameters::test_params::{
TEST_PARAM_KEYSWITCH_PKE_TO_BIG_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M128,
TEST_PARAM_PKE_TO_SMALL_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M128_ZKV2,
};
use crate::shortint::parameters::PARAM_MESSAGE_2_CARRY_2_KS_PBS;
use crate::shortint::{gen_keys, CompactPrivateKey, KeySwitchingKey};
#[test]
fn test_rerand_ci_run_filter() {
let compute_params = PARAM_MESSAGE_2_CARRY_2_KS_PBS;
let pke_params = TEST_PARAM_PKE_TO_SMALL_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M128_ZKV2;
let ks_params = TEST_PARAM_KEYSWITCH_PKE_TO_BIG_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M128;
let (cks, sks) = gen_keys(compute_params);
let privk = CompactPrivateKey::new(pke_params);
let pubk = CompactPublicKey::new(&privk);
let ksk = KeySwitchingKey::new((&privk, None), (&cks, &sks), ks_params);
let pke_lwe_dim = pke_params.encryption_lwe_dimension.0;
let msg1 = 1;
let msg2 = 2;
{
let mut cts = Vec::with_capacity(pke_lwe_dim * 2);
for _ in 0..pke_lwe_dim {
let ct1 = cks.encrypt(msg1);
cts.push(ct1);
let ct2 = cks.encrypt(msg2);
cts.push(ct2);
}
let nonce: [u8; 256 / 8] = core::array::from_fn(|_| rand::random());
let mut re_rand_context = ReRandomizationContext::new(*b"TFHE_Rrd", *b"TFHE_Enc");
re_rand_context.add_ciphertext_iterator(&cts);
re_rand_context.add_bytes(b"ct_radix");
re_rand_context.add_bytes(b"FheUint4".as_slice());
re_rand_context.add_bytes(&nonce);
let mut seeder = re_rand_context.finalize();
pubk.re_randomize_ciphertexts(
&mut cts,
&ksk.key_switching_key_material.as_view(),
seeder.next_seed(),
)
.unwrap();
cts.par_chunks(2).for_each(|pair| {
let sum = sks.add(&pair[0], &pair[1]);
let dec = cks.decrypt(&sum);
assert_eq!(dec, msg1 + msg2);
});
}
{
let mut trivial = sks.create_trivial(3);
let nonce: [u8; 256 / 8] = core::array::from_fn(|_| rand::random());
let mut re_rand_context = ReRandomizationContext::new(*b"TFHE_Rrd", *b"TFHE_Enc");
re_rand_context.add_ciphertext(&trivial);
re_rand_context.add_bytes(&nonce);
re_rand_context.add_bytes(b"trivial");
let mut seeder = re_rand_context.finalize();
pubk.re_randomize_ciphertexts(
core::slice::from_mut(&mut trivial),
&ksk.key_switching_key_material.as_view(),
seeder.next_seed(),
)
.unwrap();
let not_trivial = trivial;
assert!(not_trivial.noise_level() == NoiseLevel::NOMINAL);
let dec = cks.decrypt(¬_trivial);
assert_eq!(dec, 3);
}
}
}