feat(integer): add noise squashing to integer

This commit is contained in:
Arthur Meyre
2025-03-24 13:45:34 +01:00
parent 8e30d5e538
commit 21abcbdf4c
12 changed files with 585 additions and 2 deletions

View File

@@ -6,7 +6,8 @@ use crate::integer::ciphertext::{
BaseCrtCiphertext, BaseRadixCiphertext, BaseSignedRadixCiphertext, CompactCiphertextList,
CompressedCiphertextList, CompressedModulusSwitchedRadixCiphertext,
CompressedModulusSwitchedRadixCiphertextGeneric,
CompressedModulusSwitchedSignedRadixCiphertext, DataKind,
CompressedModulusSwitchedSignedRadixCiphertext, DataKind, SquashedNoiseBooleanBlock,
SquashedNoiseRadixCiphertext, SquashedNoiseSignedRadixCiphertext,
};
use crate::integer::BooleanBlock;
#[cfg(feature = "zk-pok")]
@@ -99,3 +100,18 @@ pub type CompressedModulusSwitchedRadixCiphertextTFHE06 =
pub enum CompressedCiphertextListVersions {
V0(CompressedCiphertextList),
}
#[derive(VersionsDispatch)]
pub enum SquashedNoiseRadixCiphertextVersions {
V0(SquashedNoiseRadixCiphertext),
}
#[derive(VersionsDispatch)]
pub enum SquashedNoiseSignedRadixCiphertextVersions {
V0(SquashedNoiseSignedRadixCiphertext),
}
#[derive(VersionsDispatch)]
pub enum SquashedNoiseBooleanBlockVersions {
V0(SquashedNoiseBooleanBlock),
}

View File

@@ -4,5 +4,6 @@ pub mod ciphertext;
pub mod client_key;
pub mod key_switching_key;
pub mod list_compression;
pub mod noise_squashing;
pub mod public_key;
pub mod server_key;

View File

@@ -0,0 +1,19 @@
use crate::integer::noise_squashing::{
CompressedNoiseSquashingKey, NoiseSquashingKey, NoiseSquashingPrivateKey,
};
use tfhe_versionable::VersionsDispatch;
#[derive(VersionsDispatch)]
pub enum NoiseSquashingKeyVersions {
V0(NoiseSquashingKey),
}
#[derive(VersionsDispatch)]
pub enum CompressedNoiseSquashingKeyVersions {
V0(CompressedNoiseSquashingKey),
}
#[derive(VersionsDispatch)]
pub enum NoiseSquashingPrivateKeyVersions {
V0(NoiseSquashingPrivateKey),
}

View File

