test(shortint): add compression atomic pattern for noise checks

- noise checks and pfail based on expected noise have been added
- compatible with KS PBS and KS32 PBS
This commit is contained in:
Arthur Meyre
2025-10-21 15:09:26 +02:00
committed by IceTDrinker
parent ba5f4850b9
commit 82cebb9b26
13 changed files with 1161 additions and 79 deletions

42
scripts/pfail_estimate.py Normal file
View File

@@ -0,0 +1,42 @@
import scipy.stats as stats
from scipy.special import erfcinv, erfc
import math
# utilities
t = 1 / (2 ** (4 + 2)) # noise bound
standard_score = lambda p_fail: math.sqrt(2) * erfcinv(p_fail) # standard score
pfail = lambda z: erfc(z / math.sqrt(2))
# Noise squashing after compression
# measured_variance = 7.598561171474912e-35
# variance_after_flood = measured_variance * (2**40 * 100) ** 2
# measured_std_dev = math.sqrt(variance_after_flood)
# New params GPU before MS 128
# measured_variance = 1.438540449823688e-6
# Rerand noise
# measured_variance = 1.4064222454361346e-6
# measured_variance = 1.408401059719539e-6
# measured_variance = 1.4120971218065554e-6 #KS32
measured_variance = 1.4150031500067098e-6
measured_std_dev = math.sqrt(measured_variance)
measured_std_score = t / measured_std_dev
estimated_pfail = pfail(measured_std_score)
print(estimated_pfail, math.log2(estimated_pfail))
# Compression encoding for 2_2
t_compression = 1 / (2 ** (2 + 2))
measured_variance = 1.0216297411906617e-5
measured_std_dev = math.sqrt(measured_variance)
measured_std_score = t_compression / measured_std_dev
estimated_pfail = pfail(measured_std_score)
print(estimated_pfail, math.log2(estimated_pfail))

View File

