mirror of
https://github.com/zama-ai/tfhe-rs.git
synced 2026-01-09 06:38:06 -05:00
feat(integer): add noise squashing to integer
This commit is contained in:
@@ -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),
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
19
tfhe/src/integer/backward_compatibility/noise_squashing.rs
Normal file
19
tfhe/src/integer/backward_compatibility/noise_squashing.rs
Normal 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),
|
||||
}
|
||||
@@ -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>
|
||||
{
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -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::*;
|
||||
|
||||
27
tfhe/src/integer/ciphertext/squashed_noise.rs
Normal file
27
tfhe/src/integer/ciphertext/squashed_noise.rs
Normal 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,
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
353
tfhe/src/integer/noise_squashing/keys.rs
Normal file
353
tfhe/src/integer/noise_squashing/keys.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
5
tfhe/src/integer/noise_squashing/mod.rs
Normal file
5
tfhe/src/integer/noise_squashing/mod.rs
Normal file
@@ -0,0 +1,5 @@
|
||||
mod keys;
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
pub use keys::*;
|
||||
92
tfhe/src/integer/noise_squashing/tests.rs
Normal file
92
tfhe/src/integer/noise_squashing/tests.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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 =
|
||||
|
||||
Reference in New Issue
Block a user