@@ -460,6 +460,23 @@ impl<const N: usize> CastFrom<u64> for StaticSignedBigInt<N> {
}
}
impl<const N: usize> CastFrom<u128> for StaticSignedBigInt<N> {
fn cast_from(input: u128) -> Self {
let mut converted = [u64::ZERO; N];
if N == 0 {
return Self(converted);
}
converted[0] = input as u64;
if N == 1 {
return Self(converted);
}
converted[1] = (input >> 64) as u64;
Self(converted)
}
}
impl<const N: usize> CastFrom<StaticSignedBigInt<N>> for u8 {
fn cast_from(input: StaticSignedBigInt<N>) -> Self {
input.0[0] as Self
@@ -484,6 +501,13 @@ impl<const N: usize> CastFrom<StaticSignedBigInt<N>> for u64 {
}
}
impl<const N: usize> CastFrom<StaticSignedBigInt<N>> for u128 {
fn cast_from(input: StaticSignedBigInt<N>) -> Self {
let inner = &input.0;
inner[0] as Self | ((inner[1] as Self) << 64)
}
}
impl<const N: usize> CastFrom<super::static_unsigned::StaticUnsignedBigInt<N>>
for StaticSignedBigInt<N>
{

View File

@@ -52,6 +52,8 @@ macro_rules! impl_recomposable_decomposable {
self.wrapping_add(other)
}
}
impl RecomposableFrom<u128> for $type { }
impl DecomposableInto<u128> for $type { }
impl RecomposableFrom<u64> for $type { }
impl DecomposableInto<u64> for $type { }
impl RecomposableFrom<u8> for $type { }
@@ -70,8 +72,10 @@ impl<const N: usize> Recomposable for StaticSignedBigInt<N> {
self
}
}
impl<const N: usize> RecomposableFrom<u128> for StaticSignedBigInt<N> {}
impl<const N: usize> RecomposableFrom<u64> for StaticSignedBigInt<N> {}
impl<const N: usize> RecomposableFrom<u8> for StaticSignedBigInt<N> {}
impl<const N: usize> DecomposableInto<u128> for StaticSignedBigInt<N> {}
impl<const N: usize> DecomposableInto<u64> for StaticSignedBigInt<N> {}
impl<const N: usize> DecomposableInto<u8> for StaticSignedBigInt<N> {}
@@ -83,8 +87,10 @@ impl<const N: usize> Recomposable for StaticUnsignedBigInt<N> {
self
}
}
impl<const N: usize> RecomposableFrom<u128> for StaticUnsignedBigInt<N> {}
impl<const N: usize> RecomposableFrom<u64> for StaticUnsignedBigInt<N> {}
impl<const N: usize> RecomposableFrom<u8> for StaticUnsignedBigInt<N> {}
impl<const N: usize> DecomposableInto<u128> for StaticUnsignedBigInt<N> {}
impl<const N: usize> DecomposableInto<u64> for StaticUnsignedBigInt<N> {}
impl<const N: usize> DecomposableInto<u8> for StaticUnsignedBigInt<N> {}
@@ -95,7 +101,6 @@ pub trait RecomposableSignedInteger:
+ std::ops::BitOrAssign<Self>
+ std::ops::BitOr<Self, Output = Self>
+ std::ops::Mul<Self, Output = Self>
+ CastFrom<Self>
+ SignedNumeric
{
}
@@ -370,6 +375,10 @@ where
true
}
/// Recompose an unsigned integer, assumes all limbs from input contribute `bits_in_block` bits
/// to the final result.
///
/// Input is expected in little endian order.
pub(crate) fn recompose_unsigned<U>(input: impl Iterator<Item = U>, bits_in_block: u32) -> T
where
T: RecomposableFrom<U>,
@@ -384,6 +393,10 @@ where
recomposer.value()
}
/// Recompose a signed integer, assumes all limbs from input contribute `bits_in_block` bits
/// to the final result.
///
/// Input is expected in little endian order.
pub(crate) fn recompose_signed<U>(input: impl Iterator<Item = U>, bits_in_block: u32) -> T
where
T: RecomposableFrom<U> + SignExtendable,
@@ -397,6 +410,33 @@ where
sign_extend_partial_number(recomposer.value(), recomposer.bit_pos)
}
/// Recompose a signed integer, all limbs from input are added as if contributing
/// `bits_in_block` bits to the result, `signed_integer_size` indicates which of the low bits
/// are actually considered as being part of the result, this is used to decide which bit
/// represents the sign.
///
/// For example with 2 limbs of 4 bits, if `signed_integer_size` is 6, then the 2 top bits from
/// the last limb are ignored.
///
/// Input is expected in little endian order.
pub(crate) fn recompose_signed_with_size<U>(
input: impl Iterator<Item = U>,
bits_in_block: u32,
signed_integer_size: u32,
) -> T
where
T: RecomposableFrom<U> + SignExtendable,
{
let mut recomposer = Self::new(bits_in_block);
for limb in input {
if !recomposer.add_unmasked(limb) {
break;
}
}
sign_extend_partial_number(recomposer.value(), signed_integer_size)
}
}
#[cfg(test)]

