test(shortint): pbs 128 + compression test with new noise measurement

This commit is contained in:
Arthur Meyre
2025-06-12 17:32:16 +02:00
parent eb6760a7c8
commit a7dd071bd4
10 changed files with 1412 additions and 19 deletions

View File

@@ -55,10 +55,10 @@ impl<Scalar: UnsignedInteger + CastFrom<u64>> ShortintEncoding<Scalar> {
}
impl<Scalar: UnsignedInteger + CastFrom<u64>> ShortintEncoding<Scalar> {
/// Return the cleatext space including the space for the padding bit if it is set to
/// Return the cleatext space including the space for the [`Self::padding_bit`] if it is set to
/// [`PaddingBit::Yes`].
pub(crate) fn full_cleartext_space(&self) -> Scalar {
let cleartext_modulus: Scalar = (self.message_modulus.0 * self.carry_modulus.0).cast_into();
let cleartext_modulus = self.cleartext_space_without_padding();
cleartext_modulus
* if self.padding_bit == PaddingBit::No {
@@ -68,6 +68,12 @@ impl<Scalar: UnsignedInteger + CastFrom<u64>> ShortintEncoding<Scalar> {
}
}
/// Return the cleatext space defined by the [`Self::message_modulus`] and
/// [`Self::carry_modulus`], not taking the value of the [`Self::padding_bit`] into account.
pub(crate) fn cleartext_space_without_padding(&self) -> Scalar {
(self.message_modulus.0 * self.carry_modulus.0).cast_into()
}
pub(crate) fn encode(&self, value: Cleartext<Scalar>) -> Plaintext<Scalar> {
let delta = compute_delta(
self.ciphertext_modulus,

View File

@@ -131,4 +131,12 @@ impl NoiseSquashingCompressionPrivateKey {
})
.collect()
}
pub fn post_packing_ks_key(&self) -> &GlweSecretKeyOwned<u128> {
&self.post_packing_ks_key
}
pub fn params(&self) -> NoiseSquashingCompressionParameters {
self.params
}
}

View File

@@ -248,6 +248,14 @@ pub struct NoiseSquashingCompressionKey {
}
impl NoiseSquashingCompressionKey {
pub fn new(
noise_squashing_private_key: &NoiseSquashingPrivateKey,
noise_squashing_compression_private_key: &NoiseSquashingCompressionPrivateKey,
) -> Self {
noise_squashing_private_key
.new_noise_squashing_compression_key(noise_squashing_compression_private_key)
}
/// Construct from raw parts
///
/// # Panics
@@ -278,6 +286,14 @@ impl NoiseSquashingCompressionKey {
(packing_key_switching_key, lwe_per_glwe)
}
pub fn packing_key_switching_key(&self) -> &LwePackingKeyswitchKey<Vec<u128>> {
&self.packing_key_switching_key
}
pub fn lwe_per_glwe(&self) -> LweCiphertextCount {
self.lwe_per_glwe
}
}
impl NoiseSquashingPrivateKey {

View File

@@ -1,6 +1,7 @@
use crate::core_crypto::algorithms::glwe_secret_key_generation::allocate_and_generate_new_binary_glwe_secret_key;
use crate::core_crypto::algorithms::lwe_encryption::decrypt_lwe_ciphertext;
use crate::core_crypto::entities::glwe_secret_key::GlweSecretKeyOwned;
use crate::core_crypto::entities::lwe_secret_key::LweSecretKeyView;
use crate::shortint::backward_compatibility::noise_squashing::NoiseSquashingPrivateKeyVersions;
use crate::shortint::ciphertext::SquashedNoiseCiphertext;
use crate::shortint::encoding::ShortintEncoding;
@@ -71,6 +72,10 @@ impl NoiseSquashingPrivateKey {
pub fn into_raw_parts(self) -> (GlweSecretKeyOwned<u128>, NoiseSquashingParameters) {
(self.post_noise_squashing_secret_key, self.params)
}
pub fn post_noise_squashing_lwe_secret_key(&self) -> LweSecretKeyView<'_, u128> {
self.post_noise_squashing_secret_key.as_lwe_secret_key()
}
}
pub(crate) struct NoiseSquashingPrivateKeyView<'a> {

View File

@@ -54,21 +54,21 @@ pub enum Shortint128BootstrappingKey {
}
impl Shortint128BootstrappingKey {
fn output_lwe_dimension(&self) -> LweDimension {
pub fn output_lwe_dimension(&self) -> LweDimension {
match self {
Self::Classic { bsk, .. } => bsk.output_lwe_dimension(),
Self::MultiBit { bsk, .. } => bsk.output_lwe_dimension(),
}
}
fn glwe_size(&self) -> GlweSize {
pub fn glwe_size(&self) -> GlweSize {
match self {
Self::Classic { bsk, .. } => bsk.glwe_size(),
Self::MultiBit { bsk, .. } => bsk.glwe_size(),
}
}
fn polynomial_size(&self) -> PolynomialSize {
pub fn polynomial_size(&self) -> PolynomialSize {
match self {
Self::Classic { bsk, .. } => bsk.polynomial_size(),
Self::MultiBit { bsk, .. } => bsk.polynomial_size(),
@@ -427,6 +427,10 @@ impl NoiseSquashingKey {
res
}
pub fn bootstrapping_key(&self) -> &Shortint128BootstrappingKey {
&self.bootstrapping_key
}
pub fn message_modulus(&self) -> MessageModulus {
self.message_modulus
}

View File

@@ -0,0 +1,804 @@
use super::should_use_single_key_debug;
use super::utils::noise_simulation::*;
use super::utils::traits::*;
use super::utils::{mean_and_variance_check, DecryptionAndNoiseResult, NoiseSample};
use crate::core_crypto::algorithms::lwe_programmable_bootstrapping::generate_programmable_bootstrap_glwe_lut;
use crate::core_crypto::commons::dispersion::Variance;
use crate::core_crypto::commons::parameters::CiphertextModulusLog;
use crate::shortint::atomic_pattern::AtomicPatternServerKey;
use crate::shortint::client_key::atomic_pattern::AtomicPatternClientKey;
use crate::shortint::client_key::ClientKey;
use crate::shortint::encoding::{PaddingBit, ShortintEncoding};
use crate::shortint::engine::ShortintEngine;
use crate::shortint::list_compression::{
NoiseSquashingCompressionKey, NoiseSquashingCompressionPrivateKey,
};
use crate::shortint::noise_squashing::{
NoiseSquashingKey, NoiseSquashingPrivateKey, Shortint128BootstrappingKey,
};
use crate::shortint::parameters::noise_squashing::NoiseSquashingParameters;
use crate::shortint::parameters::test_params::{
TEST_PARAM_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M128,
TEST_PARAM_NOISE_SQUASHING_COMP_PARAM_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M128,
TEST_PARAM_NOISE_SQUASHING_PARAM_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M128,
};
use crate::shortint::parameters::{AtomicPatternParameters, NoiseSquashingCompressionParameters};
use crate::shortint::server_key::{ModulusSwitchConfiguration, ServerKey};
use rayon::prelude::*;
#[allow(clippy::too_many_arguments)]
fn dp_ks_standard_pbs128<
InputCt,
ScalarMulResult,
KsResult,
DriftTechniqueResult,
MsResult,
PbsResult,
DPScalar,
KsKey,
DriftKey,
Bsk,
Accumulator,
Resources,
>(
input: InputCt,
scalar: DPScalar,
ksk: &KsKey,
mod_switch_noise_reduction_key_128: &DriftKey,
bsk_128: &Bsk,
br_input_modulus_log: CiphertextModulusLog,
accumulator: &Accumulator,
side_resources: &mut Resources,
) -> (
InputCt,
ScalarMulResult,
KsResult,
DriftTechniqueResult,
MsResult,
PbsResult,
)
where
// InputCt needs to be multipliable by the given scalar
InputCt: ScalarMul<DPScalar, Output = ScalarMulResult, SideResources = Resources>,
// We need to be able to allocate the result and keyswitch the result of the ScalarMul
KsKey: AllocateKeyswtichResult<Output = KsResult, SideResources = Resources>
+ Keyswitch<ScalarMulResult, KsResult, SideResources = Resources>,
// We need to be able to allocate the result and apply drift technique + mod switch it
DriftKey: AllocateDriftTechniqueStandardModSwitchResult<
AfterDriftOutput = DriftTechniqueResult,
AfterMsOutput = MsResult,
SideResources = Resources,
> + DrifTechniqueStandardModSwitch<
KsResult,
DriftTechniqueResult,
MsResult,
SideResources = Resources,
>,
// The accumulator has the information about the output size and modulus, therefore it is the
// one to allocate the blind rotation result
Accumulator: AllocateBlindRotationResult<Output = PbsResult, SideResources = Resources>,
// We need to be able to apply the PBS
Bsk: StandardFft128Bootstrap<MsResult, PbsResult, Accumulator, SideResources = Resources>,
{
let after_dp = input.scalar_mul(scalar, side_resources);
let mut ks_result = ksk.allocate_keyswitch_result(side_resources);
ksk.keyswitch(&after_dp, &mut ks_result, side_resources);
let (mut drift_technique_result, mut ms_result) = mod_switch_noise_reduction_key_128
.allocate_drift_technique_standard_mod_switch_result(side_resources);
mod_switch_noise_reduction_key_128.drift_technique_and_standard_mod_switch(
br_input_modulus_log,
&ks_result,
&mut drift_technique_result,
&mut ms_result,
side_resources,
);
let mut pbs_result = accumulator.allocated_blind_rotation_result(side_resources);
bsk_128.standard_fft_128_pbs(&ms_result, &mut pbs_result, accumulator, side_resources);
(
input,
after_dp,
ks_result,
drift_technique_result,
ms_result,
pbs_result,
)
}
#[allow(clippy::too_many_arguments)]
#[allow(clippy::type_complexity)]
fn dp_ks_standard_pbs128_packing_ks<
InputCt,
ScalarMulResult,
KsResult,
DriftTechniqueResult,
MsResult,
PbsResult,
PackingResult,
DPScalar,
KsKey,
DriftKey,
Bsk,
PackingKey,
Accumulator,
Resources,
>(
input: Vec<InputCt>,
scalar: DPScalar,
ksk: &KsKey,
mod_switch_noise_reduction_key_128: &DriftKey,
bsk_128: &Bsk,
br_input_modulus_log: CiphertextModulusLog,
accumulator: &Accumulator,
packing_ksk: &PackingKey,
side_resources: &mut [Resources],
) -> (
Vec<(
InputCt,
ScalarMulResult,
KsResult,
DriftTechniqueResult,
MsResult,
PbsResult,
)>,
PackingResult,
)
where
// InputCt needs to be multipliable by the given scalar
InputCt: ScalarMul<DPScalar, Output = ScalarMulResult, SideResources = Resources> + Send,
// We need to be able to allocate the result and keyswitch the result of the ScalarMul
KsKey: AllocateKeyswtichResult<Output = KsResult, SideResources = Resources>
+ Keyswitch<ScalarMulResult, KsResult, SideResources = Resources>
+ Sync,
// We need to be able to allocate the result and apply drift technique + mod switch it
DriftKey: AllocateDriftTechniqueStandardModSwitchResult<
AfterDriftOutput = DriftTechniqueResult,
AfterMsOutput = MsResult,
SideResources = Resources,
> + DrifTechniqueStandardModSwitch<
KsResult,
DriftTechniqueResult,
MsResult,
SideResources = Resources,
> + Sync,
// The accumulator has the information about the output size and modulus, therefore it is the
// one to allocate the blind rotation result
Accumulator: AllocateBlindRotationResult<Output = PbsResult, SideResources = Resources> + Sync,
// We need to be able to apply the PBS
Bsk:
StandardFft128Bootstrap<MsResult, PbsResult, Accumulator, SideResources = Resources> + Sync,
PackingKey: AllocatePackingKeyswitchResult<Output = PackingResult, SideResources = Resources>
+ for<'a> LwePackingKeyswitch<[&'a PbsResult], PackingResult, SideResources = Resources>,
Resources: Send,
ScalarMulResult: Send,
KsResult: Send,
DriftTechniqueResult: Send,
MsResult: Send,
PbsResult: Send,
DPScalar: Copy + Sync + Send,
{
let res: Vec<_> = input
.into_par_iter()
.zip(side_resources.par_iter_mut())
.map(|(input, side_resources)| {
let (input, after_dp, ks_result, drift_technique_result, ms_result, pbs_result) =
dp_ks_standard_pbs128(
input,
scalar,
ksk,
mod_switch_noise_reduction_key_128,
bsk_128,
br_input_modulus_log,
accumulator,
side_resources,
);
(
input,
after_dp,
ks_result,
drift_technique_result,
ms_result,
pbs_result,
)
})
.collect();
let pbs_results: Vec<_> = res
.iter()
.map(
|(_input, _after_dp, _ks_result, _drift_technique_result, _ms_result, pbs_result)| {
pbs_result
},
)
.collect();
let mut packing_result = packing_ksk.allocate_packing_keyswitch_result(&mut side_resources[0]);
packing_ksk.keyswitch_lwes_and_pack_in_glwe(
pbs_results.as_slice(),
&mut packing_result,
&mut side_resources[0],
);
(res, packing_result)
}
/// Test function to verify that the noise checking tools match the actual atomic patterns
/// implemented in shortint
fn sanity_check_encrypt_dp_ks_standard_pbs128_packing_ks<P>(
params: P,
noise_squashing_params: NoiseSquashingParameters,
noise_squashing_compression_params: NoiseSquashingCompressionParameters,
) where
P: Into<AtomicPatternParameters>,
{
let params: AtomicPatternParameters = params.into();
let cks = ClientKey::new(params);
let sks = ServerKey::new(&cks);
let noise_squashing_private_key = NoiseSquashingPrivateKey::new(noise_squashing_params);
let noise_squashing_key = NoiseSquashingKey::new(&cks, &noise_squashing_private_key);
let noise_squashing_compression_private_key =
NoiseSquashingCompressionPrivateKey::new(noise_squashing_compression_params);
let noise_squashing_compression_key = NoiseSquashingCompressionKey::new(
&noise_squashing_private_key,
&noise_squashing_compression_private_key,
);
let lwe_per_glwe = noise_squashing_compression_key.lwe_per_glwe();
let u128_encoding = ShortintEncoding {
ciphertext_modulus: noise_squashing_params.ciphertext_modulus(),
message_modulus: noise_squashing_params.message_modulus(),
carry_modulus: noise_squashing_params.carry_modulus(),
padding_bit: PaddingBit::Yes,
};
let max_scalar_mul = sks.max_noise_level.get();
match &sks.atomic_pattern {
AtomicPatternServerKey::Standard(standard_atomic_pattern_server_key) => {
let ksk = &standard_atomic_pattern_server_key.key_switching_key;
let bsk = noise_squashing_key.bootstrapping_key();
let (fbsk, drift_key) = match bsk {
Shortint128BootstrappingKey::Classic {
bsk,
modulus_switch_noise_reduction_key,
} => (bsk, modulus_switch_noise_reduction_key),
Shortint128BootstrappingKey::MultiBit { .. } => todo!(),
};
let drift_key = {
match drift_key {
ModulusSwitchConfiguration::Standard => None,
ModulusSwitchConfiguration::DriftTechniqueNoiseReduction(
modulus_switch_noise_reduction_key,
) => Some(modulus_switch_noise_reduction_key),
ModulusSwitchConfiguration::CenteredMeanNoiseReduction => None,
}
.unwrap()
};
let pksk = noise_squashing_compression_key.packing_key_switching_key();
let id_lut = generate_programmable_bootstrap_glwe_lut(
fbsk.polynomial_size(),
fbsk.glwe_size(),
u128_encoding
.cleartext_space_without_padding()
.try_into()
.unwrap(),
u128_encoding.ciphertext_modulus,
u128_encoding.delta(),
|x| x,
);
let br_input_modulus_log = fbsk.polynomial_size().to_blind_rotation_input_modulus_log();
let input_zeros: Vec<_> = (0..lwe_per_glwe.0).map(|_| cks.encrypt(0)).collect();
let mut side_resources = vec![(); input_zeros.len()];
let input_zero_as_lwe: Vec<_> = input_zeros.iter().map(|ct| ct.ct.clone()).collect();
let (_before_packing, mut after_packing) = dp_ks_standard_pbs128_packing_ks(
input_zero_as_lwe,
max_scalar_mul,
ksk,
drift_key,
fbsk,
br_input_modulus_log,
&id_lut,
pksk,
&mut side_resources,
);
let noise_squashed: Vec<_> = input_zeros
.into_par_iter()
.map(|mut ct| {
sks.unchecked_scalar_mul_assign(&mut ct, max_scalar_mul.try_into().unwrap());
noise_squashing_key.squash_ciphertext_noise(&ct, &sks)
})
.collect();
let compressed = noise_squashing_compression_key
.compress_noise_squashed_ciphertexts_into_list(&noise_squashed);
let underlying_glwes = compressed.glwe_ciphertext_list;
assert_eq!(underlying_glwes.len(), 1);
let extracted = underlying_glwes[0].extract();
// Bodies that were not filled are discarded
after_packing.get_mut_body().as_mut()[lwe_per_glwe.0..].fill(0);
assert_eq!(after_packing.as_view(), extracted.as_view());
}
AtomicPatternServerKey::KeySwitch32(_ks32_atomic_pattern_server_key) => {
todo!();
}
AtomicPatternServerKey::Dynamic(_) => unimplemented!(),
}
}
#[test]
fn test_sanity_check_encrypt_dp_ks_standard_pbs128_packing_ks_test_param_message_2_carry_2_ks_pbs_tuniform_2m128(
) {
sanity_check_encrypt_dp_ks_standard_pbs128_packing_ks(
TEST_PARAM_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M128,
TEST_PARAM_NOISE_SQUASHING_PARAM_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M128,
TEST_PARAM_NOISE_SQUASHING_COMP_PARAM_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M128,
)
}
#[allow(clippy::too_many_arguments)]
#[allow(clippy::type_complexity)]
fn encrypt_dp_ks_standard_pbs128_packing_ks_inner_helper(
params: AtomicPatternParameters,
noise_squashing_params: NoiseSquashingParameters,
noise_squashing_compression_params: NoiseSquashingCompressionParameters,
single_cks: &ClientKey,
single_sks: &ServerKey,
single_noise_squashing_private_key: &NoiseSquashingPrivateKey,
single_noise_squashing_key: &NoiseSquashingKey,
single_noise_squashing_compression_private_key: &NoiseSquashingCompressionPrivateKey,
single_noise_squashing_compression_key: &NoiseSquashingCompressionKey,
msg: u64,
scalar_for_multiplication: u64,
) -> (
Vec<(
DecryptionAndNoiseResult,
DecryptionAndNoiseResult,
DecryptionAndNoiseResult,
DecryptionAndNoiseResult,
DecryptionAndNoiseResult,
DecryptionAndNoiseResult,
)>,
Vec<DecryptionAndNoiseResult>,
) {
let mut engine = ShortintEngine::new();
let thread_cks;
let thread_sks;
let thread_private_noise_squashing_key;
let thread_noise_squashing_key;
let thread_private_noise_squashing_compression_key;
let thread_noise_squashing_compression_key;
let (
cks,
sks,
noise_squashing_private_key,
noise_squashing_key,
noise_squashing_compression_private_key,
noise_squashing_compression_key,
) = if should_use_single_key_debug() {
(
single_cks,
single_sks,
single_noise_squashing_private_key,
single_noise_squashing_key,
single_noise_squashing_compression_private_key,
single_noise_squashing_compression_key,
)
} else {
thread_cks = engine.new_client_key(params);
thread_sks = engine.new_server_key(&thread_cks);
thread_private_noise_squashing_key = NoiseSquashingPrivateKey::new(noise_squashing_params);
thread_noise_squashing_key =
NoiseSquashingKey::new(&thread_cks, &thread_private_noise_squashing_key);
thread_private_noise_squashing_compression_key =
NoiseSquashingCompressionPrivateKey::new(noise_squashing_compression_params);
thread_noise_squashing_compression_key = NoiseSquashingCompressionKey::new(
&thread_private_noise_squashing_key,
&thread_private_noise_squashing_compression_key,
);
(
&thread_cks,
&thread_sks,
&thread_private_noise_squashing_key,
&thread_noise_squashing_key,
&thread_private_noise_squashing_compression_key,
&thread_noise_squashing_compression_key,
)
};
let ksk = match &sks.atomic_pattern {
AtomicPatternServerKey::Standard(standard_atomic_pattern_server_key) => {
&standard_atomic_pattern_server_key.key_switching_key
}
AtomicPatternServerKey::KeySwitch32(_) => {
todo!()
}
AtomicPatternServerKey::Dynamic(_) => unimplemented!(),
};
let (bsk_128, drift_key) = {
let (bsk, drift_key) = match noise_squashing_key.bootstrapping_key() {
Shortint128BootstrappingKey::Classic {
bsk,
modulus_switch_noise_reduction_key,
} => (bsk, modulus_switch_noise_reduction_key),
Shortint128BootstrappingKey::MultiBit { .. } => todo!(),
};
let drift_key = match drift_key {
ModulusSwitchConfiguration::Standard => None,
ModulusSwitchConfiguration::DriftTechniqueNoiseReduction(
modulus_switch_noise_reduction_key,
) => Some(modulus_switch_noise_reduction_key),
ModulusSwitchConfiguration::CenteredMeanNoiseReduction => None,
}
.unwrap();
(bsk, drift_key)
};
let bsk_polynomial_size = bsk_128.polynomial_size();
let bsk_glwe_size = bsk_128.glwe_size();
let br_input_modulus_log = bsk_128
.polynomial_size()
.to_blind_rotation_input_modulus_log();
let compression_pksk = noise_squashing_compression_key.packing_key_switching_key();
let u128_encoding = ShortintEncoding {
ciphertext_modulus: noise_squashing_params.ciphertext_modulus(),
message_modulus: noise_squashing_params.message_modulus(),
carry_modulus: noise_squashing_params.carry_modulus(),
padding_bit: PaddingBit::Yes,
};
let id_lut = generate_programmable_bootstrap_glwe_lut(
bsk_polynomial_size,
bsk_glwe_size,
u128_encoding
.cleartext_space_without_padding()
.try_into()
.unwrap(),
u128_encoding.ciphertext_modulus,
u128_encoding.delta(),
|x| x,
);
let lwe_per_glwe = noise_squashing_compression_key.lwe_per_glwe();
let inputs: Vec<_> = (0..lwe_per_glwe.0).map(|_| cks.encrypt(msg).ct).collect();
let mut side_resources = vec![(); inputs.len()];
let (before_packing, after_packing) = dp_ks_standard_pbs128_packing_ks(
inputs,
scalar_for_multiplication,
ksk,
drift_key,
bsk_128,
br_input_modulus_log,
&id_lut,
compression_pksk,
side_resources.as_mut_slice(),
);
let u64_encoding = ShortintEncoding::from_parameters(params, PaddingBit::Yes);
let before_packing: Vec<_> = before_packing
.into_iter()
.map(
|(input, after_dp, after_ks, after_drift, after_ms, after_pbs128)| match &cks
.atomic_pattern
{
AtomicPatternClientKey::Standard(standard_atomic_pattern_client_key) => (
DecryptionAndNoiseResult::new(
&input,
&standard_atomic_pattern_client_key.large_lwe_secret_key(),
msg,
&u64_encoding,
),
DecryptionAndNoiseResult::new(
&after_dp,
&standard_atomic_pattern_client_key.large_lwe_secret_key(),
msg,
&u64_encoding,
),
DecryptionAndNoiseResult::new(
&after_ks,
&standard_atomic_pattern_client_key.small_lwe_secret_key(),
msg,
&u64_encoding,
),
DecryptionAndNoiseResult::new(
&after_drift,
&standard_atomic_pattern_client_key.small_lwe_secret_key(),
msg,
&u64_encoding,
),
DecryptionAndNoiseResult::new(
&after_ms,
&standard_atomic_pattern_client_key.small_lwe_secret_key(),
msg,
&u64_encoding,
),
DecryptionAndNoiseResult::new(
&after_pbs128,
&noise_squashing_private_key.post_noise_squashing_lwe_secret_key(),
msg.into(),
&u128_encoding,
),
),
AtomicPatternClientKey::KeySwitch32(_) => todo!(),
},
)
.collect();
let after_packing = DecryptionAndNoiseResult::new_from_glwe(
&after_packing,
noise_squashing_compression_private_key.post_packing_ks_key(),
lwe_per_glwe,
msg.into(),
&u128_encoding,
);
assert_eq!(after_packing.len(), lwe_per_glwe.0);
(before_packing, after_packing)
}
#[allow(clippy::too_many_arguments)]
#[allow(clippy::type_complexity)]
fn encrypt_dp_ks_standard_pbs128_packing_ks_noise_helper(
params: AtomicPatternParameters,
noise_squashing_params: NoiseSquashingParameters,
noise_squashing_compression_params: NoiseSquashingCompressionParameters,
single_cks: &ClientKey,
single_sks: &ServerKey,
single_noise_squashing_private_key: &NoiseSquashingPrivateKey,
single_noise_squashing_key: &NoiseSquashingKey,
single_noise_squashing_compression_private_key: &NoiseSquashingCompressionPrivateKey,
single_noise_squashing_compression_key: &NoiseSquashingCompressionKey,
msg: u64,
scalar_for_multiplication: u64,
) -> (
Vec<(
NoiseSample,
NoiseSample,
NoiseSample,
NoiseSample,
NoiseSample,
NoiseSample,
)>,
Vec<NoiseSample>,
) {
let (before_compression, after_compression) =
encrypt_dp_ks_standard_pbs128_packing_ks_inner_helper(
params,
noise_squashing_params,
noise_squashing_compression_params,
single_cks,
single_sks,
single_noise_squashing_private_key,
single_noise_squashing_key,
single_noise_squashing_compression_private_key,
single_noise_squashing_compression_key,
msg,
scalar_for_multiplication,
);
(
before_compression
.into_iter()
.map(
|(input, after_dp, after_ks, after_drift, after_ms, after_pbs)| {
(
input
.get_noise_if_decryption_was_correct()
.expect("Decryption Failed"),
after_dp
.get_noise_if_decryption_was_correct()
.expect("Decryption Failed"),
after_ks
.get_noise_if_decryption_was_correct()
.expect("Decryption Failed"),
after_drift
.get_noise_if_decryption_was_correct()
.expect("Decryption Failed"),
after_ms
.get_noise_if_decryption_was_correct()
.expect("Decryption Failed"),
after_pbs
.get_noise_if_decryption_was_correct()
.expect("Decryption Failed"),
)
},
)
.collect(),
after_compression
.into_iter()
.map(|after_compression| {
after_compression
.get_noise_if_decryption_was_correct()
.expect("Decryption Failed")
})
.collect(),
)
}
fn noise_check_encrypt_dp_ks_standard_pbs128_packing_ks_noise<P>(
params: P,
noise_squashing_params: NoiseSquashingParameters,
noise_squashing_compression_params: NoiseSquashingCompressionParameters,
) where
P: Into<AtomicPatternParameters>,
{
let params: AtomicPatternParameters = params.into();
let cks = ClientKey::new(params);
let sks = ServerKey::new(&cks);
let noise_squashing_private_key = NoiseSquashingPrivateKey::new(noise_squashing_params);
let noise_squashing_key = NoiseSquashingKey::new(&cks, &noise_squashing_private_key);
let noise_squashing_compression_private_key =
NoiseSquashingCompressionPrivateKey::new(noise_squashing_compression_params);
let noise_squashing_compression_key = NoiseSquashingCompressionKey::new(
&noise_squashing_private_key,
&noise_squashing_compression_private_key,
);
let noise_simulation_ksk = NoiseSimulationLweKsk::new_from_atomic_pattern_parameters(params);
let noise_simulation_drift_key =
NoiseSimulationDriftTechniqueKey::new_from_atomic_pattern_parameters(params);
let noise_simulation_bsk128 =
NoiseSimulationLweFourier128Bsk::new_from_parameters(params, noise_squashing_params);
let noise_simulation_packing_key = NoiseSimulationLwePackingKeyswitchKey::new_from_params(
noise_squashing_params,
noise_squashing_compression_params,
);
let fbsk_128 = match noise_squashing_key.bootstrapping_key() {
Shortint128BootstrappingKey::Classic {
bsk,
modulus_switch_noise_reduction_key: _,
} => bsk,
Shortint128BootstrappingKey::MultiBit { .. } => todo!(),
};
assert!(noise_simulation_bsk128.matches_actual_bsk(fbsk_128));
assert!(noise_simulation_packing_key
.matches_actual_pksk(noise_squashing_compression_key.packing_key_switching_key()));
let br_input_modulus_log = noise_squashing_key
.bootstrapping_key()
.polynomial_size()
.to_blind_rotation_input_modulus_log();
match &sks.atomic_pattern {
AtomicPatternServerKey::Standard(standard_atomic_pattern_server_key) => {
assert!(noise_simulation_ksk
.matches_actual_ksk(&standard_atomic_pattern_server_key.key_switching_key));
}
AtomicPatternServerKey::KeySwitch32(ks32_atomic_pattern_server_key) => {
assert!(noise_simulation_ksk
.matches_actual_ksk(&ks32_atomic_pattern_server_key.key_switching_key));
}
AtomicPatternServerKey::Dynamic(_) => unimplemented!(),
}
let max_scalar_mul = sks.max_noise_level.get();
let noise_simulation_accumulator = NoiseSimulationGlwe::new(
noise_simulation_bsk128
.output_glwe_size()
.to_glwe_dimension(),
noise_simulation_bsk128.output_polynomial_size(),
Variance(0.0),
noise_simulation_bsk128.modulus(),
);
let (_before_packing_sim, after_packing_sim) = {
let noise_simulation = NoiseSimulationLwe::encrypt(&cks, 0);
dp_ks_standard_pbs128_packing_ks(
vec![noise_simulation; noise_squashing_compression_key.lwe_per_glwe().0],
max_scalar_mul,
&noise_simulation_ksk,
&noise_simulation_drift_key,
&noise_simulation_bsk128,
br_input_modulus_log,
&noise_simulation_accumulator,
&noise_simulation_packing_key,
&mut vec![(); noise_squashing_compression_key.lwe_per_glwe().0],
)
};
let after_packing_sim = after_packing_sim.into_lwe();
// Check that the circuit is correct with respect to core implementation, i.e. does not crash on
// dimension checks
let (expected_lwe_dimension_out, expected_modulus_f64_out) = {
let pksk = noise_squashing_compression_key.packing_key_switching_key();
let out_glwe_dim = pksk.output_key_glwe_dimension();
let out_poly_size = pksk.output_key_polynomial_size();
(
out_glwe_dim.to_equivalent_lwe_dimension(out_poly_size),
pksk.ciphertext_modulus().raw_modulus_float(),
)
};
assert_eq!(
after_packing_sim.lwe_dimension(),
expected_lwe_dimension_out
);
assert_eq!(
after_packing_sim.modulus().as_f64(),
expected_modulus_f64_out
);
let cleartext_modulus = params.message_modulus().0 * params.carry_modulus().0;
let mut noise_samples_after_packing = vec![];
let sample_count_per_msg = 1000usize.div_ceil(noise_squashing_compression_key.lwe_per_glwe().0);
for _ in 0..cleartext_modulus {
let current_noise_samples_after_packing: Vec<_> = (0..sample_count_per_msg)
.into_par_iter()
.map(|_| {
let (_before_packing, after_packing) =
encrypt_dp_ks_standard_pbs128_packing_ks_noise_helper(
params,
noise_squashing_params,
noise_squashing_compression_params,
&cks,
&sks,
&noise_squashing_private_key,
&noise_squashing_key,
&noise_squashing_compression_private_key,
&noise_squashing_compression_key,
0,
max_scalar_mul,
);
after_packing
})
.flatten()
.collect();
noise_samples_after_packing.extend(
current_noise_samples_after_packing
.into_iter()
.map(|x| x.value),
);
}
let after_packing_is_ok = mean_and_variance_check(
&noise_samples_after_packing,
"after_packing",
0.0,
after_packing_sim.variance(),
noise_squashing_compression_params.packing_ks_key_noise_distribution,
after_packing_sim.lwe_dimension(),
after_packing_sim.modulus().as_f64(),
);
assert!(after_packing_is_ok);
}
#[test]
fn test_noise_check_encrypt_dp_ks_standard_pbs128_packing_ks_noise_param_message_2_carry_2_ks_pbs_tuniform_2m128(
) {
noise_check_encrypt_dp_ks_standard_pbs128_packing_ks_noise(
TEST_PARAM_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M128,
TEST_PARAM_NOISE_SQUASHING_PARAM_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M128,
TEST_PARAM_NOISE_SQUASHING_COMP_PARAM_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M128,
)
}

View File

@@ -1,4 +1,5 @@
pub(crate) mod encrypt_dp_ks_modswitch;
pub(crate) mod encrypt_dp_ks_pbs128_packingks;
pub(crate) mod utils;
pub fn should_use_single_key_debug() -> bool {

View File

@@ -1,6 +1,7 @@
pub mod noise_simulation;
pub mod traits;
use crate::core_crypto::algorithms::glwe_encryption::decrypt_glwe_ciphertext;
use crate::core_crypto::algorithms::lwe_encryption::decrypt_lwe_ciphertext;
use crate::core_crypto::algorithms::lwe_keyswitch::{
keyswitch_lwe_ciphertext, keyswitch_lwe_ciphertext_with_scalar_change,
@@ -8,6 +9,8 @@ use crate::core_crypto::algorithms::lwe_keyswitch::{
use crate::core_crypto::algorithms::lwe_linear_algebra::{
lwe_ciphertext_cleartext_mul, lwe_ciphertext_cleartext_mul_assign,
};
use crate::core_crypto::algorithms::lwe_packing_keyswitch::par_keyswitch_lwe_ciphertext_list_and_pack_in_glwe_ciphertext;
use crate::core_crypto::algorithms::lwe_programmable_bootstrapping::fft128_pbs::programmable_bootstrap_f128_lwe_ciphertext;
use crate::core_crypto::algorithms::lwe_programmable_bootstrapping::fft64_pbs::programmable_bootstrap_lwe_ciphertext;
use crate::core_crypto::algorithms::misc::torus_modular_diff;
use crate::core_crypto::algorithms::test::round_decode;
@@ -19,7 +22,7 @@ use crate::core_crypto::commons::noise_formulas::secure_noise::{
};
use crate::core_crypto::commons::numeric::{CastFrom, CastInto, UnsignedInteger};
use crate::core_crypto::commons::parameters::{
CiphertextModulusLog, DynamicDistribution, LweDimension,
CiphertextModulusLog, DynamicDistribution, LweCiphertextCount, LweDimension, PlaintextCount,
};
use crate::core_crypto::commons::test_tools::{
arithmetic_mean, equivalent_pfail_gaussian_noise, gaussian_mean_confidence_interval,
@@ -27,12 +30,16 @@ use crate::core_crypto::commons::test_tools::{
pfail_clopper_pearson_exact_confidence_interval, variance, NormalityTestResult,
};
use crate::core_crypto::commons::traits::container::{Container, ContainerMut};
use crate::core_crypto::entities::glwe_ciphertext::GlweCiphertext;
use crate::core_crypto::entities::glwe_ciphertext::{GlweCiphertext, GlweCiphertextOwned};
use crate::core_crypto::entities::glwe_secret_key::GlweSecretKey;
use crate::core_crypto::entities::lwe_ciphertext::{LweCiphertext, LweCiphertextOwned};
use crate::core_crypto::entities::lwe_ciphertext_list::LweCiphertextList;
use crate::core_crypto::entities::lwe_keyswitch_key::LweKeyswitchKey;
use crate::core_crypto::entities::lwe_packing_keyswitch_key::LwePackingKeyswitchKey;
use crate::core_crypto::entities::lwe_secret_key::LweSecretKey;
use crate::core_crypto::entities::Cleartext;
use crate::core_crypto::entities::{Cleartext, PlaintextList};
use crate::core_crypto::fft_impl::common::modulus_switch;
use crate::core_crypto::fft_impl::fft128::crypto::bootstrap::Fourier128LweBootstrapKey;
use crate::core_crypto::fft_impl::fft64::c64;
use crate::core_crypto::fft_impl::fft64::crypto::bootstrap::FourierLweBootstrapKey;
use crate::core_crypto::fft_impl::fft64::math::fft::id;
@@ -414,6 +421,53 @@ impl DecryptionAndNoiseResult {
}
}
pub fn new_from_glwe<Scalar: UnsignedInteger + CastFrom<u64>, CtCont, KeyCont>(
ct: &GlweCiphertext<CtCont>,
secret_key: &GlweSecretKey<KeyCont>,
lwe_per_glwe: LweCiphertextCount,
expected_msg: Scalar,
encoding: &ShortintEncoding<Scalar>,
) -> Vec<Self>
where
CtCont: Container<Element = Scalar>,
KeyCont: Container<Element = Scalar>,
{
let mut decrypted =
PlaintextList::new(Scalar::ZERO, PlaintextCount(ct.polynomial_size().0));
let delta = encoding.delta();
let cleartext_modulus_with_padding = encoding.full_cleartext_space();
decrypt_glwe_ciphertext(secret_key, ct, &mut decrypted);
let expected_plaintext = expected_msg * delta;
decrypted
.as_ref()
.iter()
.take(lwe_per_glwe.0)
.map(|&decrypted_plaintext| {
// We apply the modulus on the cleartext + the padding bit
let decoded_msg =
round_decode(decrypted_plaintext, delta) % cleartext_modulus_with_padding;
let noise = torus_modular_diff(
expected_plaintext,
decrypted_plaintext,
ct.ciphertext_modulus(),
);
if decoded_msg == expected_msg {
Self::DecryptionSucceeded {
noise: NoiseSample { value: noise },
}
} else {
Self::DecryptionFailed
}
})
.collect()
}
pub fn get_noise_if_decryption_was_correct(&self) -> Option<NoiseSample> {
match self {
Self::DecryptionSucceeded { noise } => Some(*noise),
@@ -726,3 +780,74 @@ impl<
programmable_bootstrap_lwe_ciphertext(input, output, accumulator, self);
}
}
impl<
InputScalar: UnsignedTorus + CastInto<usize>,
OutputScalar: UnsignedTorus,
KeyCont: Container<Element = f64>,
InputCont: Container<Element = InputScalar>,
OutputCont: ContainerMut<Element = OutputScalar>,
AccCont: Container<Element = OutputScalar>,
>
StandardFft128Bootstrap<
LweCiphertext<InputCont>,
LweCiphertext<OutputCont>,
GlweCiphertext<AccCont>,
> for Fourier128LweBootstrapKey<KeyCont>
{
type SideResources = ();
fn standard_fft_128_pbs(
&self,
input: &LweCiphertext<InputCont>,
output: &mut LweCiphertext<OutputCont>,
accumulator: &GlweCiphertext<AccCont>,
_side_resources: &mut Self::SideResources,
) {
programmable_bootstrap_f128_lwe_ciphertext(input, output, accumulator, self);
}
}
impl<Scalar: UnsignedInteger, KeyCont: Container<Element = Scalar>> AllocatePackingKeyswitchResult
for LwePackingKeyswitchKey<KeyCont>
{
type Output = GlweCiphertextOwned<Scalar>;
type SideResources = ();
fn allocate_packing_keyswitch_result(
&self,
_side_resources: &mut Self::SideResources,
) -> Self::Output {
Self::Output::new(
Scalar::ZERO,
self.output_glwe_size(),
self.output_polynomial_size(),
self.ciphertext_modulus(),
)
}
}
impl<
Scalar: UnsignedInteger,
InputCont: Container<Element = Scalar>,
OutputCont: ContainerMut<Element = Scalar>,
KeyCont: Container<Element = Scalar> + Sync,
> LwePackingKeyswitch<[&LweCiphertext<InputCont>], GlweCiphertext<OutputCont>>
for LwePackingKeyswitchKey<KeyCont>
{
type SideResources = ();
fn keyswitch_lwes_and_pack_in_glwe(
&self,
input: &[&LweCiphertext<InputCont>],
output: &mut GlweCiphertext<OutputCont>,
_side_resources: &mut Self::SideResources,
) {
let input = LweCiphertextList::new_from_lwe_ciphertext_iterator(
input.iter().map(|lwe| lwe.as_view()),
)
.unwrap();
par_keyswitch_lwe_ciphertext_list_and_pack_in_glwe_ciphertext(self, &input, output);
}
}

View File

@@ -6,15 +6,28 @@ use crate::core_crypto::commons::noise_formulas::lwe_keyswitch::{
keyswitch_additive_variance_132_bits_security_gaussian,
keyswitch_additive_variance_132_bits_security_tuniform,
};
use crate::core_crypto::commons::noise_formulas::lwe_packing_keyswitch::{
packing_keyswitch_additive_variance_132_bits_security_gaussian,
packing_keyswitch_additive_variance_132_bits_security_tuniform,
};
use crate::core_crypto::commons::noise_formulas::lwe_programmable_bootstrap_128::{
pbs_128_variance_132_bits_security_gaussian_fft_mul,
pbs_128_variance_132_bits_security_tuniform_fft_mul,
};
use crate::core_crypto::commons::noise_formulas::modulus_switch::modulus_switch_additive_variance;
use crate::core_crypto::commons::numeric::{CastInto, UnsignedInteger};
use crate::core_crypto::commons::parameters::{
CiphertextModulusLog, DecompositionBaseLog, DecompositionLevelCount, DynamicDistribution,
LweDimension,
GlweDimension, GlweSize, LweDimension, PolynomialSize,
};
use crate::core_crypto::commons::traits::container::Container;
use crate::core_crypto::entities::lwe_keyswitch_key::LweKeyswitchKey;
use crate::core_crypto::entities::lwe_packing_keyswitch_key::LwePackingKeyswitchKey;
use crate::core_crypto::fft_impl::fft128::crypto::bootstrap::Fourier128LweBootstrapKey;
use crate::shortint::client_key::ClientKey;
use crate::shortint::parameters::noise_squashing::{
NoiseSquashingCompressionParameters, NoiseSquashingParameters,
};
use crate::shortint::server_key::modulus_switch_noise_reduction::ModulusSwitchNoiseReductionKey;
use crate::shortint::AtomicPatternParameters;
@@ -67,7 +80,7 @@ impl NoiseSimulationModulus {
}
}
#[derive(Clone, Copy, Debug)]
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct NoiseSimulationLwe {
lwe_dimension: LweDimension,
variance: Variance,
@@ -75,14 +88,6 @@ pub struct NoiseSimulationLwe {
}
impl NoiseSimulationLwe {
pub fn new_zero() -> Self {
Self {
lwe_dimension: LweDimension(0),
variance: Variance(-2.0f64.powi(128)),
modulus: NoiseSimulationModulus::Other(0),
}
}
pub fn lwe_dimension(&self) -> LweDimension {
self.lwe_dimension
}
@@ -207,7 +212,11 @@ impl AllocateKeyswtichResult for NoiseSimulationLweKsk {
type SideResources = ();
fn allocate_keyswitch_result(&self, _side_resources: &mut Self::SideResources) -> Self::Output {
Self::Output::new_zero()
Self::Output {
lwe_dimension: self.output_lwe_dimension,
variance: Variance(-2.0f64.powi(128)),
modulus: self.output_modulus,
}
}
}
@@ -408,3 +417,385 @@ impl DrifTechniqueStandardModSwitch<NoiseSimulationLwe, NoiseSimulationLwe, Nois
);
}
}
#[derive(Clone, Copy, Debug)]
pub struct NoiseSimulationGlwe {
glwe_dimension: GlweDimension,
polynomial_size: PolynomialSize,
variance_per_occupied_slot: Variance,
modulus: NoiseSimulationModulus,
}
impl NoiseSimulationGlwe {
pub fn new(
glwe_dimension: GlweDimension,
polynomial_size: PolynomialSize,
variance_per_occupied_slot: Variance,
modulus: NoiseSimulationModulus,
) -> Self {
Self {
glwe_dimension,
polynomial_size,
variance_per_occupied_slot,
modulus,
}
}
pub fn into_lwe(self) -> NoiseSimulationLwe {
let lwe_dimension = self
.glwe_dimension()
.to_equivalent_lwe_dimension(self.polynomial_size());
NoiseSimulationLwe {
lwe_dimension,
variance: self.variance_per_occupied_slot(),
modulus: self.modulus(),
}
}
pub fn glwe_dimension(&self) -> GlweDimension {
self.glwe_dimension
}
pub fn polynomial_size(&self) -> PolynomialSize {
self.polynomial_size
}
pub fn variance_per_occupied_slot(&self) -> Variance {
self.variance_per_occupied_slot
}
pub fn modulus(&self) -> NoiseSimulationModulus {
self.modulus
}
}
impl AllocateBlindRotationResult for NoiseSimulationGlwe {
type Output = NoiseSimulationLwe;
type SideResources = ();
fn allocated_blind_rotation_result(
&self,
_side_resources: &mut Self::SideResources,
) -> Self::Output {
let lwe_dimension = self
.glwe_dimension()
.to_equivalent_lwe_dimension(self.polynomial_size());
Self::Output {
lwe_dimension,
variance: self.variance_per_occupied_slot(),
modulus: self.modulus(),
}
}
}
#[derive(Clone, Copy)]
pub struct NoiseSimulationLweFourier128Bsk {
input_lwe_dimension: LweDimension,
output_glwe_size: GlweSize,
output_polynomial_size: PolynomialSize,
decomp_base_log: DecompositionBaseLog,
decomp_level_count: DecompositionLevelCount,
noise_distribution: DynamicDistribution<u128>,
modulus: NoiseSimulationModulus,
}
impl NoiseSimulationLweFourier128Bsk {
// We can't really build a key from an already generated key as we need to know what the noise
// distribution is.
pub fn new_from_parameters(
params: AtomicPatternParameters,
noise_squashing_params: NoiseSquashingParameters,
) -> Self {
Self {
input_lwe_dimension: params.lwe_dimension(),
output_glwe_size: noise_squashing_params.glwe_dimension().to_glwe_size(),
output_polynomial_size: noise_squashing_params.polynomial_size(),
decomp_base_log: noise_squashing_params.decomp_base_log(),
decomp_level_count: noise_squashing_params.decomp_level_count(),
noise_distribution: noise_squashing_params.glwe_noise_distribution(),
modulus: NoiseSimulationModulus::from_ciphertext_modulus(
noise_squashing_params.ciphertext_modulus(),
),
}
}
pub fn matches_actual_bsk<C: Container<Element = f64>>(
&self,
lwe_bsk: &Fourier128LweBootstrapKey<C>,
) -> bool {
let Self {
input_lwe_dimension,
output_glwe_size: glwe_size,
output_polynomial_size: polynomial_size,
decomp_base_log,
decomp_level_count,
noise_distribution: _,
modulus: _,
} = *self;
let bsk_input_lwe_dimension = lwe_bsk.input_lwe_dimension();
let bsk_glwe_size = lwe_bsk.glwe_size();
let bsk_polynomial_size = lwe_bsk.polynomial_size();
let bsk_decomp_base_log = lwe_bsk.decomposition_base_log();
let bsk_decomp_level_count = lwe_bsk.decomposition_level_count();
input_lwe_dimension == bsk_input_lwe_dimension
&& glwe_size == bsk_glwe_size
&& polynomial_size == bsk_polynomial_size
&& decomp_base_log == bsk_decomp_base_log
&& decomp_level_count == bsk_decomp_level_count
}
pub fn input_lwe_dimension(&self) -> LweDimension {
self.input_lwe_dimension
}
pub fn output_glwe_size(&self) -> GlweSize {
self.output_glwe_size
}
pub fn output_polynomial_size(&self) -> PolynomialSize {
self.output_polynomial_size
}
pub fn decomp_base_log(&self) -> DecompositionBaseLog {
self.decomp_base_log
}
pub fn decomp_level_count(&self) -> DecompositionLevelCount {
self.decomp_level_count
}
pub fn noise_distribution(&self) -> DynamicDistribution<u128> {
self.noise_distribution
}
pub fn modulus(&self) -> NoiseSimulationModulus {
self.modulus
}
}
impl StandardFft128Bootstrap<NoiseSimulationLwe, NoiseSimulationLwe, NoiseSimulationGlwe>
for NoiseSimulationLweFourier128Bsk
{
type SideResources = ();
fn standard_fft_128_pbs(
&self,
input: &NoiseSimulationLwe,
output: &mut NoiseSimulationLwe,
accumulator: &NoiseSimulationGlwe,
_side_resources: &mut Self::SideResources,
) {
assert_eq!(self.input_lwe_dimension(), input.lwe_dimension());
assert_eq!(
self.output_glwe_size(),
accumulator.glwe_dimension().to_glwe_size()
);
assert_eq!(self.output_polynomial_size(), accumulator.polynomial_size());
assert_eq!(self.modulus(), accumulator.modulus());
let br_additive_variance = match self.noise_distribution() {
DynamicDistribution::Gaussian(_) => {
pbs_128_variance_132_bits_security_gaussian_fft_mul(
self.input_lwe_dimension(),
self.output_glwe_size().to_glwe_dimension(),
self.output_polynomial_size(),
self.decomp_base_log(),
self.decomp_level_count(),
// Current PBS 128 implem has 104 bits of equivalent mantissa
104.0f64,
self.modulus().as_f64(),
)
}
DynamicDistribution::TUniform(_) => {
pbs_128_variance_132_bits_security_tuniform_fft_mul(
self.input_lwe_dimension(),
self.output_glwe_size().to_glwe_dimension(),
self.output_polynomial_size(),
self.decomp_base_log(),
self.decomp_level_count(),
// Current PBS 128 implem has 104 bits of equivalent mantissa
104.0f64,
self.modulus().as_f64(),
)
}
};
let output_lwe_dimension = self
.output_glwe_size()
.to_glwe_dimension()
.to_equivalent_lwe_dimension(self.output_polynomial_size());
output.lwe_dimension = output_lwe_dimension;
output.variance =
Variance(accumulator.variance_per_occupied_slot().0 + br_additive_variance.0);
output.modulus = accumulator.modulus;
}
}
#[derive(Clone, Copy)]
pub struct NoiseSimulationLwePackingKeyswitchKey {
input_lwe_dimension: LweDimension,
decomp_base_log: DecompositionBaseLog,
decomp_level_count: DecompositionLevelCount,
output_glwe_size: GlweSize,
output_polynomial_size: PolynomialSize,
noise_distribution: DynamicDistribution<u128>,
modulus: NoiseSimulationModulus,
}
impl NoiseSimulationLwePackingKeyswitchKey {
pub fn new_from_params(
noise_squashing_params: NoiseSquashingParameters,
noise_squashing_compression_params: NoiseSquashingCompressionParameters,
) -> Self {
let squashing_lwe_dim = noise_squashing_params
.glwe_dimension()
.to_equivalent_lwe_dimension(noise_squashing_params.polynomial_size());
Self {
input_lwe_dimension: squashing_lwe_dim,
decomp_base_log: noise_squashing_compression_params.packing_ks_base_log,
decomp_level_count: noise_squashing_compression_params.packing_ks_level,
output_glwe_size: noise_squashing_compression_params
.packing_ks_glwe_dimension
.to_glwe_size(),
output_polynomial_size: noise_squashing_compression_params.packing_ks_polynomial_size,
noise_distribution: noise_squashing_compression_params
.packing_ks_key_noise_distribution,
modulus: NoiseSimulationModulus::from_ciphertext_modulus(
noise_squashing_compression_params.ciphertext_modulus,
),
}
}
pub fn matches_actual_pksk<Scalar: UnsignedInteger, KeyCont: Container<Element = Scalar>>(
&self,
pksk: &LwePackingKeyswitchKey<KeyCont>,
) -> bool {
let Self {
input_lwe_dimension,
decomp_base_log,
decomp_level_count,
output_glwe_size,
output_polynomial_size,
noise_distribution: _,
modulus,
} = *self;
let pksk_input_lwe_dimension = pksk.input_key_lwe_dimension();
let pksk_decomp_base_log = pksk.decomposition_base_log();
let pksk_decomp_level_count = pksk.decomposition_level_count();
let pksk_output_glwe_size = pksk.output_glwe_size();
let pksk_output_polynomial_size = pksk.output_key_polynomial_size();
let pksk_modulus =
NoiseSimulationModulus::from_ciphertext_modulus(pksk.ciphertext_modulus());
input_lwe_dimension == pksk_input_lwe_dimension
&& decomp_base_log == pksk_decomp_base_log
&& decomp_level_count == pksk_decomp_level_count
&& output_glwe_size == pksk_output_glwe_size
&& output_polynomial_size == pksk_output_polynomial_size
&& modulus == pksk_modulus
}
pub fn input_lwe_dimension(&self) -> LweDimension {
self.input_lwe_dimension
}
pub fn decomp_base_log(&self) -> DecompositionBaseLog {
self.decomp_base_log
}
pub fn decomp_level_count(&self) -> DecompositionLevelCount {
self.decomp_level_count
}
pub fn output_glwe_size(&self) -> GlweSize {
self.output_glwe_size
}
pub fn output_polynomial_size(&self) -> PolynomialSize {
self.output_polynomial_size
}
pub fn noise_distribution(&self) -> DynamicDistribution<u128> {
self.noise_distribution
}
pub fn modulus(&self) -> NoiseSimulationModulus {
self.modulus
}
}
impl AllocatePackingKeyswitchResult for NoiseSimulationLwePackingKeyswitchKey {
type Output = NoiseSimulationGlwe;
type SideResources = ();
fn allocate_packing_keyswitch_result(
&self,
_side_resources: &mut Self::SideResources,
) -> Self::Output {
Self::Output {
glwe_dimension: self.output_glwe_size().to_glwe_dimension(),
polynomial_size: self.output_polynomial_size(),
variance_per_occupied_slot: Variance(-2.0f64.powi(128)),
modulus: self.modulus,
}
}
}
impl LwePackingKeyswitch<[&NoiseSimulationLwe], NoiseSimulationGlwe>
for NoiseSimulationLwePackingKeyswitchKey
{
type SideResources = ();
fn keyswitch_lwes_and_pack_in_glwe(
&self,
input: &[&NoiseSimulationLwe],
output: &mut NoiseSimulationGlwe,
_side_resources: &mut Self::SideResources,
) {
let mut input_iter = input.iter();
let input = input_iter.next().unwrap();
let mut lwe_to_pack = 1;
assert!(input_iter.inspect(|_| lwe_to_pack += 1).all(|x| x == input));
assert_eq!(input.lwe_dimension(), self.input_lwe_dimension());
let packing_ks_additive_var = match self.noise_distribution() {
DynamicDistribution::Gaussian(_) => {
packing_keyswitch_additive_variance_132_bits_security_gaussian(
self.input_lwe_dimension(),
self.output_glwe_size().to_glwe_dimension(),
self.output_polynomial_size(),
self.decomp_base_log(),
self.decomp_level_count(),
lwe_to_pack.into(),
self.modulus().as_f64(),
)
}
DynamicDistribution::TUniform(_) => {
packing_keyswitch_additive_variance_132_bits_security_tuniform(
self.input_lwe_dimension(),
self.output_glwe_size().to_glwe_dimension(),
self.output_polynomial_size(),
self.decomp_base_log(),
self.decomp_level_count(),
lwe_to_pack.into(),
self.modulus().as_f64(),
)
}
};
output.glwe_dimension = self.output_glwe_size().to_glwe_dimension();
output.polynomial_size = self.output_polynomial_size();
output.variance_per_occupied_slot =
Variance(input.variance().0 + packing_ks_additive_var.0);
output.modulus = self.modulus();
}
}

View File

@@ -97,3 +97,36 @@ pub trait StandardFftBootstrap<Input, Output, Accumulator> {
side_resources: &mut Self::SideResources,
);
}
pub trait StandardFft128Bootstrap<Input, Output, Accumulator> {
type SideResources;
fn standard_fft_128_pbs(
&self,
input: &Input,
output: &mut Output,
accumulator: &Accumulator,
side_resources: &mut Self::SideResources,
);
}
pub trait AllocatePackingKeyswitchResult {
type Output;
type SideResources;
fn allocate_packing_keyswitch_result(
&self,
side_resources: &mut Self::SideResources,
) -> Self::Output;
}
pub trait LwePackingKeyswitch<Input: ?Sized, Output> {
type SideResources;
fn keyswitch_lwes_and_pack_in_glwe(
&self,
input: &Input,
output: &mut Output,
side_resources: &mut Self::SideResources,
);
}