-
Notifications
You must be signed in to change notification settings - Fork 151
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
test(core): add noise formulas and variance tests for KS and PBS
- Loading branch information
1 parent
07045f1
commit 50de8b2
Showing
8 changed files
with
502 additions
and
0 deletions.
There are no files selected for viewing
150 changes: 150 additions & 0 deletions
150
tfhe/src/core_crypto/algorithms/test/noise_distribution/lwe_keyswitch_noise.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,150 @@ | ||
use super::*; | ||
use crate::core_crypto::commons::noise_formulas::lwe_keyswitch::keyswitch_additive_variance_132_bits_security_gaussian; | ||
use crate::core_crypto::commons::test_tools::{torus_modular_diff, variance}; | ||
use rayon::prelude::*; | ||
|
||
// This is 1 / 16 which is exactly representable in an f64 (even an f32) | ||
// 1 / 32 is too strict and fails the tests | ||
const RELATIVE_TOLERANCE: f64 = 0.0625; | ||
|
||
const NB_TESTS: usize = 1000; | ||
|
||
fn lwe_encrypt_ks_decrypt_noise_distribution_custom_mod<Scalar: UnsignedTorus + CastInto<usize>>( | ||
params: ClassicTestParams<Scalar>, | ||
) { | ||
let lwe_dimension = params.lwe_dimension; | ||
let lwe_noise_distribution = params.lwe_noise_distribution; | ||
let glwe_noise_distribution = params.glwe_noise_distribution; | ||
let ciphertext_modulus = params.ciphertext_modulus; | ||
let message_modulus_log = params.message_modulus_log; | ||
let encoding_with_padding = get_encoding_with_padding(ciphertext_modulus); | ||
let glwe_dimension = params.glwe_dimension; | ||
let polynomial_size = params.polynomial_size; | ||
let ks_decomp_base_log = params.ks_base_log; | ||
let ks_decomp_level_count = params.ks_level; | ||
|
||
let input_lwe_dimension = glwe_dimension.to_equivalent_lwe_dimension(polynomial_size); | ||
let output_lwe_dimension = lwe_dimension; | ||
|
||
let modulus_as_f64 = if ciphertext_modulus.is_native_modulus() { | ||
2.0f64.powi(Scalar::BITS as i32) | ||
} else { | ||
ciphertext_modulus.get_custom_modulus() as f64 | ||
}; | ||
|
||
let encryption_variance = Variance(glwe_noise_distribution.gaussian_std_dev().get_variance()); | ||
let expected_variance = Variance( | ||
encryption_variance.0 | ||
+ keyswitch_additive_variance_132_bits_security_gaussian( | ||
input_lwe_dimension, | ||
output_lwe_dimension, | ||
ks_decomp_base_log, | ||
ks_decomp_level_count, | ||
modulus_as_f64, | ||
) | ||
.0, | ||
); | ||
|
||
let mut rsc = TestResources::new(); | ||
|
||
let msg_modulus = Scalar::ONE.shl(message_modulus_log.0); | ||
let mut msg = msg_modulus; | ||
let delta: Scalar = encoding_with_padding / msg_modulus; | ||
|
||
let num_samples = NB_TESTS * <Scalar as CastInto<usize>>::cast_into(msg); | ||
let mut noise_samples = Vec::with_capacity(num_samples); | ||
|
||
let lwe_sk = allocate_and_generate_new_binary_lwe_secret_key( | ||
lwe_dimension, | ||
&mut rsc.secret_random_generator, | ||
); | ||
|
||
let glwe_sk = allocate_and_generate_new_binary_glwe_secret_key( | ||
glwe_dimension, | ||
polynomial_size, | ||
&mut rsc.secret_random_generator, | ||
); | ||
|
||
let big_lwe_sk = glwe_sk.into_lwe_secret_key(); | ||
|
||
let ksk_big_to_small = allocate_and_generate_new_lwe_keyswitch_key( | ||
&big_lwe_sk, | ||
&lwe_sk, | ||
ks_decomp_base_log, | ||
ks_decomp_level_count, | ||
lwe_noise_distribution, | ||
ciphertext_modulus, | ||
&mut rsc.encryption_random_generator, | ||
); | ||
|
||
assert!(check_encrypted_content_respects_mod( | ||
&ksk_big_to_small, | ||
ciphertext_modulus | ||
)); | ||
|
||
while msg != Scalar::ZERO { | ||
msg = msg.wrapping_sub(Scalar::ONE); | ||
let current_run_samples: Vec<_> = (0..NB_TESTS) | ||
.into_par_iter() | ||
.map(|_| { | ||
let mut rsc = TestResources::new(); | ||
|
||
let plaintext = Plaintext(msg * delta); | ||
|
||
let ct = allocate_and_encrypt_new_lwe_ciphertext( | ||
&big_lwe_sk, | ||
plaintext, | ||
glwe_noise_distribution, | ||
ciphertext_modulus, | ||
&mut rsc.encryption_random_generator, | ||
); | ||
|
||
assert!(check_encrypted_content_respects_mod( | ||
&ct, | ||
ciphertext_modulus | ||
)); | ||
|
||
let mut output_ct = LweCiphertext::new( | ||
Scalar::ZERO, | ||
lwe_sk.lwe_dimension().to_lwe_size(), | ||
ciphertext_modulus, | ||
); | ||
|
||
keyswitch_lwe_ciphertext(&ksk_big_to_small, &ct, &mut output_ct); | ||
|
||
assert!(check_encrypted_content_respects_mod( | ||
&output_ct, | ||
ciphertext_modulus | ||
)); | ||
|
||
let decrypted = decrypt_lwe_ciphertext(&lwe_sk, &output_ct); | ||
|
||
let decoded = round_decode(decrypted.0, delta) % msg_modulus; | ||
|
||
assert_eq!(msg, decoded); | ||
|
||
torus_modular_diff(plaintext.0, decrypted.0, ciphertext_modulus) | ||
}) | ||
.collect(); | ||
|
||
noise_samples.extend(current_run_samples); | ||
} | ||
|
||
let measured_variance = variance(&noise_samples); | ||
let var_abs_diff = (expected_variance.0 - measured_variance.0).abs(); | ||
let tolerance_threshold = RELATIVE_TOLERANCE * expected_variance.0; | ||
// Have a log even if it's a test to have a trace in no capture mode to eyeball variances | ||
println!("measured_variance={measured_variance:?}"); | ||
println!("expected_variance={expected_variance:?}"); | ||
assert!( | ||
var_abs_diff < tolerance_threshold, | ||
"Absolute difference for variance: {var_abs_diff}, \ | ||
tolerance threshold: {tolerance_threshold}, \ | ||
got variance: {measured_variance:?}, \ | ||
expected variance: {expected_variance:?}" | ||
); | ||
} | ||
|
||
create_parametrized_test!(lwe_encrypt_ks_decrypt_noise_distribution_custom_mod { | ||
NOISE_TEST_PARAMS_4_BITS_NATIVE_U64_132_BITS_GAUSSIAN, | ||
}); |
184 changes: 184 additions & 0 deletions
184
...src/core_crypto/algorithms/test/noise_distribution/lwe_programmable_boostrapping_noise.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,184 @@ | ||
use super::*; | ||
|
||
use crate::core_crypto::commons::noise_formulas::lwe_programmable_bootstrap::pbs_variance_132_bits_security_gaussian; | ||
use crate::core_crypto::commons::test_tools::{torus_modular_diff, variance}; | ||
use rayon::prelude::*; | ||
|
||
// This is 1 / 16 which is exactly representable in an f64 (even an f32) | ||
// 1 / 32 is too strict and fails the tests | ||
const RELATIVE_TOLERANCE: f64 = 0.0625; | ||
|
||
const NB_TESTS: usize = 1000; | ||
|
||
fn lwe_encrypt_pbs_decrypt_custom_mod<Scalar>(params: ClassicTestParams<Scalar>) | ||
where | ||
Scalar: UnsignedTorus + Sync + Send + CastFrom<usize> + CastInto<usize>, | ||
{ | ||
let input_lwe_dimension = params.lwe_dimension; | ||
let lwe_noise_distribution = params.lwe_noise_distribution; | ||
let glwe_noise_distribution = params.glwe_noise_distribution; | ||
let ciphertext_modulus = params.ciphertext_modulus; | ||
let message_modulus_log = params.message_modulus_log; | ||
let msg_modulus = Scalar::ONE.shl(message_modulus_log.0); | ||
let encoding_with_padding = get_encoding_with_padding(ciphertext_modulus); | ||
let glwe_dimension = params.glwe_dimension; | ||
let polynomial_size = params.polynomial_size; | ||
let pbs_decomposition_base_log = params.pbs_base_log; | ||
let pbs_decomposition_level_count = params.pbs_level; | ||
|
||
let modulus_as_f64 = if ciphertext_modulus.is_native_modulus() { | ||
2.0f64.powi(Scalar::BITS as i32) | ||
} else { | ||
ciphertext_modulus.get_custom_modulus() as f64 | ||
}; | ||
|
||
let expected_variance = Variance( | ||
pbs_variance_132_bits_security_gaussian( | ||
input_lwe_dimension, | ||
glwe_dimension, | ||
polynomial_size, | ||
pbs_decomposition_base_log, | ||
pbs_decomposition_level_count, | ||
modulus_as_f64, | ||
) | ||
.0, | ||
); | ||
|
||
let mut rsc = TestResources::new(); | ||
|
||
let f = |x: Scalar| x; | ||
|
||
let delta: Scalar = encoding_with_padding / msg_modulus; | ||
let mut msg = msg_modulus; | ||
|
||
let num_samples = NB_TESTS * <Scalar as CastInto<usize>>::cast_into(msg); | ||
let mut noise_samples = Vec::with_capacity(num_samples); | ||
|
||
let input_lwe_secret_key = allocate_and_generate_new_binary_lwe_secret_key( | ||
input_lwe_dimension, | ||
&mut rsc.secret_random_generator, | ||
); | ||
|
||
let output_glwe_secret_key = allocate_and_generate_new_binary_glwe_secret_key( | ||
glwe_dimension, | ||
polynomial_size, | ||
&mut rsc.secret_random_generator, | ||
); | ||
|
||
let output_lwe_secret_key = output_glwe_secret_key.as_lwe_secret_key(); | ||
|
||
let fbsk = { | ||
let bsk = allocate_and_generate_new_lwe_bootstrap_key( | ||
&input_lwe_secret_key, | ||
&output_glwe_secret_key, | ||
pbs_decomposition_base_log, | ||
pbs_decomposition_level_count, | ||
glwe_noise_distribution, | ||
ciphertext_modulus, | ||
&mut rsc.encryption_random_generator, | ||
); | ||
|
||
assert!(check_encrypted_content_respects_mod( | ||
&*bsk, | ||
ciphertext_modulus | ||
)); | ||
|
||
let mut fbsk = FourierLweBootstrapKey::new( | ||
bsk.input_lwe_dimension(), | ||
bsk.glwe_size(), | ||
bsk.polynomial_size(), | ||
bsk.decomposition_base_log(), | ||
bsk.decomposition_level_count(), | ||
); | ||
|
||
par_convert_standard_lwe_bootstrap_key_to_fourier(&bsk, &mut fbsk); | ||
|
||
fbsk | ||
}; | ||
|
||
let accumulator = generate_programmable_bootstrap_glwe_lut( | ||
polynomial_size, | ||
glwe_dimension.to_glwe_size(), | ||
msg_modulus.cast_into(), | ||
ciphertext_modulus, | ||
delta, | ||
f, | ||
); | ||
|
||
assert!(check_encrypted_content_respects_mod( | ||
&accumulator, | ||
ciphertext_modulus | ||
)); | ||
|
||
while msg != Scalar::ZERO { | ||
msg = msg.wrapping_sub(Scalar::ONE); | ||
|
||
let current_run_samples: Vec<_> = (0..NB_TESTS) | ||
.into_par_iter() | ||
.map(|_| { | ||
let mut rsc = TestResources::new(); | ||
|
||
let plaintext = Plaintext(msg * delta); | ||
|
||
let lwe_ciphertext_in = allocate_and_encrypt_new_lwe_ciphertext( | ||
&input_lwe_secret_key, | ||
plaintext, | ||
lwe_noise_distribution, | ||
ciphertext_modulus, | ||
&mut rsc.encryption_random_generator, | ||
); | ||
|
||
assert!(check_encrypted_content_respects_mod( | ||
&lwe_ciphertext_in, | ||
ciphertext_modulus | ||
)); | ||
|
||
let mut out_pbs_ct = LweCiphertext::new( | ||
Scalar::ZERO, | ||
output_lwe_secret_key.lwe_dimension().to_lwe_size(), | ||
ciphertext_modulus, | ||
); | ||
|
||
programmable_bootstrap_lwe_ciphertext( | ||
&lwe_ciphertext_in, | ||
&mut out_pbs_ct, | ||
&accumulator, | ||
&fbsk, | ||
); | ||
|
||
assert!(check_encrypted_content_respects_mod( | ||
&out_pbs_ct, | ||
ciphertext_modulus | ||
)); | ||
|
||
let decrypted = decrypt_lwe_ciphertext(&output_lwe_secret_key, &out_pbs_ct); | ||
|
||
let decoded = round_decode(decrypted.0, delta) % msg_modulus; | ||
|
||
assert_eq!(decoded, f(msg)); | ||
|
||
torus_modular_diff(plaintext.0, decrypted.0, ciphertext_modulus) | ||
}) | ||
.collect(); | ||
|
||
noise_samples.extend(current_run_samples); | ||
} | ||
|
||
let measured_variance = variance(&noise_samples); | ||
let var_abs_diff = (expected_variance.0 - measured_variance.0).abs(); | ||
let tolerance_threshold = RELATIVE_TOLERANCE * expected_variance.0; | ||
// Have a log even if it's a test to have a trace in no capture mode to eyeball variances | ||
println!("measured_variance={measured_variance:?}"); | ||
println!("expected_variance={expected_variance:?}"); | ||
assert!( | ||
var_abs_diff < tolerance_threshold, | ||
"Absolute difference for variance: {var_abs_diff}, \ | ||
tolerance threshold: {tolerance_threshold}, \ | ||
got variance: {measured_variance:?}, \ | ||
expected variance: {expected_variance:?}" | ||
); | ||
} | ||
|
||
create_parametrized_test!(lwe_encrypt_pbs_decrypt_custom_mod { | ||
NOISE_TEST_PARAMS_4_BITS_NATIVE_U64_132_BITS_GAUSSIAN | ||
}); |
27 changes: 27 additions & 0 deletions
27
tfhe/src/core_crypto/algorithms/test/noise_distribution/mod.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,30 @@ | ||
use super::*; | ||
|
||
mod lwe_encryption_noise; | ||
mod lwe_keyswitch_noise; | ||
mod lwe_programmable_boostrapping_noise; | ||
|
||
#[allow(clippy::excessive_precision)] | ||
pub const NOISE_TEST_PARAMS_4_BITS_NATIVE_U64_132_BITS_GAUSSIAN: ClassicTestParams<u64> = | ||
ClassicTestParams { | ||
lwe_dimension: LweDimension(841), | ||
glwe_dimension: GlweDimension(1), | ||
polynomial_size: PolynomialSize(2048), | ||
lwe_noise_distribution: DynamicDistribution::new_gaussian_from_std_dev(StandardDev( | ||
3.1496674685772435e-06, | ||
)), | ||
glwe_noise_distribution: DynamicDistribution::new_gaussian_from_std_dev(StandardDev( | ||
2.845267479601915e-15, | ||
)), | ||
pbs_base_log: DecompositionBaseLog(22), | ||
pbs_level: DecompositionLevelCount(1), | ||
ks_level: DecompositionLevelCount(5), | ||
ks_base_log: DecompositionBaseLog(3), | ||
pfks_level: DecompositionLevelCount(0), | ||
pfks_base_log: DecompositionBaseLog(0), | ||
pfks_noise_distribution: DynamicDistribution::new_gaussian_from_std_dev(StandardDev(0.0)), | ||
cbs_level: DecompositionLevelCount(0), | ||
cbs_base_log: DecompositionBaseLog(0), | ||
message_modulus_log: MessageModulusLog(4), | ||
ciphertext_modulus: CiphertextModulus::new_native(), | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.