View File

@@ -5,6 +5,7 @@ mod compressed;
mod compressed_ciphertext_list;
mod compressed_modulus_switched_ciphertext;
mod integer_ciphertext;
mod squashed_noise;
mod utils;
pub use base::*;
@@ -14,4 +15,5 @@ pub use compressed::*;
pub use compressed_ciphertext_list::*;
pub use compressed_modulus_switched_ciphertext::*;
pub use integer_ciphertext::*;
pub use squashed_noise::*;
pub use utils::*;

View File

@@ -0,0 +1,27 @@
use crate::integer::backward_compatibility::ciphertext::{
SquashedNoiseBooleanBlockVersions, SquashedNoiseRadixCiphertextVersions,
SquashedNoiseSignedRadixCiphertextVersions,
};
use crate::shortint::ciphertext::SquashedNoiseCiphertext;
use serde::{Deserialize, Serialize};
use tfhe_versionable::Versionize;
#[derive(Serialize, Clone, Deserialize, PartialEq, Eq, Debug, Versionize)]
#[versionize(SquashedNoiseRadixCiphertextVersions)]
pub struct SquashedNoiseRadixCiphertext {
pub(crate) packed_blocks: Vec<SquashedNoiseCiphertext>,
pub(crate) original_block_count: usize,
}
#[derive(Serialize, Clone, Deserialize, PartialEq, Eq, Debug, Versionize)]
#[versionize(SquashedNoiseSignedRadixCiphertextVersions)]
pub struct SquashedNoiseSignedRadixCiphertext {
pub(crate) packed_blocks: Vec<SquashedNoiseCiphertext>,
pub(crate) original_block_count: usize,
}
#[derive(Serialize, Clone, Deserialize, PartialEq, Eq, Debug, Versionize)]
#[versionize(SquashedNoiseBooleanBlockVersions)]
pub struct SquashedNoiseBooleanBlock {
pub(crate) ciphertext: SquashedNoiseCiphertext,
}

View File

@@ -61,6 +61,7 @@ pub mod compression_keys;
pub mod key_switching_key;
#[cfg(any(test, feature = "internal-keycache"))]
pub mod keycache;
pub mod noise_squashing;
pub mod oprf;
pub mod parameters;
pub mod prelude;

View File