@@ -106,6 +106,7 @@ use crate::core_crypto::commons::noise_formulas::noise_simulation::traits::{
AllocateCenteredBinaryShiftedStandardModSwitchResult, AllocateStandardModSwitchResult,
CenteredBinaryShiftedStandardModSwitch, StandardModSwitch,
};
use crate::core_crypto::entities::glwe_ciphertext::{GlweCiphertext, GlweCiphertextOwned};
impl<Scalar: UnsignedInteger, C: Container<Element = Scalar>> AllocateStandardModSwitchResult
for LweCiphertext<C>
@@ -206,6 +207,58 @@ impl<
}
}
impl<Scalar: UnsignedInteger, C: Container<Element = Scalar>> AllocateStandardModSwitchResult
for GlweCiphertext<C>
{
type Output = GlweCiphertextOwned<Scalar>;
type SideResources = ();
fn allocate_standard_mod_switch_result(
&self,
_side_resources: &mut Self::SideResources,
) -> Self::Output {
// We will mod switch but we keep the current modulus as the noise is interesting in the
// context of the input modulus
Self::Output::new(
Scalar::ZERO,
self.glwe_size(),
self.polynomial_size(),
self.ciphertext_modulus(),
)
}
}
impl<
Scalar: UnsignedInteger,
InputCont: Container<Element = Scalar>,
OutputCont: ContainerMut<Element = Scalar>,
> StandardModSwitch<GlweCiphertext<OutputCont>> for GlweCiphertext<InputCont>
{
type SideResources = ();
fn standard_mod_switch(
&self,
output_modulus_log: CiphertextModulusLog,
output: &mut GlweCiphertext<OutputCont>,
_side_resources: &mut Self::SideResources,
) {
assert!(self
.ciphertext_modulus()
.is_compatible_with_native_modulus());
assert_eq!(self.glwe_size(), output.glwe_size());
assert_eq!(self.polynomial_size(), output.polynomial_size());
// Mod switched but the noise is to be interpreted with respect to the input modulus, as
// strictly the operation adding the noise is the rounding under the original modulus
assert_eq!(self.ciphertext_modulus(), output.ciphertext_modulus());
for (inp, out) in self.as_ref().iter().zip(output.as_mut().iter_mut()) {
let msed = modulus_switch(*inp, output_modulus_log);
// Shift in MSBs to match the power of 2 encoding in core
*out = msed << (Scalar::BITS - output_modulus_log.0);
}
}
}
#[cfg(test)]
mod tests {
use super::*;

View File

@@ -8,11 +8,11 @@ use crate::core_crypto::commons::noise_formulas::noise_simulation::traits::{
};
use crate::core_crypto::commons::noise_formulas::noise_simulation::{
NoiseSimulationGlwe, NoiseSimulationLwe, NoiseSimulationModulus,
NoiseSimulationNoiseDistribution, NoiseSimulationNoiseDistributionKind,
};
use crate::core_crypto::commons::numeric::UnsignedInteger;
use crate::core_crypto::commons::parameters::{
DecompositionBaseLog, DecompositionLevelCount, DynamicDistribution, GlweSize, LweDimension,
PolynomialSize,
DecompositionBaseLog, DecompositionLevelCount, GlweDimension, LweDimension, PolynomialSize,
};
use crate::core_crypto::commons::traits::container::Container;
use crate::core_crypto::entities::lwe_packing_keyswitch_key::LwePackingKeyswitchKey;
@@ -22,9 +22,9 @@ pub struct NoiseSimulationLwePackingKeyswitchKey {
input_lwe_dimension: LweDimension,
decomp_base_log: DecompositionBaseLog,
decomp_level_count: DecompositionLevelCount,
output_glwe_size: GlweSize,
output_glwe_dimension: GlweDimension,
output_polynomial_size: PolynomialSize,
noise_distribution: DynamicDistribution<u128>,
noise_distribution: NoiseSimulationNoiseDistribution,
modulus: NoiseSimulationModulus,
}
@@ -33,16 +33,16 @@ impl NoiseSimulationLwePackingKeyswitchKey {
input_lwe_dimension: LweDimension,
decomp_base_log: DecompositionBaseLog,
decomp_level_count: DecompositionLevelCount,
output_glwe_size: GlweSize,
output_glwe_dimension: GlweDimension,
output_polynomial_size: PolynomialSize,
noise_distribution: DynamicDistribution<u128>,
noise_distribution: NoiseSimulationNoiseDistribution,
modulus: NoiseSimulationModulus,
) -> Self {
Self {
input_lwe_dimension,
decomp_base_log,
decomp_level_count,
output_glwe_size,
output_glwe_dimension,
output_polynomial_size,
noise_distribution,
modulus,
@@ -57,7 +57,7 @@ impl NoiseSimulationLwePackingKeyswitchKey {
input_lwe_dimension,
decomp_base_log,
decomp_level_count,
output_glwe_size,
output_glwe_dimension,
output_polynomial_size,
noise_distribution: _,
modulus,
@@ -66,7 +66,7 @@ impl NoiseSimulationLwePackingKeyswitchKey {
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_glwe_dimension = pksk.output_key_glwe_dimension();
let pksk_output_polynomial_size = pksk.output_key_polynomial_size();
let pksk_modulus =
NoiseSimulationModulus::from_ciphertext_modulus(pksk.ciphertext_modulus());
@@ -74,7 +74,7 @@ impl NoiseSimulationLwePackingKeyswitchKey {
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_glwe_dimension == pksk_output_glwe_dimension
&& output_polynomial_size == pksk_output_polynomial_size
&& modulus == pksk_modulus
}
@@ -91,15 +91,15 @@ impl NoiseSimulationLwePackingKeyswitchKey {
self.decomp_level_count
}
pub fn output_glwe_size(&self) -> GlweSize {
self.output_glwe_size
pub fn output_glwe_dimension(&self) -> GlweDimension {
self.output_glwe_dimension
}
pub fn output_polynomial_size(&self) -> PolynomialSize {
self.output_polynomial_size
}
pub fn noise_distribution(&self) -> DynamicDistribution<u128> {
pub fn noise_distribution(&self) -> NoiseSimulationNoiseDistribution {
self.noise_distribution
}
@@ -117,7 +117,7 @@ impl AllocateLwePackingKeyswitchResult for NoiseSimulationLwePackingKeyswitchKey
_side_resources: &mut Self::SideResources,
) -> Self::Output {
Self::Output::new(
self.output_glwe_size().to_glwe_dimension(),
self.output_glwe_dimension(),
self.output_polynomial_size(),
Variance(f64::NAN),
self.modulus,
@@ -137,43 +137,44 @@ impl LwePackingKeyswitch<[&NoiseSimulationLwe], NoiseSimulationGlwe>
_side_resources: &mut Self::SideResources,
) {
let mut input_iter = input.iter();
let input = input_iter.next().unwrap();
let first_input = input_iter.next().unwrap();
let mut lwe_to_pack = 1;
// Check first input is compatible with us
assert_eq!(first_input.lwe_dimension(), self.input_lwe_dimension());
// Check all inputs are the same as first input
assert!(input_iter.all(|x| x == first_input));
assert!(input_iter.inspect(|_| lwe_to_pack += 1).all(|x| x == input));
let lwe_to_pack = input.len() as f64;
assert_eq!(input.lwe_dimension(), self.input_lwe_dimension());
let packing_ks_additive_var = match self.noise_distribution() {
DynamicDistribution::Gaussian(_) => {
let packing_ks_additive_var = match self.noise_distribution().kind() {
NoiseSimulationNoiseDistributionKind::Gaussian => {
packing_keyswitch_additive_variance_132_bits_security_gaussian(
self.input_lwe_dimension(),
self.output_glwe_size().to_glwe_dimension(),
self.output_glwe_dimension(),
self.output_polynomial_size(),
self.decomp_base_log(),
self.decomp_level_count(),
lwe_to_pack.into(),
lwe_to_pack,
self.modulus().as_f64(),
)
}
DynamicDistribution::TUniform(_) => {
NoiseSimulationNoiseDistributionKind::TUniform => {
packing_keyswitch_additive_variance_132_bits_security_tuniform(
self.input_lwe_dimension(),
self.output_glwe_size().to_glwe_dimension(),
self.output_glwe_dimension(),
self.output_polynomial_size(),
self.decomp_base_log(),
self.decomp_level_count(),
lwe_to_pack.into(),
lwe_to_pack,
self.modulus().as_f64(),
)
}
};
*output = NoiseSimulationGlwe::new(
self.output_glwe_size().to_glwe_dimension(),
self.output_glwe_dimension(),
self.output_polynomial_size(),
Variance(input.variance().0 + packing_ks_additive_var.0),
Variance(first_input.variance().0 + packing_ks_additive_var.0),
self.modulus(),
);
}

View File

@@ -13,13 +13,14 @@ pub use lwe_programmable_bootstrap::{
use crate::core_crypto::commons::ciphertext_modulus::CiphertextModulus;
use crate::core_crypto::commons::dispersion::Variance;
use crate::core_crypto::commons::math::random::DynamicDistribution;
use crate::core_crypto::commons::noise_formulas::noise_simulation::traits::{
AllocateLweBootstrapResult, AllocateLweMultiBitBlindRotateResult, LweUncorrelatedAdd,
LweUncorrelatedSub, ScalarMul, ScalarMulAssign,
};
use crate::core_crypto::commons::numeric::{CastInto, UnsignedInteger};
use crate::core_crypto::commons::parameters::{
CiphertextModulusLog, GlweDimension, LweDimension, PolynomialSize,
CiphertextModulusLog, GlweDimension, GlweSize, LweDimension, PolynomialSize,
};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
@@ -71,6 +72,40 @@ impl NoiseSimulationModulus {
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum NoiseSimulationNoiseDistribution {
U32(DynamicDistribution<u32>),
U64(DynamicDistribution<u64>),
U128(DynamicDistribution<u128>),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum NoiseSimulationNoiseDistributionKind {
Gaussian,
TUniform,
}
impl NoiseSimulationNoiseDistribution {
pub fn kind(&self) -> NoiseSimulationNoiseDistributionKind {
match self {
Self::U32(dynamic_distribution) => dynamic_distribution.into(),
Self::U64(dynamic_distribution) => dynamic_distribution.into(),
Self::U128(dynamic_distribution) => dynamic_distribution.into(),
}
}
}
impl<Scalar: UnsignedInteger> From<&DynamicDistribution<Scalar>>
for NoiseSimulationNoiseDistributionKind
{
fn from(value: &DynamicDistribution<Scalar>) -> Self {
match value {
DynamicDistribution::Gaussian(_) => Self::Gaussian,
DynamicDistribution::TUniform(_) => Self::TUniform,
}
}
}
// Avoids fields to be public/accessible in the noise_simulation module to make sure all functions
// use constructors
mod simulation_ciphertexts {
@@ -211,6 +246,10 @@ mod simulation_ciphertexts {
self.glwe_dimension
}
pub fn glwe_size(&self) -> GlweSize {
self.glwe_dimension().to_glwe_size()
}
pub fn polynomial_size(&self) -> PolynomialSize {
self.polynomial_size
}

View File

@@ -8,7 +8,7 @@ use crate::core_crypto::commons::noise_formulas::noise_simulation::traits::{
StandardModSwitch,
};
use crate::core_crypto::commons::noise_formulas::noise_simulation::{
NoiseSimulationLwe, NoiseSimulationModulus,
NoiseSimulationGlwe, NoiseSimulationLwe, NoiseSimulationModulus,
};
use crate::core_crypto::commons::parameters::{CiphertextModulusLog, LweBskGroupingFactor};
@@ -160,3 +160,56 @@ impl CenteredBinaryShiftedStandardModSwitch<Self> for NoiseSimulationLwe {
);
}
}
impl AllocateStandardModSwitchResult for NoiseSimulationGlwe {
type Output = Self;
type SideResources = ();
fn allocate_standard_mod_switch_result(
&self,
_side_resources: &mut Self::SideResources,
) -> Self::Output {
Self::Output::new(
self.glwe_dimension(),
self.polynomial_size(),
self.variance_per_occupied_slot(),
self.modulus(),
)
}
}
impl StandardModSwitch<Self> for NoiseSimulationGlwe {
type SideResources = ();
fn standard_mod_switch(
&self,
output_modulus_log: CiphertextModulusLog,
output: &mut Self,
_side_resources: &mut Self::SideResources,
) {
let simulation_after_mod_switch_modulus =
NoiseSimulationModulus::from_ciphertext_modulus_log(output_modulus_log);
let input_modulus_f64 = self.modulus().as_f64();
let output_modulus_f64 = simulation_after_mod_switch_modulus.as_f64();
assert!(output_modulus_f64 < input_modulus_f64);
let mod_switch_additive_variance = modulus_switch_additive_variance(
self.glwe_dimension()
.to_equivalent_lwe_dimension(self.polynomial_size()),
input_modulus_f64,
output_modulus_f64,
);
*output = Self::new(
self.glwe_dimension(),
self.polynomial_size(),
Variance(self.variance_per_occupied_slot().0 + mod_switch_additive_variance.0),
// Mod switched but the noise is to be interpreted with respect to the input modulus,
// as strictly the operation adding the noise is the rounding under the
// original modulus
self.modulus(),
)
}
}

View File

@@ -251,7 +251,21 @@ impl StandardAtomicPatternClientKey {
&self,
private_compression_key: &CompressionPrivateKeys,
) -> CompressionKey {
private_compression_key.new_compression_key(&self.glwe_secret_key, self.parameters())
ShortintEngine::with_thread_local_mut(|engine| {
self.new_compression_key_with_engine(private_compression_key, engine)
})
}
pub(crate) fn new_compression_key_with_engine(
&self,
private_compression_key: &CompressionPrivateKeys,
engine: &mut ShortintEngine,
) -> CompressionKey {
private_compression_key.new_compression_key_with_engine(
&self.glwe_secret_key,
self.parameters(),
engine,
)
}
pub fn new_compressed_compression_key(

View File

@@ -39,6 +39,17 @@ impl CompressionPrivateKeys {
&self,
glwe_secret_key: &GlweSecretKey<Vec<u64>>,
pbs_params: ShortintParameterSet,
) -> CompressionKey {
ShortintEngine::with_thread_local_mut(|engine| {
self.new_compression_key_with_engine(glwe_secret_key, pbs_params, engine)
})
}
pub(crate) fn new_compression_key_with_engine(
&self,
glwe_secret_key: &GlweSecretKey<Vec<u64>>,
pbs_params: ShortintParameterSet,
engine: &mut ShortintEngine,
) -> CompressionKey {
assert_eq!(
pbs_params.encryption_key_choice(),
@@ -57,17 +68,15 @@ impl CompressionPrivateKeys {
"Compression parameters say to store more bits than useful"
);
let packing_key_switching_key = ShortintEngine::with_thread_local_mut(|engine| {
allocate_and_generate_new_lwe_packing_keyswitch_key(
&glwe_secret_key.as_lwe_secret_key(),
&self.post_packing_ks_key,
compression_params.packing_ks_base_log(),
compression_params.packing_ks_level(),
compression_params.packing_ks_key_noise_distribution(),
pbs_params.ciphertext_modulus(),
&mut engine.encryption_generator,
)
});
let packing_key_switching_key = allocate_and_generate_new_lwe_packing_keyswitch_key(
&glwe_secret_key.as_lwe_secret_key(),
&self.post_packing_ks_key,
compression_params.packing_ks_base_log(),
compression_params.packing_ks_level(),
compression_params.packing_ks_key_noise_distribution(),
pbs_params.ciphertext_modulus(),
&mut engine.encryption_generator,
);
CompressionKey {
packing_key_switching_key,
@@ -75,6 +84,7 @@ impl CompressionPrivateKeys {
storage_log_modulus: compression_params.storage_log_modulus(),
}
}
pub(crate) fn new_compressed_compression_key(
&self,
glwe_secret_key: &GlweSecretKey<Vec<u64>>,

View File

@@ -78,6 +78,14 @@ impl ClientKey {
)
}
pub fn new_compression_key(
&self,
private_compression_key: &CompressionPrivateKeys,
) -> CompressionKey {
self.atomic_pattern
.new_compression_key(private_compression_key)
}
pub fn new_compression_decompression_keys(
&self,
private_compression_key: &CompressionPrivateKeys,

View File

@@ -0,0 +1,735 @@
use super::utils::noise_simulation::*;
use super::utils::traits::*;
use super::utils::{
expected_pfail_for_precision, mean_and_variance_check, normality_check, pfail_check,
precision_with_padding, DecryptionAndNoiseResult, NoiseSample, PfailAndPrecision,
PfailTestMeta, PfailTestResult,
};
use super::{should_run_short_pfail_tests_debug, should_use_single_key_debug};
use crate::shortint::atomic_pattern::AtomicPattern;
use crate::shortint::ciphertext::{Ciphertext, Degree, NoiseLevel};
use crate::shortint::client_key::atomic_pattern::AtomicPatternClientKey;
use crate::shortint::client_key::ClientKey;
use crate::shortint::engine::ShortintEngine;
use crate::shortint::list_compression::{CompressionKey, CompressionPrivateKeys};
use crate::shortint::parameters::test_params::{
TEST_COMP_PARAM_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M128,
TEST_PARAM_MESSAGE_2_CARRY_2_KS32_PBS_TUNIFORM_2M128,
TEST_PARAM_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M128,
};
use crate::shortint::parameters::{
AtomicPatternParameters, CarryModulus, CiphertextModulusLog, CompressionParameters,
MessageModulus, PBSParameters, Variance,
};
use crate::shortint::server_key::ServerKey;
use crate::shortint::{PaddingBit, ShortintEncoding};
use rayon::prelude::*;
#[allow(clippy::too_many_arguments)]
#[allow(clippy::type_complexity)]
pub fn br_dp_packing_ks_ms<
InputCt,
PBSResult,
PBSKey,
Accumulator,
DPScalar,
DPResult,
PackingKsk,
PackingKsResult,
MsResult,
Resources,
>(
input: Vec<InputCt>,
bsk: &PBSKey,
accumulator: &Accumulator,
scalar: DPScalar,
packing_ksk: &PackingKsk,
storage_modulus_log: CiphertextModulusLog,
side_resources: &mut [Resources],
) -> (
Vec<(InputCt, PBSResult, DPResult)>,
PackingKsResult,
MsResult,
)
where
Accumulator: AllocateLweBootstrapResult<Output = PBSResult, SideResources = Resources> + Sync,
PBSKey:
LweClassicFftBootstrap<InputCt, PBSResult, Accumulator, SideResources = Resources> + Sync,
PBSResult: ScalarMul<DPScalar, Output = DPResult, SideResources = Resources> + Send,
PackingKsk: AllocateLwePackingKeyswitchResult<Output = PackingKsResult, SideResources = Resources>
+ for<'a> LwePackingKeyswitch<[&'a DPResult], PackingKsResult, SideResources = Resources>,
PackingKsResult: AllocateStandardModSwitchResult<Output = MsResult, SideResources = Resources>
+ StandardModSwitch<MsResult, SideResources = Resources>,
InputCt: Send,
DPResult: Send,
DPScalar: Copy + Send + Sync,
Resources: Send,
{
let res: Vec<_> = input
.into_par_iter()
.zip(side_resources.par_iter_mut())
.map(|(input, side_resources)| {
let mut pbs_result = accumulator.allocate_lwe_bootstrap_result(side_resources);
bsk.lwe_classic_fft_pbs(&input, &mut pbs_result, accumulator, side_resources);
let after_dp = pbs_result.scalar_mul(scalar, side_resources);
(input, pbs_result, after_dp)
})
.collect();
let after_dp: Vec<_> = res
.iter()
.map(|(_input, _pbs_result, after_dp)| after_dp)
.collect();
let mut packing_result =
packing_ksk.allocate_lwe_packing_keyswitch_result(&mut side_resources[0]);
packing_ksk.keyswitch_lwes_and_pack_in_glwe(
after_dp.as_slice(),
&mut packing_result,
&mut side_resources[0],
);
let mut ms_result = packing_result.allocate_standard_mod_switch_result(&mut side_resources[0]);
packing_result.standard_mod_switch(storage_modulus_log, &mut ms_result, &mut side_resources[0]);
(res, packing_result, ms_result)
}
fn sanity_check_encrypt_br_dp_packing_ks_ms<P>(params: P, comp_params: CompressionParameters)
where
P: Into<AtomicPatternParameters>,
{
let params: AtomicPatternParameters = params.into();
let cks = ClientKey::new(params);
let sks = ServerKey::new(&cks);
let compression_private_key = cks.new_compression_private_key(comp_params);
let compression_key = cks.new_compression_key(&compression_private_key);
let lwe_per_glwe = compression_key.lwe_per_glwe;
// The multiplication done in the compression is made to move the message up at the top of the
// carry space, multiplying by the carry modulus achieves that
let dp_scalar = params.carry_modulus().0;
let br_input_modulus_log = sks.br_input_modulus_log();
let storage_modulus_log = compression_key.storage_log_modulus;
let id_lut = sks.generate_lookup_table(|x| x);
let input_zeros: Vec<_> = (0..lwe_per_glwe.0)
.map(|_| cks.encrypt_noiseless_pbs_input_dyn_lwe(br_input_modulus_log, 0))
.collect();
let mut side_resources = vec![(); input_zeros.len()];
let (before_packing, _after_packing, mut after_ms) = br_dp_packing_ks_ms(
input_zeros,
&sks,
&id_lut,
dp_scalar,
&compression_key,
storage_modulus_log,
&mut side_resources,
);
let compression_inputs: Vec<_> = before_packing
.into_iter()
.map(|(_input, pbs_result, _dp_result)| {
Ciphertext::new(
pbs_result.into_lwe_64(),
Degree::new(sks.message_modulus.0 - 1),
NoiseLevel::NOMINAL,
sks.message_modulus,
sks.carry_modulus,
sks.atomic_pattern.kind(),
)
})
.collect();
let compressed = compression_key.compress_ciphertexts_into_list(&compression_inputs);
let underlying_glwes = compressed.modulus_switched_glwe_ciphertext_list;
assert_eq!(underlying_glwes.len(), 1);
let extracted = underlying_glwes[0].extract();
// Bodies that were not filled are discarded
after_ms.get_mut_body().as_mut()[lwe_per_glwe.0..].fill(0);
assert_eq!(after_ms.as_view(), extracted.as_view());
}
#[test]
fn test_sanity_check_encrypt_br_dp_packing_ks_ms_test_param_message_2_carry_2_ks_pbs_tuniform_2m128(
) {
sanity_check_encrypt_br_dp_packing_ks_ms(
TEST_PARAM_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M128,
TEST_COMP_PARAM_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M128,
)
}
#[test]
fn test_sanity_check_encrypt_br_dp_packing_ks_ms_test_param_message_2_carry_2_ks32_pbs_tuniform_2m128(
) {
sanity_check_encrypt_br_dp_packing_ks_ms(
TEST_PARAM_MESSAGE_2_CARRY_2_KS32_PBS_TUNIFORM_2M128,
TEST_COMP_PARAM_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M128,
)
}
#[allow(clippy::type_complexity)]
fn encrypt_br_dp_packing_ks_ms_inner_helper(
params: AtomicPatternParameters,
comp_params: CompressionParameters,
single_cks: &ClientKey,
single_sks: &ServerKey,
single_compression_private_key: &CompressionPrivateKeys,
single_compression_key: &CompressionKey,
msg: u64,
) -> (
Vec<(
DecryptionAndNoiseResult,
DecryptionAndNoiseResult,
DecryptionAndNoiseResult,
)>,
Vec<DecryptionAndNoiseResult>,
Vec<DecryptionAndNoiseResult>,
) {
let mut engine = ShortintEngine::new();
let thread_cks;
let thread_sks;
let thread_compression_private_key;
let thread_compression_key;
let (cks, sks, compression_private_key, compression_key) = if should_use_single_key_debug() {
(
single_cks,
single_sks,
single_compression_private_key,
single_compression_key,
)
} else {
thread_cks = engine.new_client_key(params);
thread_sks = engine.new_server_key(&thread_cks);
thread_compression_private_key =
thread_cks.new_compression_private_key_with_engine(comp_params, &mut engine);
thread_compression_key = thread_cks.new_compression_key(&thread_compression_private_key);
(
&thread_cks,
&thread_sks,
&thread_compression_private_key,
&thread_compression_key,
)
};
let br_input_modulus_log = sks.br_input_modulus_log();
let lwe_per_glwe = compression_key.lwe_per_glwe;
let input_zeros: Vec<_> = (0..lwe_per_glwe.0)
.map(|_| {
cks.encrypt_noiseless_pbs_input_dyn_lwe_with_engine(
br_input_modulus_log,
msg,
&mut engine,
)
})
.collect();
let id_lut = sks.generate_lookup_table(|x| x);
let mut side_resources = vec![(); input_zeros.len()];
let dp_scalar = params.carry_modulus().0;
let storage_modulus_log = compression_key.storage_log_modulus;
let (before_packing, after_packing, after_ms) = br_dp_packing_ks_ms(
input_zeros,
sks,
&id_lut,
dp_scalar,
compression_key,
storage_modulus_log,
&mut side_resources,
);
let compute_large_lwe_secret_key = cks.encryption_key();
let compression_glwe_secret_key = &compression_private_key.post_packing_ks_key;
let compute_encoding = sks.encoding(PaddingBit::Yes);
let compression_encoding = ShortintEncoding {
carry_modulus: CarryModulus(1),
..compute_encoding
};
(
before_packing
.into_iter()
.map(|(input, pbs_result, dp_result)| {
(
match &cks.atomic_pattern {
AtomicPatternClientKey::Standard(standard_atomic_pattern_client_key) => {
DecryptionAndNoiseResult::new_from_lwe(
input.as_ref_64(),
&standard_atomic_pattern_client_key.lwe_secret_key,
msg,
&compute_encoding,
)
}
AtomicPatternClientKey::KeySwitch32(ks32_atomic_pattern_client_key) => {
let ks32_params = ks32_atomic_pattern_client_key.parameters;
let compute_encoding_32 = ShortintEncoding {
ciphertext_modulus: ks32_params.post_keyswitch_ciphertext_modulus,
message_modulus: ks32_params.message_modulus,
carry_modulus: ks32_params.carry_modulus,
padding_bit: PaddingBit::Yes,
};
DecryptionAndNoiseResult::new_from_lwe(
input.as_ref_32(),
&ks32_atomic_pattern_client_key.lwe_secret_key,
msg.try_into().unwrap(),
&compute_encoding_32,
)
}
},
DecryptionAndNoiseResult::new_from_lwe(
pbs_result.as_ref_64(),
&compute_large_lwe_secret_key,
msg,
&compute_encoding,
),
DecryptionAndNoiseResult::new_from_lwe(
dp_result.as_ref_64(),
&compute_large_lwe_secret_key,
msg,
&compression_encoding,
),
)
})
.collect(),
DecryptionAndNoiseResult::new_from_glwe(
&after_packing,
compression_glwe_secret_key,
compression_private_key.params.lwe_per_glwe(),
msg,
&compression_encoding,
),
DecryptionAndNoiseResult::new_from_glwe(
&after_ms,
compression_glwe_secret_key,
compression_private_key.params.lwe_per_glwe(),
msg,
&compression_encoding,
),
)
}
#[allow(clippy::type_complexity)]
fn encrypt_br_dp_packing_ks_ms_noise_helper(
params: AtomicPatternParameters,
comp_params: CompressionParameters,
single_cks: &ClientKey,
single_sks: &ServerKey,
single_compression_private_key: &CompressionPrivateKeys,
single_compression_key: &CompressionKey,
msg: u64,
) -> (
Vec<(NoiseSample, NoiseSample, NoiseSample)>,
Vec<NoiseSample>,
Vec<NoiseSample>,
) {
let (before_packing, after_packing, after_ms) = encrypt_br_dp_packing_ks_ms_inner_helper(
params,
comp_params,
single_cks,
single_sks,
single_compression_private_key,
single_compression_key,
msg,
);
(
before_packing
.into_iter()
.map(|(input, after_pbs, after_dp)| {
(
input
.get_noise_if_decryption_was_correct()
.expect("Decryption Failed"),
after_pbs
.get_noise_if_decryption_was_correct()
.expect("Decryption Failed"),
after_dp
.get_noise_if_decryption_was_correct()
.expect("Decryption Failed"),
)
})
.collect(),
after_packing
.into_iter()
.map(|x| {
x.get_noise_if_decryption_was_correct()
.expect("Decryption Failed")
})
.collect(),
after_ms
.into_iter()
.map(|x| {
x.get_noise_if_decryption_was_correct()
.expect("Decryption Failed")
})
.collect(),
)
}
#[allow(clippy::type_complexity)]
fn encrypt_br_dp_packing_ks_ms_pfail_helper(
params: AtomicPatternParameters,
comp_params: CompressionParameters,
single_cks: &ClientKey,
single_sks: &ServerKey,
single_compression_private_key: &CompressionPrivateKeys,
single_compression_key: &CompressionKey,
msg: u64,
) -> Vec<DecryptionAndNoiseResult> {
let (_before_packing, _after_packing, after_ms) = encrypt_br_dp_packing_ks_ms_inner_helper(
params,
comp_params,
single_cks,
single_sks,
single_compression_private_key,
single_compression_key,
msg,
);
after_ms
}
fn noise_check_encrypt_br_dp_packing_ks_ms_noise<P>(params: P, comp_params: CompressionParameters)
where
P: Into<AtomicPatternParameters>,
{
let params: AtomicPatternParameters = params.into();
let cks = ClientKey::new(params);
let sks = ServerKey::new(&cks);
let compression_private_key = cks.new_compression_private_key(comp_params);
let compression_key = cks.new_compression_key(&compression_private_key);
let noise_simulation_bsk =
NoiseSimulationLweFourierBsk::new_from_atomic_pattern_parameters(params);
let noise_simulation_packing_key =
NoiseSimulationLwePackingKeyswitchKey::new_from_comp_parameters(params, comp_params);
assert!(noise_simulation_bsk.matches_actual_shortint_server_key(&sks));
assert!(noise_simulation_packing_key.matches_actual_shortint_comp_key(&compression_key));
// The multiplication done in the compression is made to move the message up at the top of the
// carry space, multiplying by the carry modulus achieves that
let dp_scalar = params.carry_modulus().0;
let noise_simulation_accumulator = NoiseSimulationGlwe::new(
noise_simulation_bsk.output_glwe_size().to_glwe_dimension(),
noise_simulation_bsk.output_polynomial_size(),
Variance(0.0),
noise_simulation_bsk.modulus(),
);
let lwe_per_glwe = compression_key.lwe_per_glwe;
let storage_modulus_log = compression_key.storage_log_modulus;
let br_input_modulus_log = sks.br_input_modulus_log();
let (_before_packing_sim, _after_packing_sim, after_ms_sim) = {
let noise_simulation = NoiseSimulationLwe::new(
cks.parameters().lwe_dimension(),
Variance(0.0),
NoiseSimulationModulus::from_ciphertext_modulus(cks.parameters().ciphertext_modulus()),
);
br_dp_packing_ks_ms(
vec![noise_simulation; lwe_per_glwe.0],
&noise_simulation_bsk,
&noise_simulation_accumulator,
dp_scalar,
&noise_simulation_packing_key,
storage_modulus_log,
&mut vec![(); lwe_per_glwe.0],
)
};
let input_zeros: Vec<_> = (0..lwe_per_glwe.0)
.map(|_| cks.encrypt_noiseless_pbs_input_dyn_lwe(br_input_modulus_log, 0))
.collect();
let id_lut = sks.generate_lookup_table(|x| x);
let mut side_resources = vec![(); input_zeros.len()];
// Check that the circuit is correct with respect to core implementation, i.e. does not crash on
// dimension checks
let (expected_glwe_size_out, expected_polynomial_size_out, expected_modulus_f64_out) = {
let (_before_packing_sim, _after_packing, after_ms) = br_dp_packing_ks_ms(
input_zeros,
&sks,
&id_lut,
dp_scalar,
&compression_key,
storage_modulus_log,
&mut side_resources,
);
(
after_ms.glwe_size(),
after_ms.polynomial_size(),
after_ms.ciphertext_modulus().raw_modulus_float(),
)
};
assert_eq!(after_ms_sim.glwe_size(), expected_glwe_size_out);
assert_eq!(after_ms_sim.polynomial_size(), expected_polynomial_size_out);
assert_eq!(after_ms_sim.modulus().as_f64(), expected_modulus_f64_out);
let cleartext_modulus = params.message_modulus().0 * params.carry_modulus().0;
let mut noise_samples_before_ms = vec![];
let mut noise_samples_after_ms = vec![];
let sample_count_per_msg = 1000usize;
for _ in 0..cleartext_modulus {
let (current_noise_samples_before_ms, current_noise_samples_after_ms): (Vec<_>, Vec<_>) =
(0..sample_count_per_msg)
.into_par_iter()
.map(|_| {
let (_before_packing, after_packing, after_ms) =
encrypt_br_dp_packing_ks_ms_noise_helper(
params,
comp_params,
&cks,
&sks,
&compression_private_key,
&compression_key,
0,
);
(after_packing, after_ms)
})
.flatten()
.unzip();
noise_samples_before_ms
.extend(current_noise_samples_before_ms.into_iter().map(|x| x.value));
noise_samples_after_ms.extend(current_noise_samples_after_ms.into_iter().map(|x| x.value));
}
let before_ms_normality = normality_check(&noise_samples_before_ms, "before ms", 0.01);
let after_ms_is_ok = mean_and_variance_check(
&noise_samples_after_ms,
"after_ms",
0.0,
after_ms_sim.variance_per_occupied_slot(),
comp_params.packing_ks_key_noise_distribution(),
after_ms_sim
.glwe_dimension()
.to_equivalent_lwe_dimension(after_ms_sim.polynomial_size()),
after_ms_sim.modulus().as_f64(),
);
assert!(before_ms_normality.null_hypothesis_is_valid && after_ms_is_ok);
}
#[test]
fn test_noise_check_encrypt_br_dp_packing_ks_ms_noise_test_param_message_2_carry_2_ks_pbs_tuniform_2m128(
) {
noise_check_encrypt_br_dp_packing_ks_ms_noise(
TEST_PARAM_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M128,
TEST_COMP_PARAM_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M128,
)
}
#[test]
fn test_noise_check_encrypt_br_dp_packing_ks_ms_noise_test_param_message_2_carry_2_ks32_pbs_tuniform_2m128(
) {
noise_check_encrypt_br_dp_packing_ks_ms_noise(
TEST_PARAM_MESSAGE_2_CARRY_2_KS32_PBS_TUNIFORM_2M128,
TEST_COMP_PARAM_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M128,
)
}
fn noise_check_encrypt_br_dp_packing_ks_ms_pfail<P>(params: P, comp_params: CompressionParameters)
where
P: Into<AtomicPatternParameters>,
{
let (pfail_test_meta, params) = {
let mut params: AtomicPatternParameters = params.into();
let original_message_modulus = params.message_modulus();
let original_carry_modulus = params.carry_modulus();
// For now only allow 2_2 parameters, and see later for heuristics to use
assert_eq!(original_message_modulus.0, 4);
assert_eq!(original_carry_modulus.0, 4);
let noise_simulation_bsk =
NoiseSimulationLweFourierBsk::new_from_atomic_pattern_parameters(params);
let noise_simulation_packing_key =
NoiseSimulationLwePackingKeyswitchKey::new_from_comp_parameters(params, comp_params);
// The multiplication done in the compression is made to move the message up at the top of
// the carry space, multiplying by the carry modulus achieves that
let dp_scalar = params.carry_modulus().0;
let noise_simulation_accumulator = NoiseSimulationGlwe::new(
noise_simulation_bsk.output_glwe_size().to_glwe_dimension(),
noise_simulation_bsk.output_polynomial_size(),
Variance(0.0),
noise_simulation_bsk.modulus(),
);
let lwe_per_glwe = comp_params.lwe_per_glwe();
let storage_modulus_log = comp_params.storage_log_modulus();
let (_before_packing_sim, _after_packing_sim, after_ms_sim) = {
let noise_simulation = NoiseSimulationLwe::new(
params.lwe_dimension(),
Variance(0.0),
NoiseSimulationModulus::from_ciphertext_modulus(params.ciphertext_modulus()),
);
br_dp_packing_ks_ms(
vec![noise_simulation; lwe_per_glwe.0],
&noise_simulation_bsk,
&noise_simulation_accumulator,
dp_scalar,
&noise_simulation_packing_key,
storage_modulus_log,
&mut vec![(); lwe_per_glwe.0],
)
};
let expected_variance_after_storage = after_ms_sim.variance_per_occupied_slot();
let compression_carry_mod = CarryModulus(1);
let compression_message_mod = original_message_modulus;
let compression_precision_with_padding =
precision_with_padding(compression_message_mod, compression_carry_mod);
let expected_pfail_for_storage = expected_pfail_for_precision(
compression_precision_with_padding,
expected_variance_after_storage,
);
let original_pfail_and_precision = PfailAndPrecision::new(
expected_pfail_for_storage,
compression_message_mod,
compression_carry_mod,
);
let updated_message_mod = MessageModulus(1 << 6);
let updated_carry_mod = compression_carry_mod;
let updated_precision_with_padding =
precision_with_padding(updated_message_mod, updated_carry_mod);
let new_expected_pfail_for_storage = expected_pfail_for_precision(
updated_precision_with_padding,
expected_variance_after_storage,
);
let new_expected_pfail_and_precision = PfailAndPrecision::new(
new_expected_pfail_for_storage,
updated_message_mod,
updated_carry_mod,
);
// Here we update the message modulus only:
// - because the message modulus matches for the compression encoding and compute encoding
// - so that the carry modulus stays the same and we apply the same dot product as normal
// for 2_2
// - so that the effective encoding after the storage is the one we used to evaluate the
// pfail
// TODO: do something about this
match &mut params {
AtomicPatternParameters::Standard(pbsparameters) => match pbsparameters {
PBSParameters::PBS(classic_pbsparameters) => {
classic_pbsparameters.message_modulus = updated_message_mod
}
PBSParameters::MultiBitPBS(multi_bit_pbsparameters) => {
multi_bit_pbsparameters.message_modulus = updated_message_mod
}
},
AtomicPatternParameters::KeySwitch32(key_switch32_pbsparameters) => {
key_switch32_pbsparameters.message_modulus = updated_message_mod
}
}
let pfail_test_meta = if should_run_short_pfail_tests_debug() {
// To have the same amount of keys generated as the case where a single run is a single
// sample
let expected_fails = 200 * lwe_per_glwe.0 as u32;
PfailTestMeta::new_with_desired_expected_fails(
original_pfail_and_precision,
new_expected_pfail_and_precision,
expected_fails,
)
} else {
// To guarantee 1_000_000 keysets are generated
let total_runs = 1_000_000 * lwe_per_glwe.0 as u32;
PfailTestMeta::new_with_total_runs(
original_pfail_and_precision,
new_expected_pfail_and_precision,
total_runs,
)
};
(pfail_test_meta, params)
};
let cks = ClientKey::new(params);
let sks = ServerKey::new(&cks);
let compression_private_key = cks.new_compression_private_key(comp_params);
let compression_key = cks.new_compression_key(&compression_private_key);
let lwe_per_glwe = compression_key.lwe_per_glwe;
let total_runs_for_expected_fails = pfail_test_meta
.total_runs_for_expected_fails()
.div_ceil(lwe_per_glwe.0.try_into().unwrap());
println!(
"Actual runs with {} samples per run: {total_runs_for_expected_fails}",
lwe_per_glwe.0
);
let measured_fails: f64 = (0..total_runs_for_expected_fails)
.into_par_iter()
.map(|_| {
let after_ms_decryption_result = encrypt_br_dp_packing_ks_ms_pfail_helper(
params,
comp_params,
&cks,
&sks,
&compression_private_key,
&compression_key,
0,
);
after_ms_decryption_result
.into_iter()
.map(|x| x.failure_as_f64())
.sum::<f64>()
})
.sum();
let test_result = PfailTestResult { measured_fails };
pfail_check(&pfail_test_meta, test_result);
}
#[test]
fn test_noise_check_encrypt_br_dp_packing_ks_ms_pfail_test_param_message_2_carry_2_ks_pbs_tuniform_2m128(
) {
noise_check_encrypt_br_dp_packing_ks_ms_pfail(
TEST_PARAM_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M128,
TEST_COMP_PARAM_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M128,
)
}
#[test]
fn test_noise_check_encrypt_br_dp_packing_ks_ms_pfail_test_param_message_2_carry_2_ks32_pbs_tuniform_2m128(
) {
noise_check_encrypt_br_dp_packing_ks_ms_pfail(
TEST_PARAM_MESSAGE_2_CARRY_2_KS32_PBS_TUNIFORM_2M128,
TEST_COMP_PARAM_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M128,
)
}

View File

@@ -233,7 +233,7 @@ fn encrypt_decomp_br_rerand_dp_ks_any_ms_inner_helper(
let br_input_modulus_log = sks.br_input_modulus_log();
let modulus_switch_config = sks.noise_simulation_modulus_switch_config();
let ct = comp_private_key.encrypt_noiseless_decompression_input_dyn_lwe(cks, 0, &mut engine);
let ct = comp_private_key.encrypt_noiseless_decompression_input_dyn_lwe(cks, msg, &mut engine);
let cpk_ct_zero_rerand = {
let compact_list = cpk.encrypt_iter_with_modulus_with_engine(

View File

@@ -1,4 +1,5 @@
pub(crate) mod br_dp_ks_ms;
pub(crate) mod br_dp_packingks_ms;
pub(crate) mod br_rerand_dp_ks_ms;
pub(crate) mod cpk_ks_ms;
pub(crate) mod dp_ks_ms;

View File

@@ -35,6 +35,11 @@ use crate::shortint::parameters::{
AtomicPatternParameters, CarryModulus, MessageModulus, PBSParameters,
};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct PrecisionWithPadding {
value: u32,
}
pub fn normality_check(
noise_samples: &[f64],
check_location: &str,
@@ -187,7 +192,7 @@ pub fn encrypt_new_noiseless_lwe<
#[derive(Clone, Copy)]
pub struct PfailAndPrecision {
pfail: f64,
precision_with_padding: u32,
precision_with_padding: PrecisionWithPadding,
}
impl PfailAndPrecision {
@@ -215,7 +220,7 @@ impl PfailAndPrecision {
self.pfail
}
pub fn precision_with_padding(&self) -> u32 {
pub fn precision_with_padding(&self) -> PrecisionWithPadding {
self.precision_with_padding
}
}
@@ -310,9 +315,9 @@ pub fn pfail_check(pfail_test_meta: &PfailTestMeta, pfail_test_result: PfailTest
println!("expected_pfail={expected_pfail}");
let equivalent_measured_pfail = equivalent_pfail_gaussian_noise(
new_precision_with_padding,
new_precision_with_padding.value,
measured_pfail,
original_precision_with_padding,
original_precision_with_padding.value,
);
println!("equivalent_measured_pfail={equivalent_measured_pfail}");
@@ -336,14 +341,14 @@ pub fn pfail_check(pfail_test_meta: &PfailTestMeta, pfail_test_result: PfailTest
println!("pfail_upper_bound={pfail_upper_bound}");
let equivalent_pfail_lower_bound = equivalent_pfail_gaussian_noise(
new_precision_with_padding,
new_precision_with_padding.value,
pfail_lower_bound,
original_precision_with_padding,
original_precision_with_padding.value,
);
let equivalent_pfail_upper_bound = equivalent_pfail_gaussian_noise(
new_precision_with_padding,
new_precision_with_padding.value,
pfail_upper_bound,
original_precision_with_padding,
original_precision_with_padding.value,
);
println!("equivalent_pfail_lower_bound={equivalent_pfail_lower_bound}");
@@ -529,9 +534,9 @@ pub fn update_ap_params_for_pfail(
}
let new_expected_pfail = equivalent_pfail_gaussian_noise(
orig_pfail_and_precision.precision_with_padding(),
orig_pfail_and_precision.precision_with_padding().value,
orig_pfail_and_precision.pfail(),
precision_with_padding(ap_params.message_modulus(), ap_params.carry_modulus()),
precision_with_padding(ap_params.message_modulus(), ap_params.carry_modulus()).value,
);
let new_expected_log2_pfail = new_expected_pfail.log2();
@@ -560,8 +565,41 @@ pub fn update_ap_params_for_pfail(
(orig_pfail_and_precision, new_expected_pfail)
}
pub fn precision_with_padding(msg_mod: MessageModulus, carr_mod: CarryModulus) -> u32 {
let cleartext_modulus = msg_mod.0 * carr_mod.0;
pub fn precision_with_padding(
msg_mod: MessageModulus,
carry_mod: CarryModulus,
) -> PrecisionWithPadding {
let cleartext_modulus = msg_mod.0 * carry_mod.0;
assert!(cleartext_modulus.is_power_of_two());
cleartext_modulus.ilog2() + 1
PrecisionWithPadding {
value: cleartext_modulus.ilog2() + 1,
}
}
pub fn expected_pfail_for_precision(
precision_with_padding: PrecisionWithPadding,
variance: Variance,
) -> f64 {
// The additional 1 is to guarantee proper decryption
let precision_for_proper_decryption: i32 =
(precision_with_padding.value + 1).try_into().unwrap();
let correctness_threshold = 2.0f64.powi(-precision_for_proper_decryption);
let measured_std_dev = variance.get_standard_dev().0;
let measured_std_score = correctness_threshold / measured_std_dev;
statrs::function::erf::erfc(measured_std_score / core::f64::consts::SQRT_2)
}
#[test]
fn test_expected_pfail_for_ci_run_filter() {
// Practical check on a compression-like scenario, of interest because pfail is known to be very
// low
let precision_with_padding = precision_with_padding(MessageModulus(1 << 2), CarryModulus(1));
let theoretical_variance = Variance(1.0216297411906617e-5);
assert_eq!(
expected_pfail_for_precision(precision_with_padding, theoretical_variance).log2(),
-280.4295428516361
);
}

View File

@@ -30,7 +30,7 @@ use crate::shortint::key_switching_key::{
KeySwitchingKeyDestinationAtomicPattern, KeySwitchingKeyView,
};
use crate::shortint::list_compression::{
CompressionPrivateKeys, DecompressionKey, NoiseSquashingCompressionKey,
CompressionKey, CompressionPrivateKeys, DecompressionKey, NoiseSquashingCompressionKey,
};
use crate::shortint::noise_squashing::atomic_pattern::AtomicPatternNoiseSquashingKey;
use crate::shortint::noise_squashing::{
@@ -142,6 +142,33 @@ impl DynLwe {
Self::U128(lwe_ciphertext) => lwe_ciphertext.as_view(),
}
}
#[track_caller]
pub fn as_ref_32(&self) -> &LweCiphertextOwned<u32> {
match self {
Self::U32(lwe_ciphertext) => lwe_ciphertext,
Self::U64(_) => panic!("Tried getting a u64 LweCiphertext as u32."),
Self::U128(_) => panic!("Tried getting a u128 LweCiphertext as u32."),
}
}
#[track_caller]
pub fn as_ref_64(&self) -> &LweCiphertextOwned<u64> {
match self {
Self::U32(_) => panic!("Tried getting a u32 LweCiphertext as u64."),
Self::U64(lwe_ciphertext) => lwe_ciphertext,
Self::U128(_) => panic!("Tried getting a u128 LweCiphertext as u64."),
}
}
#[track_caller]
pub fn as_ref_128(&self) -> &LweCiphertextOwned<u128> {
match self {
Self::U32(_) => panic!("Tried getting a u32 LweCiphertext as u128."),
Self::U64(_) => panic!("Tried getting a u64 LweCiphertext as u128."),
Self::U128(lwe_ciphertext) => lwe_ciphertext,
}
}
}
impl<Scalar: CastInto<u32> + CastInto<u64> + CastInto<u128>> ScalarMul<Scalar> for DynLwe {
@@ -320,6 +347,17 @@ impl ClientKey {
&self,
modulus_log: CiphertextModulusLog,
msg: u64,
) -> DynLwe {
ShortintEngine::with_thread_local_mut(|engine| {
self.encrypt_noiseless_pbs_input_dyn_lwe_with_engine(modulus_log, msg, engine)
})
}
pub fn encrypt_noiseless_pbs_input_dyn_lwe_with_engine(
&self,
modulus_log: CiphertextModulusLog,
msg: u64,
engine: &mut ShortintEngine,
) -> DynLwe {
match &self.atomic_pattern {
AtomicPatternClientKey::Standard(standard_atomic_pattern_client_key) => {
@@ -331,15 +369,13 @@ impl ClientKey {
padding_bit: PaddingBit::Yes,
};
ShortintEngine::with_thread_local_mut(|engine| {
DynLwe::U64(encrypt_new_noiseless_lwe(
&standard_atomic_pattern_client_key.lwe_secret_key,
CiphertextModulus::try_new_power_of_2(modulus_log.0).unwrap(),
msg,
&encoding,
&mut engine.encryption_generator,
))
})
DynLwe::U64(encrypt_new_noiseless_lwe(
&standard_atomic_pattern_client_key.lwe_secret_key,
CiphertextModulus::try_new_power_of_2(modulus_log.0).unwrap(),
msg,
&encoding,
&mut engine.encryption_generator,
))
}
AtomicPatternClientKey::KeySwitch32(ks32_atomic_pattern_client_key) => {
let params = ks32_atomic_pattern_client_key.parameters;
@@ -350,15 +386,13 @@ impl ClientKey {
padding_bit: PaddingBit::Yes,
};
ShortintEngine::with_thread_local_mut(|engine| {
DynLwe::U32(encrypt_new_noiseless_lwe(
&ks32_atomic_pattern_client_key.lwe_secret_key,
CiphertextModulus::try_new_power_of_2(modulus_log.0).unwrap(),
msg.try_into().unwrap(),
&encoding,
&mut engine.encryption_generator,
))
})
DynLwe::U32(encrypt_new_noiseless_lwe(
&ks32_atomic_pattern_client_key.lwe_secret_key,
CiphertextModulus::try_new_power_of_2(modulus_log.0).unwrap(),
msg.try_into().unwrap(),
&encoding,
&mut engine.encryption_generator,
))
}
}
}
@@ -1351,6 +1385,35 @@ impl LweKeyswitch<DynLwe, DynLwe> for KeySwitchingKeyView<'_> {
}
}
impl AllocateLwePackingKeyswitchResult for CompressionKey {
type Output = GlweCiphertextOwned<u64>;
type SideResources = ();
fn allocate_lwe_packing_keyswitch_result(
&self,
side_resources: &mut Self::SideResources,
) -> Self::Output {
self.packing_key_switching_key
.allocate_lwe_packing_keyswitch_result(side_resources)
}
}
impl LwePackingKeyswitch<[&DynLwe], GlweCiphertextOwned<u64>> for CompressionKey {
type SideResources = ();
fn keyswitch_lwes_and_pack_in_glwe(
&self,
input: &[&DynLwe],
output: &mut GlweCiphertextOwned<u64>,
side_resources: &mut Self::SideResources,
) {
let input: Vec<_> = input.iter().map(|x| x.as_ref_64()).collect();
self.packing_key_switching_key
.keyswitch_lwes_and_pack_in_glwe(&input, output, side_resources);
}
}
impl LweClassicFftBootstrap<DynLwe, DynLwe, LookupTable<Vec<u64>>> for DecompressionKey {
type SideResources = ();
@@ -1688,16 +1751,41 @@ impl NoiseSimulationLwePackingKeyswitchKey {
squashing_lwe_dim,
noise_squashing_compression_params.packing_ks_base_log,
noise_squashing_compression_params.packing_ks_level,
noise_squashing_compression_params
.packing_ks_glwe_dimension
.to_glwe_size(),
noise_squashing_compression_params.packing_ks_glwe_dimension,
noise_squashing_compression_params.packing_ks_polynomial_size,
noise_squashing_compression_params.packing_ks_key_noise_distribution,
NoiseSimulationNoiseDistribution::U128(
noise_squashing_compression_params.packing_ks_key_noise_distribution,
),
NoiseSimulationModulus::from_ciphertext_modulus(
noise_squashing_compression_params.ciphertext_modulus,
),
)
}
pub fn new_from_comp_parameters(
params: AtomicPatternParameters,
compression_params: CompressionParameters,
) -> Self {
let params_big_lwe_dim = params
.glwe_dimension()
.to_equivalent_lwe_dimension(params.polynomial_size());
Self::new(
params_big_lwe_dim,
compression_params.packing_ks_base_log(),
compression_params.packing_ks_level(),
compression_params.packing_ks_glwe_dimension(),
compression_params.packing_ks_polynomial_size(),
NoiseSimulationNoiseDistribution::U64(
compression_params.packing_ks_key_noise_distribution(),
),
NoiseSimulationModulus::from_ciphertext_modulus(params.ciphertext_modulus()),
)
}
pub fn matches_actual_shortint_comp_key(&self, comp_key: &CompressionKey) -> bool {
self.matches_actual_pksk(&comp_key.packing_key_switching_key)
}
}
impl NoiseSimulationLweKeyswitchKey {