Skip to content

Commit 27d936a

Browse files
committed
test(hl): add noise check test for encryption from HL API
1 parent 014e002 commit 27d936a

File tree

7 files changed

+244
-3
lines changed

7 files changed

+244
-3
lines changed

tfhe/src/core_crypto/algorithms/test/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ mod lwe_packing_keyswitch_key_generation;
2424
mod lwe_private_functional_packing_keyswitch;
2525
pub(crate) mod lwe_programmable_bootstrapping;
2626
mod modulus_switch_compression;
27-
mod noise_distribution;
27+
pub(crate) mod noise_distribution;
2828

2929
pub struct TestResources {
3030
pub seeder: Box<dyn Seeder>,

tfhe/src/core_crypto/algorithms/test/noise_distribution/lwe_encryption_noise.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ create_parameterized_test!(lwe_encrypt_decrypt_noise_distribution_custom_mod {
8888
TEST_PARAMS_3_BITS_63_U64
8989
});
9090

91-
fn lwe_compact_public_key_encryption_expected_variance(
91+
pub(crate) fn lwe_compact_public_key_encryption_expected_variance(
9292
input_noise: impl DispersionParameter,
9393
lwe_dimension: LweDimension,
9494
) -> Variance {

tfhe/src/core_crypto/algorithms/test/noise_distribution/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use super::*;
22

3-
mod lwe_encryption_noise;
3+
pub(crate) mod lwe_encryption_noise;
44
mod lwe_keyswitch_noise;
55
// We are having crashes on aarch64 at the moment, problem is the code paths are not the same
66
// between archs, so we disable those on the Apple M1

tfhe/src/core_crypto/commons/math/random/t_uniform.rs

+12
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use crate::core_crypto::backward_compatibility::commons::math::random::TUniformVersions;
22

33
use super::*;
4+
use crate::core_crypto::commons::dispersion::Variance;
45
use serde::{Deserialize, Serialize};
56
use tfhe_versionable::Versionize;
67

@@ -70,6 +71,17 @@ impl<T: UnsignedInteger> TUniform<T> {
7071
pub fn max_value_inclusive(&self) -> T::Signed {
7172
T::Signed::ONE << self.bound_log2 as usize
7273
}
74+
75+
// TODO: return the ModularVariance? Essentially the first part of the formula corresponds to
76+
// what older iterations of the lib called ModularVariance, this struct does not currently have
77+
// the DispersionParameter trait implemented, so here for convenience we compute the Variance
78+
// directly by dividing the ModularVariance by the modulus squared to be in the Torus domain.
79+
pub(crate) fn variance(&self, modulus: f64) -> Variance {
80+
// (2^{2 * b + 1} + 1) / 6
81+
Variance(
82+
((2.0f64.powi(2 * (self.bound_log2 as i32) + 1) + 1.0f64) / 6.0f64) * modulus.powi(-2),
83+
)
84+
}
7385
}
7486

7587
macro_rules! implement_t_uniform_uint {

tfhe/src/high_level_api/tests/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
mod noise_distribution;
12
mod tags_on_entities;
23

34
use crate::high_level_api::prelude::*;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
1+
use shortint::parameters::compact_public_key_only::p_fail_2_minus_64::ks_pbs::{
2+
V0_11_PARAM_PKE_TO_BIG_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M64_ZKV1,
3+
V0_11_PARAM_PKE_TO_SMALL_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M64_ZKV1,
4+
};
5+
use shortint::parameters::key_switching::p_fail_2_minus_64::ks_pbs::{
6+
V0_11_PARAM_KEYSWITCH_PKE_TO_BIG_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M64_ZKV1,
7+
V0_11_PARAM_KEYSWITCH_PKE_TO_SMALL_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M64_ZKV1,
8+
};
9+
use shortint::parameters::{CompactPublicKeyEncryptionParameters, ShortintKeySwitchingParameters};
10+
use shortint::ClassicPBSParameters;
11+
12+
use crate::core_crypto::algorithms::lwe_encryption::decrypt_lwe_ciphertext;
13+
use crate::core_crypto::algorithms::test::noise_distribution::lwe_encryption_noise::lwe_compact_public_key_encryption_expected_variance;
14+
use crate::core_crypto::commons::math::random::DynamicDistribution;
15+
use crate::core_crypto::commons::test_tools::{variance, variance_confidence_interval};
16+
use crate::prelude::*;
17+
use crate::shortint::parameters::classic::tuniform::p_fail_2_minus_64::ks_pbs::PARAM_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M64;
18+
use crate::shortint::parameters::compact_public_key_only::p_fail_2_minus_64::ks_pbs::V0_11_PARAM_PKE_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M64;
19+
use crate::shortint::parameters::compact_public_key_only::CompactCiphertextListExpansionKind;
20+
use crate::shortint::parameters::key_switching::p_fail_2_minus_64::ks_pbs::V0_11_PARAM_KEYSWITCH_PKE_TO_SMALL_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M64;
21+
use crate::shortint::parameters::ShortintParameterSet;
22+
use crate::*;
23+
24+
#[test]
25+
fn test_noise_check_secret_key_encryption_noise_tuniform() {
26+
let params_as_shortint_parameter_set = ShortintParameterSet::new_pbs_param_set(
27+
PARAM_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M64.into(),
28+
);
29+
30+
let modulus_as_f64 = if params_as_shortint_parameter_set
31+
.ciphertext_modulus()
32+
.is_native_modulus()
33+
{
34+
2.0f64.powi(u64::BITS as i32)
35+
} else {
36+
params_as_shortint_parameter_set
37+
.ciphertext_modulus()
38+
.get_custom_modulus() as f64
39+
};
40+
41+
let encryption_noise = params_as_shortint_parameter_set.encryption_noise_distribution();
42+
let (inclusive_min_val, inclusive_max_val, expected_variance) = match encryption_noise {
43+
DynamicDistribution::Gaussian(_gaussian) => {
44+
panic!("This test is written for TUniform, wrong parameter set used")
45+
}
46+
DynamicDistribution::TUniform(tuniform) => (
47+
tuniform.min_value_inclusive(),
48+
tuniform.max_value_inclusive(),
49+
tuniform.variance(modulus_as_f64),
50+
),
51+
};
52+
53+
let expected_encryption_noise = match params_as_shortint_parameter_set.encryption_key_choice() {
54+
shortint::EncryptionKeyChoice::Big => {
55+
params_as_shortint_parameter_set.glwe_noise_distribution()
56+
}
57+
shortint::EncryptionKeyChoice::Small => {
58+
params_as_shortint_parameter_set.lwe_noise_distribution()
59+
}
60+
};
61+
62+
assert_eq!(encryption_noise, expected_encryption_noise);
63+
64+
let config =
65+
ConfigBuilder::with_custom_parameters(PARAM_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M64).build();
66+
67+
const NB_TEST: usize = 16000;
68+
69+
let mut noise_samples = Vec::with_capacity(NB_TEST);
70+
for _ in 0..NB_TEST {
71+
let cks = ClientKey::generate(config);
72+
let encrypted = FheUint2::encrypt(0u32, &cks);
73+
74+
// Drop to lower level APIs to get to the noise
75+
let integer_key = cks.into_raw_parts().0;
76+
let shortint_key: &crate::shortint::ClientKey = integer_key.as_ref();
77+
78+
let radix = encrypted.into_raw_parts().0;
79+
assert!(radix.blocks.len() == 1);
80+
let shortint_block = radix.blocks.into_iter().next().unwrap();
81+
82+
let noise = shortint_key.decrypt_no_decode(&shortint_block).0;
83+
let signed_noise: i64 = noise as i64;
84+
85+
assert!(
86+
signed_noise >= inclusive_min_val,
87+
"{signed_noise} is not >= {inclusive_min_val}"
88+
);
89+
90+
assert!(
91+
signed_noise <= inclusive_max_val,
92+
"{signed_noise} is not <= {inclusive_max_val}"
93+
);
94+
95+
// Rescale
96+
noise_samples.push(signed_noise as f64 / modulus_as_f64);
97+
}
98+
99+
let measured_variance = variance(&noise_samples);
100+
let measured_confidence_interval =
101+
variance_confidence_interval(noise_samples.len() as f64, measured_variance, 0.999);
102+
103+
// For --no-capture inspection
104+
println!("measured_variance={measured_variance:?}");
105+
println!("expected_variance={expected_variance:?}");
106+
println!(
107+
"lower_bound={:?}",
108+
measured_confidence_interval.lower_bound()
109+
);
110+
println!(
111+
"upper_bound={:?}",
112+
measured_confidence_interval.upper_bound()
113+
);
114+
115+
assert!(measured_confidence_interval.variance_is_in_interval(expected_variance));
116+
}
117+
118+
#[test]
119+
fn test_noise_check_compact_public_key_encryption_noise_tuniform() {
120+
noise_check_compact_public_key_encryption_noise_tuniform(
121+
V0_11_PARAM_PKE_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M64,
122+
V0_11_PARAM_KEYSWITCH_PKE_TO_SMALL_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M64,
123+
PARAM_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M64,
124+
)
125+
}
126+
127+
#[test]
128+
fn test_noise_check_compact_public_key_encryption_noise_tuniform_zkv1_small() {
129+
noise_check_compact_public_key_encryption_noise_tuniform(
130+
V0_11_PARAM_PKE_TO_SMALL_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M64_ZKV1,
131+
V0_11_PARAM_KEYSWITCH_PKE_TO_SMALL_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M64_ZKV1,
132+
PARAM_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M64,
133+
)
134+
}
135+
136+
#[test]
137+
fn test_noise_check_compact_public_key_encryption_noise_tuniform_zkv1_big() {
138+
noise_check_compact_public_key_encryption_noise_tuniform(
139+
V0_11_PARAM_PKE_TO_BIG_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M64_ZKV1,
140+
V0_11_PARAM_KEYSWITCH_PKE_TO_BIG_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M64_ZKV1,
141+
PARAM_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M64,
142+
)
143+
}
144+
145+
fn noise_check_compact_public_key_encryption_noise_tuniform(
146+
mut cpke_params: CompactPublicKeyEncryptionParameters,
147+
ksk_params: ShortintKeySwitchingParameters,
148+
block_params: ClassicPBSParameters,
149+
) {
150+
// Hack to avoid server key needs and get the ciphertext directly
151+
cpke_params.expansion_kind =
152+
CompactCiphertextListExpansionKind::NoCasting(block_params.encryption_key_choice.into());
153+
154+
let modulus_as_f64 = if cpke_params.ciphertext_modulus.is_native_modulus() {
155+
2.0f64.powi(u64::BITS as i32)
156+
} else {
157+
cpke_params.ciphertext_modulus.get_custom_modulus() as f64
158+
};
159+
160+
let encryption_noise = cpke_params.encryption_noise_distribution;
161+
let encryption_variance = match encryption_noise {
162+
DynamicDistribution::Gaussian(_gaussian) => {
163+
panic!("This test is written for TUniform, wrong parameter set used")
164+
}
165+
DynamicDistribution::TUniform(tuniform) => tuniform.variance(modulus_as_f64),
166+
};
167+
168+
let expected_variance = lwe_compact_public_key_encryption_expected_variance(
169+
encryption_variance,
170+
cpke_params.encryption_lwe_dimension,
171+
);
172+
173+
let config = ConfigBuilder::with_custom_parameters(block_params)
174+
.use_dedicated_compact_public_key_parameters((cpke_params, ksk_params))
175+
.build();
176+
177+
const NB_TEST: usize = 16000;
178+
179+
let mut noise_samples = Vec::with_capacity(NB_TEST);
180+
for _ in 0..NB_TEST {
181+
let cks = ClientKey::generate(config);
182+
let cpk = CompactPublicKey::new(&cks);
183+
184+
let mut builder = CompactCiphertextList::builder(&cpk);
185+
builder
186+
.push_with_num_bits(0u32, cpke_params.message_modulus.0.ilog2() as usize)
187+
.unwrap();
188+
let list = builder.build();
189+
let expanded = list.expand().unwrap();
190+
let encrypted: FheUint2 = expanded.get(0).unwrap().unwrap();
191+
192+
// Drop to lower level APIs to get to the noise
193+
let integer_key = cks.into_raw_parts().1.unwrap().0;
194+
let shortint_key = integer_key.into_raw_parts();
195+
let core_key = shortint_key.into_raw_parts().0;
196+
197+
let radix = encrypted.into_raw_parts().0;
198+
assert!(radix.blocks.len() == 1);
199+
let shortint_block = radix.blocks.into_iter().next().unwrap();
200+
let lwe_ct = shortint_block.ct;
201+
202+
// This is directly the noise as we encrypted a 0
203+
let noise = decrypt_lwe_ciphertext(&core_key, &lwe_ct).0;
204+
let signed_noise: i64 = noise as i64;
205+
206+
// Rescale
207+
noise_samples.push(signed_noise as f64 / modulus_as_f64);
208+
}
209+
210+
let measured_variance = variance(&noise_samples);
211+
let measured_confidence_interval =
212+
variance_confidence_interval(noise_samples.len() as f64, measured_variance, 0.999);
213+
214+
// For --no-capture inspection
215+
println!("measured_variance={measured_variance:?}");
216+
println!("expected_variance={expected_variance:?}");
217+
println!(
218+
"lower_bound={:?}",
219+
measured_confidence_interval.lower_bound()
220+
);
221+
println!(
222+
"upper_bound={:?}",
223+
measured_confidence_interval.upper_bound()
224+
);
225+
226+
assert!(measured_confidence_interval.variance_is_in_interval(expected_variance));
227+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
mod encryption_noise;

0 commit comments

Comments
 (0)