@@ -0,0 +1,353 @@
use crate::conformance::ParameterSetConformant;
use crate::core_crypto::commons::numeric::UnsignedNumeric;
use crate::integer::backward_compatibility::noise_squashing::*;
use crate::integer::block_decomposition::{BlockRecomposer, RecomposableFrom, SignExtendable};
use crate::integer::ciphertext::{
BooleanBlock, RadixCiphertext, SignedRadixCiphertext, SquashedNoiseBooleanBlock,
SquashedNoiseRadixCiphertext, SquashedNoiseSignedRadixCiphertext,
};
use crate::integer::server_key::ServerKey;
use crate::integer::ClientKey;
use crate::named::Named;
use crate::shortint::noise_squashing::NoiseSquashingKeyConformanceParams;
use crate::shortint::parameters::NoiseSquashingParameters;
use rayon::prelude::*;
use serde::{Deserialize, Serialize};
use tfhe_versionable::Versionize;
#[derive(Clone, Debug, Serialize, Deserialize, Versionize)]
#[versionize(NoiseSquashingPrivateKeyVersions)]
pub struct NoiseSquashingPrivateKey {
pub(crate) key: crate::shortint::noise_squashing::NoiseSquashingPrivateKey,
}
#[derive(Clone, Debug, Serialize, Deserialize, Versionize)]
#[versionize(NoiseSquashingKeyVersions)]
pub struct NoiseSquashingKey {
pub(crate) key: crate::shortint::noise_squashing::NoiseSquashingKey,
}
#[derive(Clone, Debug, Serialize, Deserialize, Versionize)]
#[versionize(CompressedNoiseSquashingKeyVersions)]
pub struct CompressedNoiseSquashingKey {
pub(crate) key: crate::shortint::noise_squashing::CompressedNoiseSquashingKey,
}
impl Named for NoiseSquashingPrivateKey {
const NAME: &'static str = "integer::NoiseSquashingPrivateKey";
}
impl Named for NoiseSquashingKey {
const NAME: &'static str = "integer::NoiseSquashingKey";
}
impl Named for CompressedNoiseSquashingKey {
const NAME: &'static str = "integer::CompressedNoiseSquashingKey";
}
impl CompressedNoiseSquashingKey {
pub fn decompress(&self) -> NoiseSquashingKey {
NoiseSquashingKey {
key: self.key.decompress(),
}
}
pub fn into_raw_parts(self) -> crate::shortint::noise_squashing::CompressedNoiseSquashingKey {
let Self { key } = self;
key
}
pub fn from_raw_parts(
key: crate::shortint::noise_squashing::CompressedNoiseSquashingKey,
) -> Self {
Self { key }
}
}
impl NoiseSquashingPrivateKey {
pub fn new_compressed_noise_squashing_key(
&self,
client_key: &ClientKey,
) -> CompressedNoiseSquashingKey {
client_key.new_compressed_noise_squashing_key(self)
}
pub fn new(params: NoiseSquashingParameters) -> Self {
Self {
key: crate::shortint::noise_squashing::NoiseSquashingPrivateKey::new(params),
}
}
pub fn decrypt_radix<T>(&self, ct: &SquashedNoiseRadixCiphertext) -> crate::Result<T>
where
T: RecomposableFrom<u128> + UnsignedNumeric,
{
let SquashedNoiseRadixCiphertext {
packed_blocks,
original_block_count: _,
} = ct;
if packed_blocks.is_empty() {
return Ok(T::ZERO);
}
let packed_blocks_msg_mod = packed_blocks[0].message_modulus();
let packed_blocks_carry_mod = packed_blocks[0].carry_modulus();
if !packed_blocks.iter().all(|block| {
block.message_modulus() == packed_blocks_msg_mod
&& block.carry_modulus() == packed_blocks_carry_mod
}) {
return Err(crate::error!(
"Inconsistent message and carry modules in provided SquashedNoiseRadixCiphertext."
));
}
let key_msg_mod = self.key.noise_squashing_parameters().message_modulus;
let key_carry_mod = self.key.noise_squashing_parameters().carry_modulus;
if packed_blocks_msg_mod != key_msg_mod || packed_blocks_carry_mod != key_carry_mod {
return Err(crate::error!(
"Input SquashedNoiseRadixCiphertext has incompatible \
message modulus {packed_blocks_msg_mod:?} or \
carry modulus {packed_blocks_carry_mod:?} with the current \
NoiseSquashingPrivateKey message modulus {key_msg_mod:?}, \
carry modulus {key_carry_mod:?}."
));
}
let bits_in_packed_block = (packed_blocks_msg_mod.0 * packed_blocks_carry_mod.0).ilog2();
let decrypted_packed_block_iter = packed_blocks
.iter()
.map(|block| self.key.decrypt_squashed_noise_ciphertext(block));
Ok(BlockRecomposer::recompose_unsigned(
decrypted_packed_block_iter,
bits_in_packed_block,
))
}
pub fn decrypt_signed_radix<T>(
&self,
ct: &SquashedNoiseSignedRadixCiphertext,
) -> crate::Result<T>
where
T: RecomposableFrom<u128> + SignExtendable,
{
let SquashedNoiseSignedRadixCiphertext {
packed_blocks,
original_block_count,
} = ct;
if packed_blocks.is_empty() {
return Ok(T::ZERO);
}
let packed_blocks_msg_mod = packed_blocks[0].message_modulus();
let packed_blocks_carry_mod = packed_blocks[0].carry_modulus();
if !packed_blocks.iter().all(|block| {
block.message_modulus() == packed_blocks_msg_mod
&& block.carry_modulus() == packed_blocks_carry_mod
}) {
return Err(crate::error!(
"Inconsistent message and carry modules in provided \
SquashedNoiseSignedRadixCiphertext"
));
}
let key_msg_mod = self.key.noise_squashing_parameters().message_modulus;
let key_carry_mod = self.key.noise_squashing_parameters().carry_modulus;
if packed_blocks_msg_mod != key_msg_mod || packed_blocks_carry_mod != key_carry_mod {
return Err(crate::error!(
"Input SquashedNoiseSignedRadixCiphertext has incompatible \
message modulus {packed_blocks_msg_mod:?} or \
carry modulus {packed_blocks_carry_mod:?} with the current \
NoiseSquashingPrivateKey message modulus {key_msg_mod:?}, \
carry modulus {key_carry_mod:?}."
));
}
let bits_in_packed_block = (packed_blocks_msg_mod.0 * packed_blocks_carry_mod.0).ilog2();
// Packed block has a message modulus with 2x the number of bits vs. the original
let bits_in_original_block = bits_in_packed_block / 2;
let original_block_count = *original_block_count as u32;
let original_bit_size = bits_in_original_block * original_block_count;
let decrypted_packed_block_iter = packed_blocks
.iter()
.map(|block| self.key.decrypt_squashed_noise_ciphertext(block));
Ok(BlockRecomposer::recompose_signed_with_size(
decrypted_packed_block_iter,
bits_in_packed_block,
original_bit_size,
))
}
pub fn decrypt_bool(&self, ct: &SquashedNoiseBooleanBlock) -> crate::Result<bool> {
let SquashedNoiseBooleanBlock { ciphertext } = ct;
let boolean_block_msg_mod = ciphertext.message_modulus();
let boolean_block_carry_mod = ciphertext.carry_modulus();
let key_msg_mod = self.key.noise_squashing_parameters().message_modulus;
let key_carry_mod = self.key.noise_squashing_parameters().carry_modulus;
if boolean_block_msg_mod != key_msg_mod || boolean_block_carry_mod != key_carry_mod {
return Err(crate::error!(
"Input SquashedNoiseBooleanBlock has incompatible \
message modulus {boolean_block_msg_mod:?} or \
carry modulus {boolean_block_carry_mod:?} with the current \
NoiseSquashingPrivateKey message modulus {key_msg_mod:?}, \
carry modulus {key_carry_mod:?}."
));
}
let decrypted = self.key.decrypt_squashed_noise_ciphertext(ciphertext);
Ok(decrypted != 0)
}
pub fn into_raw_parts(self) -> crate::shortint::noise_squashing::NoiseSquashingPrivateKey {
let Self { key } = self;
key
}
pub fn from_raw_parts(key: crate::shortint::noise_squashing::NoiseSquashingPrivateKey) -> Self {
Self { key }
}
}
impl NoiseSquashingKey {
pub fn into_raw_parts(self) -> crate::shortint::noise_squashing::NoiseSquashingKey {
let Self { key } = self;
key
}
pub fn from_raw_parts(key: crate::shortint::noise_squashing::NoiseSquashingKey) -> Self {
Self { key }
}
pub fn squash_radix_ciphertext_noise(
&self,
src_server_key: &ServerKey,
ciphertext: &RadixCiphertext,
) -> SquashedNoiseRadixCiphertext {
let original_block_count = ciphertext.blocks.len();
let packed_blocks: Vec<_> = ciphertext
.blocks
.par_chunks(2)
.map(|two_values| {
let packed = src_server_key.pack_block_chunk(two_values);
self.key
.squash_ciphertext_noise(&packed, &src_server_key.key)
})
.collect();
SquashedNoiseRadixCiphertext {
packed_blocks,
original_block_count,
}
}
pub fn squash_signed_radix_ciphertext_noise(
&self,
src_server_key: &ServerKey,
ciphertext: &SignedRadixCiphertext,
) -> SquashedNoiseSignedRadixCiphertext {
let original_block_count = ciphertext.blocks.len();
let packed_blocks: Vec<_> = ciphertext
.blocks
.par_chunks(2)
.map(|two_values| {
let packed = src_server_key.pack_block_chunk(two_values);
self.key
.squash_ciphertext_noise(&packed, &src_server_key.key)
})
.collect();
SquashedNoiseSignedRadixCiphertext {
packed_blocks,
original_block_count,
}
}
pub fn squash_boolean_block_noise(
&self,
src_server_key: &ServerKey,
boolean_block: &BooleanBlock,
) -> SquashedNoiseBooleanBlock {
SquashedNoiseBooleanBlock {
ciphertext: self
.key
.squash_ciphertext_noise(&boolean_block.0, &src_server_key.key),
}
}
}
impl ClientKey {
pub fn new_compressed_noise_squashing_key(
&self,
noise_squashing_private_key: &NoiseSquashingPrivateKey,
) -> CompressedNoiseSquashingKey {
CompressedNoiseSquashingKey {
key: self
.key
.new_compressed_noise_squashing_key(&noise_squashing_private_key.key),
}
}
pub fn new_noise_squashing_key(
&self,
noise_squashing_private_key: &NoiseSquashingPrivateKey,
) -> NoiseSquashingKey {
NoiseSquashingKey {
key: self
.key
.new_noise_squashing_key(&noise_squashing_private_key.key),
}
}
}
impl CompressedNoiseSquashingKey {
pub fn new(
client_key: &ClientKey,
noise_squashing_private_key: &NoiseSquashingPrivateKey,
) -> Self {
client_key.new_compressed_noise_squashing_key(noise_squashing_private_key)
}
}
impl NoiseSquashingKey {
pub fn new(
client_key: &ClientKey,
noise_squashing_private_key: &NoiseSquashingPrivateKey,
) -> Self {
client_key.new_noise_squashing_key(noise_squashing_private_key)
}
}
impl ParameterSetConformant for NoiseSquashingKey {
type ParameterSet = NoiseSquashingKeyConformanceParams;
fn is_conformant(&self, parameter_set: &Self::ParameterSet) -> bool {
let Self { key } = self;
key.is_conformant(parameter_set)
}
}
impl ParameterSetConformant for CompressedNoiseSquashingKey {
type ParameterSet = NoiseSquashingKeyConformanceParams;
fn is_conformant(&self, parameter_set: &Self::ParameterSet) -> bool {
let Self { key } = self;
key.is_conformant(parameter_set)
}
}

View File

@@ -0,0 +1,5 @@
mod keys;
#[cfg(test)]
mod tests;
pub use keys::*;

View File

@@ -0,0 +1,92 @@
use crate::integer::keycache::KEY_CACHE;
use crate::integer::noise_squashing::{NoiseSquashingKey, NoiseSquashingPrivateKey};
use crate::integer::IntegerKeyKind;
use crate::shortint::parameters::{
NOISE_SQUASHING_PARAM_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M128,
PARAM_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M128,
};
use rand::prelude::*;
#[test]
fn test_integer_noise_squashing_decrypt_auto_cast_and_bool() {
let param = PARAM_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M128;
let noise_squashing_parameters = NOISE_SQUASHING_PARAM_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M128;
// The goal is to test that encrypting a value stored in a type
// for which the bit count does not match the target block count of the encrypted
// radix properly applies upcasting/downcasting
let (cks, sks) = KEY_CACHE.get_from_params(param, IntegerKeyKind::Radix);
let noise_squashing_private_key = NoiseSquashingPrivateKey::new(noise_squashing_parameters);
let noise_squashing_key = NoiseSquashingKey::new(&cks, &noise_squashing_private_key);
let mut rng = rand::thread_rng();
let num_blocks = 32u32.div_ceil(param.message_modulus.0.ilog2()) as usize;
// Positive signed value
let value = rng.gen_range(0..=i32::MAX);
let ct = cks.encrypt_signed_radix(value, num_blocks * 2);
let ct = sks.bitand_parallelized(&ct, &ct);
let ct = noise_squashing_key.squash_signed_radix_ciphertext_noise(&sks, &ct);
let d: i64 = noise_squashing_private_key
.decrypt_signed_radix(&ct)
.unwrap();
assert_eq!(i64::from(value), d);
let ct = cks.encrypt_signed_radix(value, num_blocks.div_ceil(2));
let ct = sks.bitand_parallelized(&ct, &ct);
let ct = noise_squashing_key.squash_signed_radix_ciphertext_noise(&sks, &ct);
let d: i16 = noise_squashing_private_key
.decrypt_signed_radix(&ct)
.unwrap();
assert_eq!(value as i16, d);
let odd_block_count = if num_blocks % 2 == 1 {
num_blocks
} else {
num_blocks + 1
};
// Negative signed value
for block_count in [odd_block_count, num_blocks * 2, num_blocks.div_ceil(2)] {
let value = rng.gen_range(i8::MIN..0);
let ct = cks.encrypt_signed_radix(value, block_count);
let ct = sks.bitand_parallelized(&ct, &ct);
let ct = noise_squashing_key.squash_signed_radix_ciphertext_noise(&sks, &ct);
let d: i64 = noise_squashing_private_key
.decrypt_signed_radix(&ct)
.unwrap();
assert_eq!(i64::from(value), d);
let ct = cks.encrypt_signed_radix(value, block_count);
let ct = sks.bitand_parallelized(&ct, &ct);
let ct = noise_squashing_key.squash_signed_radix_ciphertext_noise(&sks, &ct);
let d: i16 = noise_squashing_private_key
.decrypt_signed_radix(&ct)
.unwrap();
assert_eq!(value as i16, d);
}
// Unsigned value
let value = rng.gen::<u32>();
let ct = cks.encrypt_radix(value, num_blocks * 2);
let ct = sks.bitand_parallelized(&ct, &ct);
let ct = noise_squashing_key.squash_radix_ciphertext_noise(&sks, &ct);
let d: u64 = noise_squashing_private_key.decrypt_radix(&ct).unwrap();
assert_eq!(u64::from(value), d);
let ct = cks.encrypt_radix(value, num_blocks.div_ceil(2));
let ct = sks.bitand_parallelized(&ct, &ct);
let ct = noise_squashing_key.squash_radix_ciphertext_noise(&sks, &ct);
let d: u16 = noise_squashing_private_key.decrypt_radix(&ct).unwrap();
assert_eq!(value as u16, d);
// Booleans
for val in [true, false] {
let ct = cks.encrypt_bool(val);
let ct = sks.boolean_bitand(&ct, &ct);
let ct = noise_squashing_key.squash_boolean_block_noise(&sks, &ct);
let d = noise_squashing_private_key.decrypt_bool(&ct).unwrap();
assert_eq!(val, d);
}
}

View File

@@ -47,6 +47,9 @@ fn test_noise_squashing_ci_run_filter() {
decompressed_noise_squashing_key.squash_ciphertext_noise(&packed, sks);
let squashed_noise_ct = noise_squashing_key.squash_ciphertext_noise(&packed, sks);
assert_eq!(squashed_noise_ct.degree(), packed.degree);
assert_eq!(squashed_noise_ct_from_compressed.degree(), packed.degree);
let recovered_from_compressed = noise_squashing_private_key
.decrypt_squashed_noise_ciphertext(&squashed_noise_ct_from_compressed);
let recovered =