From 59fd6e71b3868542c5ba97d948218c056b24a36c Mon Sep 17 00:00:00 2001 From: Agnes Leroy Date: Thu, 14 Nov 2024 10:22:51 +0100 Subject: [PATCH] chore(gpu): update multi-bit params, add noise test for the classical & multi-bit PBS --- tfhe/src/core_crypto/algorithms/test/mod.rs | 6 + ...ti_bit_programmable_bootstrapping_noise.rs | 226 ++++++ .../algorithms/test/noise_distribution/mod.rs | 88 ++ .../src/core_crypto/algorithms/test/params.rs | 1 + .../lwe_multi_bit_programmable_bootstrap.rs | 763 ++++++++++++++++++ .../core_crypto/commons/noise_formulas/mod.rs | 1 + .../core_crypto/gpu/algorithms/test/mod.rs | 1 + ...ti_bit_programmable_bootstrapping_noise.rs | 264 ++++++ .../lwe_programmable_bootstrapping_noise.rs | 248 ++++++ .../algorithms/test/noise_distribution/mod.rs | 49 ++ .../gaussian/p_fail_2_minus_64/ks_pbs_gpu.rs | 92 ++- .../gaussian/p_fail_2_minus_64/mod.rs | 6 + .../tuniform/p_fail_2_minus_64/ks_pbs_gpu.rs | 26 +- .../tuniform/p_fail_2_minus_64/mod.rs | 1 + 14 files changed, 1767 insertions(+), 5 deletions(-) create mode 100644 tfhe/src/core_crypto/algorithms/test/noise_distribution/lwe_multi_bit_programmable_bootstrapping_noise.rs create mode 100644 tfhe/src/core_crypto/commons/noise_formulas/lwe_multi_bit_programmable_bootstrap.rs create mode 100644 tfhe/src/core_crypto/gpu/algorithms/test/noise_distribution/lwe_multi_bit_programmable_bootstrapping_noise.rs create mode 100644 tfhe/src/core_crypto/gpu/algorithms/test/noise_distribution/lwe_programmable_bootstrapping_noise.rs create mode 100644 tfhe/src/core_crypto/gpu/algorithms/test/noise_distribution/mod.rs diff --git a/tfhe/src/core_crypto/algorithms/test/mod.rs b/tfhe/src/core_crypto/algorithms/test/mod.rs index 947e8c0ad8..64f8374c04 100644 --- a/tfhe/src/core_crypto/algorithms/test/mod.rs +++ b/tfhe/src/core_crypto/algorithms/test/mod.rs @@ -194,6 +194,7 @@ pub const MULTI_BIT_2_2_2_PARAMS: MultiBitTestParams = MultiBitTestParams { ciphertext_modulus: CiphertextModulus::new_native(), grouping_factor: LweBskGroupingFactor(2), thread_count: ThreadCount(5), + tuniform: false, }; pub const MULTI_BIT_3_3_2_PARAMS: MultiBitTestParams = MultiBitTestParams { @@ -212,6 +213,7 @@ pub const MULTI_BIT_3_3_2_PARAMS: MultiBitTestParams = MultiBitTestParams { ciphertext_modulus: CiphertextModulus::new_native(), grouping_factor: LweBskGroupingFactor(2), thread_count: ThreadCount(5), + tuniform: false, }; pub const MULTI_BIT_2_2_2_CUSTOM_MOD_PARAMS: MultiBitTestParams = MultiBitTestParams { @@ -230,6 +232,7 @@ pub const MULTI_BIT_2_2_2_CUSTOM_MOD_PARAMS: MultiBitTestParams = MultiBitT ciphertext_modulus: CiphertextModulus::new(1 << 63), grouping_factor: LweBskGroupingFactor(2), thread_count: ThreadCount(5), + tuniform: false, }; pub const MULTI_BIT_2_2_3_PARAMS: MultiBitTestParams = MultiBitTestParams { @@ -248,6 +251,7 @@ pub const MULTI_BIT_2_2_3_PARAMS: MultiBitTestParams = MultiBitTestParams { ciphertext_modulus: CiphertextModulus::new_native(), grouping_factor: LweBskGroupingFactor(3), thread_count: ThreadCount(12), + tuniform: false, }; pub const MULTI_BIT_3_3_3_PARAMS: MultiBitTestParams = MultiBitTestParams { @@ -266,6 +270,7 @@ pub const MULTI_BIT_3_3_3_PARAMS: MultiBitTestParams = MultiBitTestParams { ciphertext_modulus: CiphertextModulus::new_native(), grouping_factor: LweBskGroupingFactor(3), thread_count: ThreadCount(5), + tuniform: false, }; pub const MULTI_BIT_2_2_3_CUSTOM_MOD_PARAMS: MultiBitTestParams = MultiBitTestParams { @@ -284,6 +289,7 @@ pub const MULTI_BIT_2_2_3_CUSTOM_MOD_PARAMS: MultiBitTestParams = MultiBitT ciphertext_modulus: CiphertextModulus::new(1 << 63), grouping_factor: LweBskGroupingFactor(3), thread_count: ThreadCount(12), + tuniform: false, }; // DISCLAIMER: example parameters tailored for FFT implementation tests. There are not guaranteed diff --git a/tfhe/src/core_crypto/algorithms/test/noise_distribution/lwe_multi_bit_programmable_bootstrapping_noise.rs b/tfhe/src/core_crypto/algorithms/test/noise_distribution/lwe_multi_bit_programmable_bootstrapping_noise.rs new file mode 100644 index 0000000000..d267effe10 --- /dev/null +++ b/tfhe/src/core_crypto/algorithms/test/noise_distribution/lwe_multi_bit_programmable_bootstrapping_noise.rs @@ -0,0 +1,226 @@ +use super::*; +use crate::core_crypto::commons::noise_formulas::lwe_multi_bit_programmable_bootstrap::{ + multi_bit_pbs_variance_132_bits_security_gaussian_gf_3_fft_mul, + multi_bit_pbs_variance_132_bits_security_tuniform_gf_3_fft_mul, +}; +use crate::core_crypto::commons::noise_formulas::secure_noise::minimal_lwe_variance_for_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_multi_bit_pbs_group_3_decrypt_custom_mod(params: MultiBitTestParams) +where + Scalar: UnsignedTorus + Sync + Send + CastFrom + CastInto, +{ + let input_lwe_dimension = params.input_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.decomp_base_log; + let pbs_decomposition_level_count = params.decomp_level_count; + let grouping_factor = params.grouping_factor; + assert_eq!(grouping_factor.0, 3); + + 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 = if params.tuniform { + multi_bit_pbs_variance_132_bits_security_tuniform_gf_3_fft_mul( + input_lwe_dimension, + glwe_dimension, + polynomial_size, + pbs_decomposition_base_log, + pbs_decomposition_level_count, + modulus_as_f64, + ) + } else { + multi_bit_pbs_variance_132_bits_security_gaussian_gf_3_fft_mul( + input_lwe_dimension, + glwe_dimension, + polynomial_size, + pbs_decomposition_base_log, + pbs_decomposition_level_count, + modulus_as_f64, + ) + }; + + 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 * >::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_multi_bit_bootstrap_key( + &input_lwe_secret_key, + &output_glwe_secret_key, + pbs_decomposition_base_log, + pbs_decomposition_level_count, + grouping_factor, + glwe_noise_distribution, + ciphertext_modulus, + &mut rsc.encryption_random_generator, + ); + + assert!(check_encrypted_content_respects_mod( + &*bsk, + ciphertext_modulus + )); + + let mut fbsk = FourierLweMultiBitBootstrapKey::new( + bsk.input_lwe_dimension(), + bsk.glwe_size(), + bsk.polynomial_size(), + bsk.decomposition_base_log(), + bsk.decomposition_level_count(), + bsk.grouping_factor(), + ); + + par_convert_standard_lwe_multi_bit_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, + ); + + multi_bit_programmable_bootstrap_lwe_ciphertext( + &lwe_ciphertext_in, + &mut out_pbs_ct, + &accumulator, + &fbsk, + params.thread_count, + true, + ); + + 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 minimal_variance = minimal_lwe_variance_for_132_bits_security_gaussian( + fbsk.output_lwe_dimension(), + if ciphertext_modulus.is_native_modulus() { + 2.0f64.powi(Scalar::BITS as i32) + } else { + ciphertext_modulus.get_custom_modulus() as f64 + }, + ); + + // 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:?}"); + println!("minimal_variance={minimal_variance:?}"); + + if measured_variance.0 < expected_variance.0 { + // We are in the clear as long as we have at least the noise for security + assert!( + measured_variance.0 >= minimal_variance.0, + "Found insecure variance after PBS\n\ + measure_variance={measured_variance:?}\n\ + minimal_variance={minimal_variance:?}" + ); + } else { + // Check we are not too far from the expected variance if we are bigger + let var_abs_diff = (expected_variance.0 - measured_variance.0).abs(); + let tolerance_threshold = RELATIVE_TOLERANCE * expected_variance.0; + + 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_parameterized_test!(lwe_encrypt_multi_bit_pbs_group_3_decrypt_custom_mod { + NOISE_TEST_PARAMS_MULTI_BIT_GROUP_3_4_BITS_NATIVE_U64_132_BITS_GAUSSIAN, + NOISE_TEST_PARAMS_MULTI_BIT_GROUP_3_4_BITS_NATIVE_U64_132_BITS_TUNIFORM +}); diff --git a/tfhe/src/core_crypto/algorithms/test/noise_distribution/mod.rs b/tfhe/src/core_crypto/algorithms/test/noise_distribution/mod.rs index 16bdf52c73..a8395f5735 100644 --- a/tfhe/src/core_crypto/algorithms/test/noise_distribution/mod.rs +++ b/tfhe/src/core_crypto/algorithms/test/noise_distribution/mod.rs @@ -2,6 +2,7 @@ use super::*; mod lwe_encryption_noise; mod lwe_keyswitch_noise; +mod lwe_multi_bit_programmable_bootstrapping_noise; mod lwe_programmable_bootstrapping_noise; #[allow(clippy::excessive_precision)] @@ -28,3 +29,90 @@ pub const NOISE_TEST_PARAMS_4_BITS_NATIVE_U64_132_BITS_GAUSSIAN: ClassicTestPara message_modulus_log: MessageModulusLog(4), ciphertext_modulus: CiphertextModulus::new_native(), }; + +// ---- GAUSSIAN --------------------------------------------------------- + +#[allow(clippy::excessive_precision)] +#[allow(dead_code)] +pub const NOISE_TEST_PARAMS_MULTI_BIT_GROUP_3_2_BITS_NATIVE_U64_132_BITS_GAUSSIAN: + MultiBitTestParams = MultiBitTestParams { + input_lwe_dimension: LweDimension(256 * 3), + lwe_noise_distribution: DynamicDistribution::new_gaussian_from_std_dev(StandardDev( + 1.1098369627275701e-05, + )), + decomp_base_log: DecompositionBaseLog(17), + decomp_level_count: DecompositionLevelCount(1), + glwe_dimension: GlweDimension(3), + polynomial_size: PolynomialSize(512), + glwe_noise_distribution: DynamicDistribution::new_gaussian_from_std_dev(StandardDev( + 1.9524392655548086e-11, + )), + message_modulus_log: MessageModulusLog(2), + ciphertext_modulus: CiphertextModulus::new_native(), + grouping_factor: LweBskGroupingFactor(3), + thread_count: ThreadCount(12), + tuniform: false, +}; + +#[allow(clippy::excessive_precision)] +pub const NOISE_TEST_PARAMS_MULTI_BIT_GROUP_3_4_BITS_NATIVE_U64_132_BITS_GAUSSIAN: + MultiBitTestParams = MultiBitTestParams { + input_lwe_dimension: LweDimension(279 * 3), + lwe_noise_distribution: DynamicDistribution::new_gaussian_from_std_dev(StandardDev( + 3.3747142481837397e-06, + )), + decomp_base_log: DecompositionBaseLog(22), + decomp_level_count: DecompositionLevelCount(1), + glwe_dimension: GlweDimension(1), + polynomial_size: PolynomialSize(2048), + glwe_noise_distribution: DynamicDistribution::new_gaussian_from_std_dev(StandardDev( + 2.845267479601915e-15, + )), + message_modulus_log: MessageModulusLog(4), + ciphertext_modulus: CiphertextModulus::new_native(), + grouping_factor: LweBskGroupingFactor(3), + thread_count: ThreadCount(12), + tuniform: false, +}; + +#[allow(clippy::excessive_precision)] +#[allow(dead_code)] +pub const NOISE_TEST_PARAMS_MULTI_BIT_GROUP_3_6_BITS_NATIVE_U64_132_BITS_GAUSSIAN: + MultiBitTestParams = MultiBitTestParams { + input_lwe_dimension: LweDimension(326 * 3), + lwe_noise_distribution: DynamicDistribution::new_gaussian_from_std_dev(StandardDev( + 2.962875621642539e-07, + )), + decomp_base_log: DecompositionBaseLog(14), + decomp_level_count: DecompositionLevelCount(2), + glwe_dimension: GlweDimension(1), + polynomial_size: PolynomialSize(8192), + glwe_noise_distribution: DynamicDistribution::new_gaussian_from_std_dev(StandardDev( + 2.168404344971009e-19, + )), + message_modulus_log: MessageModulusLog(6), + ciphertext_modulus: CiphertextModulus::new_native(), + grouping_factor: LweBskGroupingFactor(3), + thread_count: ThreadCount(12), + tuniform: false, +}; + +// ---- TUNIFORM --------------------------------------------------------- + +#[allow(clippy::excessive_precision)] +#[allow(dead_code)] +pub const NOISE_TEST_PARAMS_MULTI_BIT_GROUP_3_4_BITS_NATIVE_U64_132_BITS_TUNIFORM: + MultiBitTestParams = MultiBitTestParams { + input_lwe_dimension: LweDimension(293 * 3), + lwe_noise_distribution: DynamicDistribution::new_t_uniform(46), + decomp_base_log: DecompositionBaseLog(22), + decomp_level_count: DecompositionLevelCount(1), + glwe_dimension: GlweDimension(1), + polynomial_size: PolynomialSize(2048), + glwe_noise_distribution: DynamicDistribution::new_t_uniform(17), + message_modulus_log: MessageModulusLog(4), + ciphertext_modulus: CiphertextModulus::new_native(), + grouping_factor: LweBskGroupingFactor(3), + thread_count: ThreadCount(12), + tuniform: true, +}; diff --git a/tfhe/src/core_crypto/algorithms/test/params.rs b/tfhe/src/core_crypto/algorithms/test/params.rs index 2a3f6254a1..fee55040f3 100644 --- a/tfhe/src/core_crypto/algorithms/test/params.rs +++ b/tfhe/src/core_crypto/algorithms/test/params.rs @@ -77,6 +77,7 @@ pub struct MultiBitTestParams { pub ciphertext_modulus: CiphertextModulus, pub grouping_factor: LweBskGroupingFactor, pub thread_count: ThreadCount, + pub tuniform: bool, } // PartialEq is implemented manually because thread_count doesn't affect key generation and we want diff --git a/tfhe/src/core_crypto/commons/noise_formulas/lwe_multi_bit_programmable_bootstrap.rs b/tfhe/src/core_crypto/commons/noise_formulas/lwe_multi_bit_programmable_bootstrap.rs new file mode 100644 index 0000000000..b650d6025c --- /dev/null +++ b/tfhe/src/core_crypto/commons/noise_formulas/lwe_multi_bit_programmable_bootstrap.rs @@ -0,0 +1,763 @@ +// This file was autogenerated, do not modify by hand. +use crate::core_crypto::commons::dispersion::Variance; +use crate::core_crypto::commons::parameters::*; + +// FFT Multiplication + +/// This formula is only valid if the proper noise distributions are used and +/// if the keys used are encrypted using secure noise given by the +/// [`minimal_glwe_variance`](`super::secure_noise`) +/// and [`minimal_lwe_variance`](`super::secure_noise`) family of functions. +pub fn multi_bit_pbs_variance_132_bits_security_gaussian_gf_2_fft_mul( + input_lwe_dimension: LweDimension, + output_glwe_dimension: GlweDimension, + output_polynomial_size: PolynomialSize, + decomposition_base_log: DecompositionBaseLog, + decomposition_level_count: DecompositionLevelCount, + modulus: f64, +) -> Variance { + Variance( + multi_bit_pbs_variance_132_bits_security_gaussian_gf_2_fft_mul_impl( + input_lwe_dimension.0 as f64, + output_glwe_dimension.0 as f64, + output_polynomial_size.0 as f64, + 2.0f64.powi(decomposition_base_log.0 as i32), + decomposition_level_count.0 as f64, + modulus, + ), + ) +} + +/// This formula is only valid if the proper noise distributions are used and +/// if the keys used are encrypted using secure noise given by the +/// [`minimal_glwe_variance`](`super::secure_noise`) +/// and [`minimal_lwe_variance`](`super::secure_noise`) family of functions. +pub fn multi_bit_pbs_variance_132_bits_security_gaussian_gf_2_fft_mul_impl( + input_lwe_dimension: f64, + output_glwe_dimension: f64, + output_polynomial_size: f64, + decomposition_base: f64, + decomposition_level_count: f64, + modulus: f64, +) -> f64 { + (1_f64 / 2.0) + * input_lwe_dimension + * (2.40868445115171 + * (2.0 * 0.0_f64.max(std::f64::consts::LOG2_E * modulus.ln() - 53.0)).exp2() + * decomposition_base.powf(2.0) + * decomposition_level_count + * modulus.powf(-2.0) + * output_glwe_dimension + * output_polynomial_size.powf(2.0) + * (output_glwe_dimension + 1.0) + + 1.0 + * decomposition_level_count + * output_polynomial_size + * ((4.0 - 2.88539008177793 * modulus.ln()).exp2() + + (-0.0497829131652661 * output_glwe_dimension * output_polynomial_size + + 5.31469187675068) + .exp2()) + * ((1_f64 / 12.0) * decomposition_base.powf(2.0) + 0.166666666666667) + * (output_glwe_dimension + 1.0) + + (1_f64 / 6.0) * modulus.powf(-2.0) + + output_glwe_dimension + * output_polynomial_size + * (0.0208333333333333 * modulus.powf(-2.0) + + 0.0416666666666667 + * decomposition_base.powf(-2.0 * decomposition_level_count)) + + (1_f64 / 12.0) * decomposition_base.powf(-2.0 * decomposition_level_count)) +} + +/// This formula is only valid if the proper noise distributions are used and +/// if the keys used are encrypted using secure noise given by the +/// [`minimal_glwe_variance`](`super::secure_noise`) +/// and [`minimal_lwe_variance`](`super::secure_noise`) family of functions. +pub fn multi_bit_pbs_variance_132_bits_security_gaussian_gf_3_fft_mul( + input_lwe_dimension: LweDimension, + output_glwe_dimension: GlweDimension, + output_polynomial_size: PolynomialSize, + decomposition_base_log: DecompositionBaseLog, + decomposition_level_count: DecompositionLevelCount, + modulus: f64, +) -> Variance { + Variance( + multi_bit_pbs_variance_132_bits_security_gaussian_gf_3_fft_mul_impl( + input_lwe_dimension.0 as f64, + output_glwe_dimension.0 as f64, + output_polynomial_size.0 as f64, + 2.0f64.powi(decomposition_base_log.0 as i32), + decomposition_level_count.0 as f64, + modulus, + ), + ) +} + +/// This formula is only valid if the proper noise distributions are used and +/// if the keys used are encrypted using secure noise given by the +/// [`minimal_glwe_variance`](`super::secure_noise`) +/// and [`minimal_lwe_variance`](`super::secure_noise`) family of functions. +pub fn multi_bit_pbs_variance_132_bits_security_gaussian_gf_3_fft_mul_impl( + input_lwe_dimension: f64, + output_glwe_dimension: f64, + output_polynomial_size: f64, + decomposition_base: f64, + decomposition_level_count: f64, + modulus: f64, +) -> f64 { + (1_f64 / 3.0) + * input_lwe_dimension + * (5.2 + * (2.0 * 0.0_f64.max(std::f64::consts::LOG2_E * modulus.ln() - 53.0)).exp2() + * decomposition_base.powf(2.0) + * decomposition_level_count + * modulus.powf(-2.0) + * output_glwe_dimension + * output_polynomial_size.powf(2.0) + * (output_glwe_dimension + 1.0) + + 2.0 + * decomposition_level_count + * output_polynomial_size + * ((4.0 - 2.88539008177793 * modulus.ln()).exp2() + + (-0.0497829131652661 * output_glwe_dimension * output_polynomial_size + + 5.31469187675068) + .exp2()) + * ((1_f64 / 12.0) * decomposition_base.powf(2.0) + 0.166666666666667) + * (output_glwe_dimension + 1.0) + + (1_f64 / 6.0) * modulus.powf(-2.0) + + output_glwe_dimension + * output_polynomial_size + * (0.0208333333333333 * modulus.powf(-2.0) + + 0.0416666666666667 + * decomposition_base.powf(-2.0 * decomposition_level_count)) + + (1_f64 / 12.0) * decomposition_base.powf(-2.0 * decomposition_level_count)) +} + +/// This formula is only valid if the proper noise distributions are used and +/// if the keys used are encrypted using secure noise given by the +/// [`minimal_glwe_variance`](`super::secure_noise`) +/// and [`minimal_lwe_variance`](`super::secure_noise`) family of functions. +pub fn multi_bit_pbs_variance_132_bits_security_gaussian_gf_4_fft_mul( + input_lwe_dimension: LweDimension, + output_glwe_dimension: GlweDimension, + output_polynomial_size: PolynomialSize, + decomposition_base_log: DecompositionBaseLog, + decomposition_level_count: DecompositionLevelCount, + modulus: f64, +) -> Variance { + Variance( + multi_bit_pbs_variance_132_bits_security_gaussian_gf_4_fft_mul_impl( + input_lwe_dimension.0 as f64, + output_glwe_dimension.0 as f64, + output_polynomial_size.0 as f64, + 2.0f64.powi(decomposition_base_log.0 as i32), + decomposition_level_count.0 as f64, + modulus, + ), + ) +} + +/// This formula is only valid if the proper noise distributions are used and +/// if the keys used are encrypted using secure noise given by the +/// [`minimal_glwe_variance`](`super::secure_noise`) +/// and [`minimal_lwe_variance`](`super::secure_noise`) family of functions. +pub fn multi_bit_pbs_variance_132_bits_security_gaussian_gf_4_fft_mul_impl( + input_lwe_dimension: f64, + output_glwe_dimension: f64, + output_polynomial_size: f64, + decomposition_base: f64, + decomposition_level_count: f64, + modulus: f64, +) -> f64 { + (1_f64 / 4.0) + * input_lwe_dimension + * (11.0544737502456 + * (2.0 * 0.0_f64.max(std::f64::consts::LOG2_E * modulus.ln() - 53.0)).exp2() + * decomposition_base.powf(2.0) + * decomposition_level_count + * modulus.powf(-2.0) + * output_glwe_dimension + * output_polynomial_size.powf(2.0) + * (output_glwe_dimension + 1.0) + + 4.0 + * decomposition_level_count + * output_polynomial_size + * ((4.0 - 2.88539008177793 * modulus.ln()).exp2() + + (-0.0497829131652661 * output_glwe_dimension * output_polynomial_size + + 5.31469187675068) + .exp2()) + * ((1_f64 / 12.0) * decomposition_base.powf(2.0) + 0.166666666666667) + * (output_glwe_dimension + 1.0) + + (1_f64 / 6.0) * modulus.powf(-2.0) + + output_glwe_dimension + * output_polynomial_size + * (0.0208333333333333 * modulus.powf(-2.0) + + 0.0416666666666667 + * decomposition_base.powf(-2.0 * decomposition_level_count)) + + (1_f64 / 12.0) * decomposition_base.powf(-2.0 * decomposition_level_count)) +} + +/// This formula is only valid if the proper noise distributions are used and +/// if the keys used are encrypted using secure noise given by the +/// [`minimal_glwe_variance`](`super::secure_noise`) +/// and [`minimal_lwe_variance`](`super::secure_noise`) family of functions. +pub fn multi_bit_pbs_variance_132_bits_security_tuniform_gf_2_fft_mul( + input_lwe_dimension: LweDimension, + output_glwe_dimension: GlweDimension, + output_polynomial_size: PolynomialSize, + decomposition_base_log: DecompositionBaseLog, + decomposition_level_count: DecompositionLevelCount, + modulus: f64, +) -> Variance { + Variance( + multi_bit_pbs_variance_132_bits_security_tuniform_gf_2_fft_mul_impl( + input_lwe_dimension.0 as f64, + output_glwe_dimension.0 as f64, + output_polynomial_size.0 as f64, + 2.0f64.powi(decomposition_base_log.0 as i32), + decomposition_level_count.0 as f64, + modulus, + ), + ) +} + +/// This formula is only valid if the proper noise distributions are used and +/// if the keys used are encrypted using secure noise given by the +/// [`minimal_glwe_variance`](`super::secure_noise`) +/// and [`minimal_lwe_variance`](`super::secure_noise`) family of functions. +pub fn multi_bit_pbs_variance_132_bits_security_tuniform_gf_2_fft_mul_impl( + input_lwe_dimension: f64, + output_glwe_dimension: f64, + output_polynomial_size: f64, + decomposition_base: f64, + decomposition_level_count: f64, + modulus: f64, +) -> f64 { + (1_f64 / 2.0) + * input_lwe_dimension + * (2.40868445115171 + * (2.0 * 0.0_f64.max(std::f64::consts::LOG2_E * modulus.ln() - 53.0)).exp2() + * decomposition_base.powf(2.0) + * decomposition_level_count + * modulus.powf(-2.0) + * output_glwe_dimension + * output_polynomial_size.powf(2.0) + * (output_glwe_dimension + 1.0) + + 1.0 + * decomposition_level_count + * output_polynomial_size + * ((4.0 - 2.88539008177793 * modulus.ln()).exp2() + + (1_f64 / 3.0) + * modulus.powf(-2.0) + * ((2.0 + * (-0.025167785 * output_glwe_dimension * output_polynomial_size + + std::f64::consts::LOG2_E * modulus.ln() + + 4.10067100000001) + .ceil()) + .exp2() + + 0.5)) + * ((1_f64 / 12.0) * decomposition_base.powf(2.0) + 0.166666666666667) + * (output_glwe_dimension + 1.0) + + (1_f64 / 6.0) * modulus.powf(-2.0) + + output_glwe_dimension + * output_polynomial_size + * (0.0208333333333333 * modulus.powf(-2.0) + + 0.0416666666666667 + * decomposition_base.powf(-2.0 * decomposition_level_count)) + + (1_f64 / 12.0) * decomposition_base.powf(-2.0 * decomposition_level_count)) +} + +/// This formula is only valid if the proper noise distributions are used and +/// if the keys used are encrypted using secure noise given by the +/// [`minimal_glwe_variance`](`super::secure_noise`) +/// and [`minimal_lwe_variance`](`super::secure_noise`) family of functions. +pub fn multi_bit_pbs_variance_132_bits_security_tuniform_gf_3_fft_mul( + input_lwe_dimension: LweDimension, + output_glwe_dimension: GlweDimension, + output_polynomial_size: PolynomialSize, + decomposition_base_log: DecompositionBaseLog, + decomposition_level_count: DecompositionLevelCount, + modulus: f64, +) -> Variance { + Variance( + multi_bit_pbs_variance_132_bits_security_tuniform_gf_3_fft_mul_impl( + input_lwe_dimension.0 as f64, + output_glwe_dimension.0 as f64, + output_polynomial_size.0 as f64, + 2.0f64.powi(decomposition_base_log.0 as i32), + decomposition_level_count.0 as f64, + modulus, + ), + ) +} + +/// This formula is only valid if the proper noise distributions are used and +/// if the keys used are encrypted using secure noise given by the +/// [`minimal_glwe_variance`](`super::secure_noise`) +/// and [`minimal_lwe_variance`](`super::secure_noise`) family of functions. +pub fn multi_bit_pbs_variance_132_bits_security_tuniform_gf_3_fft_mul_impl( + input_lwe_dimension: f64, + output_glwe_dimension: f64, + output_polynomial_size: f64, + decomposition_base: f64, + decomposition_level_count: f64, + modulus: f64, +) -> f64 { + (1_f64 / 3.0) + * input_lwe_dimension + * (5.2 + * (2.0 * 0.0_f64.max(std::f64::consts::LOG2_E * modulus.ln() - 53.0)).exp2() + * decomposition_base.powf(2.0) + * decomposition_level_count + * modulus.powf(-2.0) + * output_glwe_dimension + * output_polynomial_size.powf(2.0) + * (output_glwe_dimension + 1.0) + + 2.0 + * decomposition_level_count + * output_polynomial_size + * ((4.0 - 2.88539008177793 * modulus.ln()).exp2() + + (1_f64 / 3.0) + * modulus.powf(-2.0) + * ((2.0 + * (-0.025167785 * output_glwe_dimension * output_polynomial_size + + std::f64::consts::LOG2_E * modulus.ln() + + 4.10067100000001) + .ceil()) + .exp2() + + 0.5)) + * ((1_f64 / 12.0) * decomposition_base.powf(2.0) + 0.166666666666667) + * (output_glwe_dimension + 1.0) + + (1_f64 / 6.0) * modulus.powf(-2.0) + + output_glwe_dimension + * output_polynomial_size + * (0.0208333333333333 * modulus.powf(-2.0) + + 0.0416666666666667 + * decomposition_base.powf(-2.0 * decomposition_level_count)) + + (1_f64 / 12.0) * decomposition_base.powf(-2.0 * decomposition_level_count)) +} + +/// This formula is only valid if the proper noise distributions are used and +/// if the keys used are encrypted using secure noise given by the +/// [`minimal_glwe_variance`](`super::secure_noise`) +/// and [`minimal_lwe_variance`](`super::secure_noise`) family of functions. +pub fn multi_bit_pbs_variance_132_bits_security_tuniform_gf_4_fft_mul( + input_lwe_dimension: LweDimension, + output_glwe_dimension: GlweDimension, + output_polynomial_size: PolynomialSize, + decomposition_base_log: DecompositionBaseLog, + decomposition_level_count: DecompositionLevelCount, + modulus: f64, +) -> Variance { + Variance( + multi_bit_pbs_variance_132_bits_security_tuniform_gf_4_fft_mul_impl( + input_lwe_dimension.0 as f64, + output_glwe_dimension.0 as f64, + output_polynomial_size.0 as f64, + 2.0f64.powi(decomposition_base_log.0 as i32), + decomposition_level_count.0 as f64, + modulus, + ), + ) +} + +/// This formula is only valid if the proper noise distributions are used and +/// if the keys used are encrypted using secure noise given by the +/// [`minimal_glwe_variance`](`super::secure_noise`) +/// and [`minimal_lwe_variance`](`super::secure_noise`) family of functions. +pub fn multi_bit_pbs_variance_132_bits_security_tuniform_gf_4_fft_mul_impl( + input_lwe_dimension: f64, + output_glwe_dimension: f64, + output_polynomial_size: f64, + decomposition_base: f64, + decomposition_level_count: f64, + modulus: f64, +) -> f64 { + (1_f64 / 4.0) + * input_lwe_dimension + * (11.0544737502456 + * (2.0 * 0.0_f64.max(std::f64::consts::LOG2_E * modulus.ln() - 53.0)).exp2() + * decomposition_base.powf(2.0) + * decomposition_level_count + * modulus.powf(-2.0) + * output_glwe_dimension + * output_polynomial_size.powf(2.0) + * (output_glwe_dimension + 1.0) + + 4.0 + * decomposition_level_count + * output_polynomial_size + * ((4.0 - 2.88539008177793 * modulus.ln()).exp2() + + (1_f64 / 3.0) + * modulus.powf(-2.0) + * ((2.0 + * (-0.025167785 * output_glwe_dimension * output_polynomial_size + + std::f64::consts::LOG2_E * modulus.ln() + + 4.10067100000001) + .ceil()) + .exp2() + + 0.5)) + * ((1_f64 / 12.0) * decomposition_base.powf(2.0) + 0.166666666666667) + * (output_glwe_dimension + 1.0) + + (1_f64 / 6.0) * modulus.powf(-2.0) + + output_glwe_dimension + * output_polynomial_size + * (0.0208333333333333 * modulus.powf(-2.0) + + 0.0416666666666667 + * decomposition_base.powf(-2.0 * decomposition_level_count)) + + (1_f64 / 12.0) * decomposition_base.powf(-2.0 * decomposition_level_count)) +} + +// Exact (Karatsuba) Multiplication + +/// This formula is only valid if the proper noise distributions are used and +/// if the keys used are encrypted using secure noise given by the +/// [`minimal_glwe_variance`](`super::secure_noise`) +/// and [`minimal_lwe_variance`](`super::secure_noise`) family of functions. +pub fn multi_bit_pbs_variance_132_bits_security_gaussian_gf_2_exact_mul( + input_lwe_dimension: LweDimension, + output_glwe_dimension: GlweDimension, + output_polynomial_size: PolynomialSize, + decomposition_base_log: DecompositionBaseLog, + decomposition_level_count: DecompositionLevelCount, + modulus: f64, +) -> Variance { + Variance( + multi_bit_pbs_variance_132_bits_security_gaussian_gf_2_exact_mul_impl( + input_lwe_dimension.0 as f64, + output_glwe_dimension.0 as f64, + output_polynomial_size.0 as f64, + 2.0f64.powi(decomposition_base_log.0 as i32), + decomposition_level_count.0 as f64, + modulus, + ), + ) +} + +/// This formula is only valid if the proper noise distributions are used and +/// if the keys used are encrypted using secure noise given by the +/// [`minimal_glwe_variance`](`super::secure_noise`) +/// and [`minimal_lwe_variance`](`super::secure_noise`) family of functions. +pub fn multi_bit_pbs_variance_132_bits_security_gaussian_gf_2_exact_mul_impl( + input_lwe_dimension: f64, + output_glwe_dimension: f64, + output_polynomial_size: f64, + decomposition_base: f64, + decomposition_level_count: f64, + modulus: f64, +) -> f64 { + (1_f64 / 2.0) + * input_lwe_dimension + * (1.0 + * decomposition_level_count + * output_polynomial_size + * ((4.0 - 2.88539008177793 * modulus.ln()).exp2() + + (-0.0497829131652661 * output_glwe_dimension * output_polynomial_size + + 5.31469187675068) + .exp2()) + * ((1_f64 / 12.0) * decomposition_base.powf(2.0) + 0.166666666666667) + * (output_glwe_dimension + 1.0) + + (1_f64 / 6.0) * modulus.powf(-2.0) + + output_glwe_dimension + * output_polynomial_size + * (0.0208333333333333 * modulus.powf(-2.0) + + 0.0416666666666667 + * decomposition_base.powf(-2.0 * decomposition_level_count)) + + (1_f64 / 12.0) * decomposition_base.powf(-2.0 * decomposition_level_count)) +} + +/// This formula is only valid if the proper noise distributions are used and +/// if the keys used are encrypted using secure noise given by the +/// [`minimal_glwe_variance`](`super::secure_noise`) +/// and [`minimal_lwe_variance`](`super::secure_noise`) family of functions. +pub fn multi_bit_pbs_variance_132_bits_security_gaussian_gf_3_exact_mul( + input_lwe_dimension: LweDimension, + output_glwe_dimension: GlweDimension, + output_polynomial_size: PolynomialSize, + decomposition_base_log: DecompositionBaseLog, + decomposition_level_count: DecompositionLevelCount, + modulus: f64, +) -> Variance { + Variance( + multi_bit_pbs_variance_132_bits_security_gaussian_gf_3_exact_mul_impl( + input_lwe_dimension.0 as f64, + output_glwe_dimension.0 as f64, + output_polynomial_size.0 as f64, + 2.0f64.powi(decomposition_base_log.0 as i32), + decomposition_level_count.0 as f64, + modulus, + ), + ) +} + +/// This formula is only valid if the proper noise distributions are used and +/// if the keys used are encrypted using secure noise given by the +/// [`minimal_glwe_variance`](`super::secure_noise`) +/// and [`minimal_lwe_variance`](`super::secure_noise`) family of functions. +pub fn multi_bit_pbs_variance_132_bits_security_gaussian_gf_3_exact_mul_impl( + input_lwe_dimension: f64, + output_glwe_dimension: f64, + output_polynomial_size: f64, + decomposition_base: f64, + decomposition_level_count: f64, + modulus: f64, +) -> f64 { + (1_f64 / 3.0) + * input_lwe_dimension + * (2.0 + * decomposition_level_count + * output_polynomial_size + * ((4.0 - 2.88539008177793 * modulus.ln()).exp2() + + (-0.0497829131652661 * output_glwe_dimension * output_polynomial_size + + 5.31469187675068) + .exp2()) + * ((1_f64 / 12.0) * decomposition_base.powf(2.0) + 0.166666666666667) + * (output_glwe_dimension + 1.0) + + (1_f64 / 6.0) * modulus.powf(-2.0) + + output_glwe_dimension + * output_polynomial_size + * (0.0208333333333333 * modulus.powf(-2.0) + + 0.0416666666666667 + * decomposition_base.powf(-2.0 * decomposition_level_count)) + + (1_f64 / 12.0) * decomposition_base.powf(-2.0 * decomposition_level_count)) +} + +/// This formula is only valid if the proper noise distributions are used and +/// if the keys used are encrypted using secure noise given by the +/// [`minimal_glwe_variance`](`super::secure_noise`) +/// and [`minimal_lwe_variance`](`super::secure_noise`) family of functions. +pub fn multi_bit_pbs_variance_132_bits_security_gaussian_gf_4_exact_mul( + input_lwe_dimension: LweDimension, + output_glwe_dimension: GlweDimension, + output_polynomial_size: PolynomialSize, + decomposition_base_log: DecompositionBaseLog, + decomposition_level_count: DecompositionLevelCount, + modulus: f64, +) -> Variance { + Variance( + multi_bit_pbs_variance_132_bits_security_gaussian_gf_4_exact_mul_impl( + input_lwe_dimension.0 as f64, + output_glwe_dimension.0 as f64, + output_polynomial_size.0 as f64, + 2.0f64.powi(decomposition_base_log.0 as i32), + decomposition_level_count.0 as f64, + modulus, + ), + ) +} + +/// This formula is only valid if the proper noise distributions are used and +/// if the keys used are encrypted using secure noise given by the +/// [`minimal_glwe_variance`](`super::secure_noise`) +/// and [`minimal_lwe_variance`](`super::secure_noise`) family of functions. +pub fn multi_bit_pbs_variance_132_bits_security_gaussian_gf_4_exact_mul_impl( + input_lwe_dimension: f64, + output_glwe_dimension: f64, + output_polynomial_size: f64, + decomposition_base: f64, + decomposition_level_count: f64, + modulus: f64, +) -> f64 { + (1_f64 / 4.0) + * input_lwe_dimension + * (4.0 + * decomposition_level_count + * output_polynomial_size + * ((4.0 - 2.88539008177793 * modulus.ln()).exp2() + + (-0.0497829131652661 * output_glwe_dimension * output_polynomial_size + + 5.31469187675068) + .exp2()) + * ((1_f64 / 12.0) * decomposition_base.powf(2.0) + 0.166666666666667) + * (output_glwe_dimension + 1.0) + + (1_f64 / 6.0) * modulus.powf(-2.0) + + output_glwe_dimension + * output_polynomial_size + * (0.0208333333333333 * modulus.powf(-2.0) + + 0.0416666666666667 + * decomposition_base.powf(-2.0 * decomposition_level_count)) + + (1_f64 / 12.0) * decomposition_base.powf(-2.0 * decomposition_level_count)) +} + +/// This formula is only valid if the proper noise distributions are used and +/// if the keys used are encrypted using secure noise given by the +/// [`minimal_glwe_variance`](`super::secure_noise`) +/// and [`minimal_lwe_variance`](`super::secure_noise`) family of functions. +pub fn multi_bit_pbs_variance_132_bits_security_tuniform_gf_2_exact_mul( + input_lwe_dimension: LweDimension, + output_glwe_dimension: GlweDimension, + output_polynomial_size: PolynomialSize, + decomposition_base_log: DecompositionBaseLog, + decomposition_level_count: DecompositionLevelCount, + modulus: f64, +) -> Variance { + Variance( + multi_bit_pbs_variance_132_bits_security_tuniform_gf_2_exact_mul_impl( + input_lwe_dimension.0 as f64, + output_glwe_dimension.0 as f64, + output_polynomial_size.0 as f64, + 2.0f64.powi(decomposition_base_log.0 as i32), + decomposition_level_count.0 as f64, + modulus, + ), + ) +} + +/// This formula is only valid if the proper noise distributions are used and +/// if the keys used are encrypted using secure noise given by the +/// [`minimal_glwe_variance`](`super::secure_noise`) +/// and [`minimal_lwe_variance`](`super::secure_noise`) family of functions. +pub fn multi_bit_pbs_variance_132_bits_security_tuniform_gf_2_exact_mul_impl( + input_lwe_dimension: f64, + output_glwe_dimension: f64, + output_polynomial_size: f64, + decomposition_base: f64, + decomposition_level_count: f64, + modulus: f64, +) -> f64 { + (1_f64 / 2.0) + * input_lwe_dimension + * (1.0 + * decomposition_level_count + * output_polynomial_size + * ((4.0 - 2.88539008177793 * modulus.ln()).exp2() + + (1_f64 / 3.0) + * modulus.powf(-2.0) + * ((2.0 + * (-0.025167785 * output_glwe_dimension * output_polynomial_size + + std::f64::consts::LOG2_E * modulus.ln() + + 4.10067100000001) + .ceil()) + .exp2() + + 0.5)) + * ((1_f64 / 12.0) * decomposition_base.powf(2.0) + 0.166666666666667) + * (output_glwe_dimension + 1.0) + + (1_f64 / 6.0) * modulus.powf(-2.0) + + output_glwe_dimension + * output_polynomial_size + * (0.0208333333333333 * modulus.powf(-2.0) + + 0.0416666666666667 + * decomposition_base.powf(-2.0 * decomposition_level_count)) + + (1_f64 / 12.0) * decomposition_base.powf(-2.0 * decomposition_level_count)) +} + +/// This formula is only valid if the proper noise distributions are used and +/// if the keys used are encrypted using secure noise given by the +/// [`minimal_glwe_variance`](`super::secure_noise`) +/// and [`minimal_lwe_variance`](`super::secure_noise`) family of functions. +pub fn multi_bit_pbs_variance_132_bits_security_tuniform_gf_3_exact_mul( + input_lwe_dimension: LweDimension, + output_glwe_dimension: GlweDimension, + output_polynomial_size: PolynomialSize, + decomposition_base_log: DecompositionBaseLog, + decomposition_level_count: DecompositionLevelCount, + modulus: f64, +) -> Variance { + Variance( + multi_bit_pbs_variance_132_bits_security_tuniform_gf_3_exact_mul_impl( + input_lwe_dimension.0 as f64, + output_glwe_dimension.0 as f64, + output_polynomial_size.0 as f64, + 2.0f64.powi(decomposition_base_log.0 as i32), + decomposition_level_count.0 as f64, + modulus, + ), + ) +} + +/// This formula is only valid if the proper noise distributions are used and +/// if the keys used are encrypted using secure noise given by the +/// [`minimal_glwe_variance`](`super::secure_noise`) +/// and [`minimal_lwe_variance`](`super::secure_noise`) family of functions. +pub fn multi_bit_pbs_variance_132_bits_security_tuniform_gf_3_exact_mul_impl( + input_lwe_dimension: f64, + output_glwe_dimension: f64, + output_polynomial_size: f64, + decomposition_base: f64, + decomposition_level_count: f64, + modulus: f64, +) -> f64 { + (1_f64 / 3.0) + * input_lwe_dimension + * (2.0 + * decomposition_level_count + * output_polynomial_size + * ((4.0 - 2.88539008177793 * modulus.ln()).exp2() + + (1_f64 / 3.0) + * modulus.powf(-2.0) + * ((2.0 + * (-0.025167785 * output_glwe_dimension * output_polynomial_size + + std::f64::consts::LOG2_E * modulus.ln() + + 4.10067100000001) + .ceil()) + .exp2() + + 0.5)) + * ((1_f64 / 12.0) * decomposition_base.powf(2.0) + 0.166666666666667) + * (output_glwe_dimension + 1.0) + + (1_f64 / 6.0) * modulus.powf(-2.0) + + output_glwe_dimension + * output_polynomial_size + * (0.0208333333333333 * modulus.powf(-2.0) + + 0.0416666666666667 + * decomposition_base.powf(-2.0 * decomposition_level_count)) + + (1_f64 / 12.0) * decomposition_base.powf(-2.0 * decomposition_level_count)) +} + +/// This formula is only valid if the proper noise distributions are used and +/// if the keys used are encrypted using secure noise given by the +/// [`minimal_glwe_variance`](`super::secure_noise`) +/// and [`minimal_lwe_variance`](`super::secure_noise`) family of functions. +pub fn multi_bit_pbs_variance_132_bits_security_tuniform_gf_4_exact_mul( + input_lwe_dimension: LweDimension, + output_glwe_dimension: GlweDimension, + output_polynomial_size: PolynomialSize, + decomposition_base_log: DecompositionBaseLog, + decomposition_level_count: DecompositionLevelCount, + modulus: f64, +) -> Variance { + Variance( + multi_bit_pbs_variance_132_bits_security_tuniform_gf_4_exact_mul_impl( + input_lwe_dimension.0 as f64, + output_glwe_dimension.0 as f64, + output_polynomial_size.0 as f64, + 2.0f64.powi(decomposition_base_log.0 as i32), + decomposition_level_count.0 as f64, + modulus, + ), + ) +} + +/// This formula is only valid if the proper noise distributions are used and +/// if the keys used are encrypted using secure noise given by the +/// [`minimal_glwe_variance`](`super::secure_noise`) +/// and [`minimal_lwe_variance`](`super::secure_noise`) family of functions. +pub fn multi_bit_pbs_variance_132_bits_security_tuniform_gf_4_exact_mul_impl( + input_lwe_dimension: f64, + output_glwe_dimension: f64, + output_polynomial_size: f64, + decomposition_base: f64, + decomposition_level_count: f64, + modulus: f64, +) -> f64 { + (1_f64 / 4.0) + * input_lwe_dimension + * (4.0 + * decomposition_level_count + * output_polynomial_size + * ((4.0 - 2.88539008177793 * modulus.ln()).exp2() + + (1_f64 / 3.0) + * modulus.powf(-2.0) + * ((2.0 + * (-0.025167785 * output_glwe_dimension * output_polynomial_size + + std::f64::consts::LOG2_E * modulus.ln() + + 4.10067100000001) + .ceil()) + .exp2() + + 0.5)) + * ((1_f64 / 12.0) * decomposition_base.powf(2.0) + 0.166666666666667) + * (output_glwe_dimension + 1.0) + + (1_f64 / 6.0) * modulus.powf(-2.0) + + output_glwe_dimension + * output_polynomial_size + * (0.0208333333333333 * modulus.powf(-2.0) + + 0.0416666666666667 + * decomposition_base.powf(-2.0 * decomposition_level_count)) + + (1_f64 / 12.0) * decomposition_base.powf(-2.0 * decomposition_level_count)) +} diff --git a/tfhe/src/core_crypto/commons/noise_formulas/mod.rs b/tfhe/src/core_crypto/commons/noise_formulas/mod.rs index c0a0526a77..67d005a8a0 100644 --- a/tfhe/src/core_crypto/commons/noise_formulas/mod.rs +++ b/tfhe/src/core_crypto/commons/noise_formulas/mod.rs @@ -1,4 +1,5 @@ // This file was autogenerated, do not modify by hand. pub mod lwe_keyswitch; +pub mod lwe_multi_bit_programmable_bootstrap; pub mod lwe_programmable_bootstrap; pub mod secure_noise; diff --git a/tfhe/src/core_crypto/gpu/algorithms/test/mod.rs b/tfhe/src/core_crypto/gpu/algorithms/test/mod.rs index db058d4cb2..ff87363b71 100644 --- a/tfhe/src/core_crypto/gpu/algorithms/test/mod.rs +++ b/tfhe/src/core_crypto/gpu/algorithms/test/mod.rs @@ -6,6 +6,7 @@ mod lwe_linear_algebra; mod lwe_multi_bit_programmable_bootstrapping; mod lwe_packing_keyswitch; mod lwe_programmable_bootstrapping; +mod noise_distribution; pub struct CudaPackingKeySwitchKeys { pub lwe_sk: LweSecretKey>, diff --git a/tfhe/src/core_crypto/gpu/algorithms/test/noise_distribution/lwe_multi_bit_programmable_bootstrapping_noise.rs b/tfhe/src/core_crypto/gpu/algorithms/test/noise_distribution/lwe_multi_bit_programmable_bootstrapping_noise.rs new file mode 100644 index 0000000000..974f3502cc --- /dev/null +++ b/tfhe/src/core_crypto/gpu/algorithms/test/noise_distribution/lwe_multi_bit_programmable_bootstrapping_noise.rs @@ -0,0 +1,264 @@ +use super::*; +use crate::core_crypto::commons::noise_formulas::lwe_multi_bit_programmable_bootstrap::{ + multi_bit_pbs_variance_132_bits_security_gaussian_gf_3_fft_mul, + multi_bit_pbs_variance_132_bits_security_tuniform_gf_3_fft_mul, +}; +use crate::core_crypto::commons::noise_formulas::secure_noise::minimal_lwe_variance_for_132_bits_security_gaussian; +use crate::core_crypto::commons::test_tools::{torus_modular_diff, variance}; +use crate::core_crypto::gpu::glwe_ciphertext_list::CudaGlweCiphertextList; +use crate::core_crypto::gpu::lwe_ciphertext_list::CudaLweCiphertextList; +use crate::core_crypto::gpu::lwe_multi_bit_bootstrap_key::CudaLweMultiBitBootstrapKey; +use crate::core_crypto::gpu::vec::{CudaVec, GpuIndex}; +use crate::core_crypto::gpu::{cuda_multi_bit_programmable_bootstrap_lwe_ciphertext, CudaStreams}; +use itertools::Itertools; +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_multi_bit_pbs_decrypt_custom_mod(params: MultiBitTestParams) +where + Scalar: UnsignedTorus + Sync + Send + CastFrom + CastInto, +{ + let input_lwe_dimension = params.input_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.decomp_base_log; + let pbs_decomposition_level_count = params.decomp_level_count; + let grouping_factor = params.grouping_factor; + let number_of_messages = 1; + + let gpu_index = GpuIndex(0); + let stream = CudaStreams::new_single_gpu(gpu_index); + + 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 = if params.tuniform { + multi_bit_pbs_variance_132_bits_security_tuniform_gf_3_fft_mul( + input_lwe_dimension, + glwe_dimension, + polynomial_size, + pbs_decomposition_base_log, + pbs_decomposition_level_count, + modulus_as_f64, + ) + } else { + multi_bit_pbs_variance_132_bits_security_gaussian_gf_3_fft_mul( + input_lwe_dimension, + glwe_dimension, + polynomial_size, + pbs_decomposition_base_log, + pbs_decomposition_level_count, + modulus_as_f64, + ) + }; + + 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 * >::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 output_lwe_dimension = output_lwe_secret_key.lwe_dimension(); + + 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 + )); + + let mut bsk = LweMultiBitBootstrapKey::new( + Scalar::ZERO, + glwe_dimension.to_glwe_size(), + polynomial_size, + pbs_decomposition_base_log, + pbs_decomposition_level_count, + input_lwe_dimension, + grouping_factor, + ciphertext_modulus, + ); + + par_generate_lwe_multi_bit_bootstrap_key( + &input_lwe_secret_key, + &output_glwe_secret_key, + &mut bsk, + glwe_noise_distribution, + &mut rsc.encryption_random_generator, + ); + + assert!(check_encrypted_content_respects_mod( + &*bsk, + ciphertext_modulus + )); + + let d_bsk = CudaLweMultiBitBootstrapKey::from_lwe_multi_bit_bootstrap_key(&bsk, &stream); + + 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 d_lwe_ciphertext_in = + CudaLweCiphertextList::from_lwe_ciphertext(&lwe_ciphertext_in, &stream); + let mut d_out_pbs_ct = CudaLweCiphertextList::new( + output_lwe_dimension, + LweCiphertextCount(1), + ciphertext_modulus, + &stream, + ); + let d_accumulator = + CudaGlweCiphertextList::from_glwe_ciphertext(&accumulator, &stream); + + let mut test_vector_indexes: Vec = vec![Scalar::ZERO; number_of_messages]; + for (i, ind) in test_vector_indexes.iter_mut().enumerate() { + *ind = >::cast_into(i); + } + + let mut d_test_vector_indexes = + unsafe { CudaVec::::new_async(number_of_messages, &stream, 0) }; + unsafe { + d_test_vector_indexes.copy_from_cpu_async(&test_vector_indexes, &stream, 0) + }; + + let num_blocks = d_lwe_ciphertext_in.0.lwe_ciphertext_count.0; + let lwe_indexes_usize: Vec = (0..num_blocks).collect_vec(); + let lwe_indexes = lwe_indexes_usize + .iter() + .map(|&x| >::cast_into(x)) + .collect_vec(); + let mut d_output_indexes = + unsafe { CudaVec::::new_async(num_blocks, &stream, 0) }; + let mut d_input_indexes = + unsafe { CudaVec::::new_async(num_blocks, &stream, 0) }; + unsafe { + d_input_indexes.copy_from_cpu_async(&lwe_indexes, &stream, 0); + d_output_indexes.copy_from_cpu_async(&lwe_indexes, &stream, 0); + } + + cuda_multi_bit_programmable_bootstrap_lwe_ciphertext( + &d_lwe_ciphertext_in, + &mut d_out_pbs_ct, + &d_accumulator, + &d_test_vector_indexes, + &d_output_indexes, + &d_input_indexes, + &d_bsk, + &stream, + ); + + let out_pbs_ct = d_out_pbs_ct.into_lwe_ciphertext(&stream); + 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 minimal_variance = minimal_lwe_variance_for_132_bits_security_gaussian( + bsk.output_lwe_dimension(), + if ciphertext_modulus.is_native_modulus() { + 2.0f64.powi(Scalar::BITS as i32) + } else { + ciphertext_modulus.get_custom_modulus() as f64 + }, + ); + + // 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:?}"); + println!("minimal_variance={minimal_variance:?}"); + + if measured_variance.0 < expected_variance.0 { + // We are in the clear as long as we have at least the noise for security + assert!( + measured_variance.0 >= minimal_variance.0, + "Found insecure variance after PBS\n\ + measure_variance={measured_variance:?}\n\ + minimal_variance={minimal_variance:?}" + ); + } else { + // Check we are not too far from the expected variance if we are bigger + let var_abs_diff = (expected_variance.0 - measured_variance.0).abs(); + let tolerance_threshold = RELATIVE_TOLERANCE * expected_variance.0; + + 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_parameterized_test!(lwe_encrypt_multi_bit_pbs_decrypt_custom_mod { + NOISE_TEST_PARAMS_GPU_MULTI_BIT_GROUP_3_4_BITS_NATIVE_U64_132_BITS_GAUSSIAN +}); diff --git a/tfhe/src/core_crypto/gpu/algorithms/test/noise_distribution/lwe_programmable_bootstrapping_noise.rs b/tfhe/src/core_crypto/gpu/algorithms/test/noise_distribution/lwe_programmable_bootstrapping_noise.rs new file mode 100644 index 0000000000..27419214e8 --- /dev/null +++ b/tfhe/src/core_crypto/gpu/algorithms/test/noise_distribution/lwe_programmable_bootstrapping_noise.rs @@ -0,0 +1,248 @@ +use super::*; +use crate::core_crypto::commons::noise_formulas::lwe_programmable_bootstrap::pbs_variance_132_bits_security_gaussian; +use crate::core_crypto::commons::noise_formulas::secure_noise::minimal_lwe_variance_for_132_bits_security_gaussian; +use crate::core_crypto::commons::test_tools::{torus_modular_diff, variance}; +use crate::core_crypto::gpu::glwe_ciphertext_list::CudaGlweCiphertextList; +use crate::core_crypto::gpu::lwe_bootstrap_key::CudaLweBootstrapKey; +use crate::core_crypto::gpu::lwe_ciphertext_list::CudaLweCiphertextList; +use crate::core_crypto::gpu::vec::{CudaVec, GpuIndex}; +use crate::core_crypto::gpu::{cuda_programmable_bootstrap_lwe_ciphertext, CudaStreams}; +use itertools::Itertools; +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(params: ClassicTestParams) +where + Scalar: UnsignedTorus + Sync + Send + CastFrom + CastInto, +{ + 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 number_of_messages = 1; + + let gpu_index = GpuIndex(0); + let stream = CudaStreams::new_single_gpu(gpu_index); + + 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 = pbs_variance_132_bits_security_gaussian( + input_lwe_dimension, + glwe_dimension, + polynomial_size, + pbs_decomposition_base_log, + pbs_decomposition_level_count, + modulus_as_f64, + ); + + 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 * >::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 output_lwe_dimension = output_lwe_secret_key.lwe_dimension(); + + 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 + )); + + let mut bsk = LweBootstrapKey::new( + Scalar::ZERO, + glwe_dimension.to_glwe_size(), + polynomial_size, + pbs_decomposition_base_log, + pbs_decomposition_level_count, + input_lwe_dimension, + ciphertext_modulus, + ); + + par_generate_lwe_bootstrap_key( + &input_lwe_secret_key, + &output_glwe_secret_key, + &mut bsk, + glwe_noise_distribution, + &mut rsc.encryption_random_generator, + ); + + assert!(check_encrypted_content_respects_mod( + &*bsk, + ciphertext_modulus + )); + + let d_bsk = CudaLweBootstrapKey::from_lwe_bootstrap_key(&bsk, &stream); + 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 d_lwe_ciphertext_in = + CudaLweCiphertextList::from_lwe_ciphertext(&lwe_ciphertext_in, &stream); + let mut d_out_pbs_ct = CudaLweCiphertextList::new( + output_lwe_dimension, + LweCiphertextCount(1), + ciphertext_modulus, + &stream, + ); + let d_accumulator = + CudaGlweCiphertextList::from_glwe_ciphertext(&accumulator, &stream); + + let mut test_vector_indexes: Vec = vec![Scalar::ZERO; number_of_messages]; + for (i, ind) in test_vector_indexes.iter_mut().enumerate() { + *ind = >::cast_into(i); + } + + let mut d_test_vector_indexes = + unsafe { CudaVec::::new_async(number_of_messages, &stream, 0) }; + unsafe { + d_test_vector_indexes.copy_from_cpu_async(&test_vector_indexes, &stream, 0) + }; + + let num_blocks = d_lwe_ciphertext_in.0.lwe_ciphertext_count.0; + let lwe_indexes_usize: Vec = (0..num_blocks).collect_vec(); + let lwe_indexes = lwe_indexes_usize + .iter() + .map(|&x| >::cast_into(x)) + .collect_vec(); + let mut d_output_indexes = + unsafe { CudaVec::::new_async(num_blocks, &stream, 0) }; + let mut d_input_indexes = + unsafe { CudaVec::::new_async(num_blocks, &stream, 0) }; + unsafe { + d_input_indexes.copy_from_cpu_async(&lwe_indexes, &stream, 0); + d_output_indexes.copy_from_cpu_async(&lwe_indexes, &stream, 0); + } + + cuda_programmable_bootstrap_lwe_ciphertext( + &d_lwe_ciphertext_in, + &mut d_out_pbs_ct, + &d_accumulator, + &d_test_vector_indexes, + &d_output_indexes, + &d_input_indexes, + LweCiphertextCount(num_blocks), + &d_bsk, + &stream, + ); + + let out_pbs_ct = d_out_pbs_ct.into_lwe_ciphertext(&stream); + 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 minimal_variance = minimal_lwe_variance_for_132_bits_security_gaussian( + bsk.output_lwe_dimension(), + if ciphertext_modulus.is_native_modulus() { + 2.0f64.powi(Scalar::BITS as i32) + } else { + ciphertext_modulus.get_custom_modulus() as f64 + }, + ); + + // 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:?}"); + println!("minimal_variance={minimal_variance:?}"); + + if measured_variance.0 < expected_variance.0 { + // We are in the clear as long as we have at least the noise for security + assert!( + measured_variance.0 >= minimal_variance.0, + "Found insecure variance after PBS\n\ + measure_variance={measured_variance:?}\n\ + minimal_variance={minimal_variance:?}" + ); + } else { + // Check we are not too far from the expected variance if we are bigger + let var_abs_diff = (expected_variance.0 - measured_variance.0).abs(); + let tolerance_threshold = RELATIVE_TOLERANCE * expected_variance.0; + + 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_parameterized_test!(lwe_encrypt_pbs_decrypt_custom_mod { + NOISE_TEST_PARAMS_4_BITS_NATIVE_U64_132_BITS_GAUSSIAN +}); diff --git a/tfhe/src/core_crypto/gpu/algorithms/test/noise_distribution/mod.rs b/tfhe/src/core_crypto/gpu/algorithms/test/noise_distribution/mod.rs new file mode 100644 index 0000000000..a4efa19e15 --- /dev/null +++ b/tfhe/src/core_crypto/gpu/algorithms/test/noise_distribution/mod.rs @@ -0,0 +1,49 @@ +use super::*; + +mod lwe_multi_bit_programmable_bootstrapping_noise; +mod lwe_programmable_bootstrapping_noise; + +#[allow(clippy::excessive_precision)] +pub const NOISE_TEST_PARAMS_4_BITS_NATIVE_U64_132_BITS_GAUSSIAN: ClassicTestParams = + 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(), + }; +#[allow(clippy::excessive_precision)] +pub const NOISE_TEST_PARAMS_GPU_MULTI_BIT_GROUP_3_4_BITS_NATIVE_U64_132_BITS_GAUSSIAN: + MultiBitTestParams = MultiBitTestParams { + input_lwe_dimension: LweDimension(909), + lwe_noise_distribution: DynamicDistribution::new_gaussian_from_std_dev(StandardDev( + 9.743962418842028e-07, + )), + decomp_base_log: DecompositionBaseLog(21), + decomp_level_count: DecompositionLevelCount(1), + glwe_dimension: GlweDimension(1), + polynomial_size: PolynomialSize(2048), + glwe_noise_distribution: DynamicDistribution::new_gaussian_from_std_dev(StandardDev( + 2.845267479601915e-15, + )), + message_modulus_log: MessageModulusLog(4), + ciphertext_modulus: CiphertextModulus::new_native(), + grouping_factor: LweBskGroupingFactor(3), + thread_count: ThreadCount(1), + tuniform: false, +}; diff --git a/tfhe/src/shortint/parameters/multi_bit/gaussian/p_fail_2_minus_64/ks_pbs_gpu.rs b/tfhe/src/shortint/parameters/multi_bit/gaussian/p_fail_2_minus_64/ks_pbs_gpu.rs index 5eaf41f253..e2d477bcf1 100644 --- a/tfhe/src/shortint/parameters/multi_bit/gaussian/p_fail_2_minus_64/ks_pbs_gpu.rs +++ b/tfhe/src/shortint/parameters/multi_bit/gaussian/p_fail_2_minus_64/ks_pbs_gpu.rs @@ -82,8 +82,7 @@ pub const PARAM_GPU_MULTI_BIT_GROUP_2_MESSAGE_3_CARRY_3_KS_PBS_GAUSSIAN_2M64: // Group 3 -// p-fail = 2^-68.192, algorithmic cost ~ 64, 2-norm = 3 -pub const PARAM_GPU_MULTI_BIT_GROUP_3_MESSAGE_1_CARRY_1_KS_PBS_GAUSSIAN_2M64: +pub const V0_10_PARAM_GPU_MULTI_BIT_GROUP_3_MESSAGE_1_CARRY_1_KS_PBS_GAUSSIAN_2M64: MultiBitPBSParameters = MultiBitPBSParameters { lwe_dimension: LweDimension(720), glwe_dimension: GlweDimension(2), @@ -108,7 +107,7 @@ pub const PARAM_GPU_MULTI_BIT_GROUP_3_MESSAGE_1_CARRY_1_KS_PBS_GAUSSIAN_2M64: deterministic_execution: false, }; // p-fail = 2^-64.655, algorithmic cost ~ 79, 2-norm = 5 -pub const PARAM_GPU_MULTI_BIT_GROUP_3_MESSAGE_2_CARRY_2_KS_PBS_GAUSSIAN_2M64: +pub const V0_10_PARAM_GPU_MULTI_BIT_GROUP_3_MESSAGE_2_CARRY_2_KS_PBS_GAUSSIAN_2M64: MultiBitPBSParameters = MultiBitPBSParameters { lwe_dimension: LweDimension(837), glwe_dimension: GlweDimension(1), @@ -133,7 +132,82 @@ pub const PARAM_GPU_MULTI_BIT_GROUP_3_MESSAGE_2_CARRY_2_KS_PBS_GAUSSIAN_2M64: deterministic_execution: false, }; // p-fail = 2^-64.372, algorithmic cost ~ 641, 2-norm = 9 -pub const PARAM_GPU_MULTI_BIT_GROUP_3_MESSAGE_3_CARRY_3_KS_PBS_GAUSSIAN_2M64: +pub const V0_10_PARAM_GPU_MULTI_BIT_GROUP_3_MESSAGE_3_CARRY_3_KS_PBS_GAUSSIAN_2M64: + MultiBitPBSParameters = MultiBitPBSParameters { + lwe_dimension: LweDimension(978), + glwe_dimension: GlweDimension(1), + polynomial_size: PolynomialSize(8192), + lwe_noise_distribution: DynamicDistribution::new_gaussian_from_std_dev(StandardDev( + 2.962875621642539e-07, + )), + glwe_noise_distribution: DynamicDistribution::new_gaussian_from_std_dev(StandardDev( + 2.168404344971009e-19, + )), + pbs_base_log: DecompositionBaseLog(14), + pbs_level: DecompositionLevelCount(2), + ks_base_log: DecompositionBaseLog(3), + ks_level: DecompositionLevelCount(6), + message_modulus: MessageModulus(8), + carry_modulus: CarryModulus(8), + max_noise_level: MaxNoiseLevel::new(9), + log2_p_fail: -64.372, + ciphertext_modulus: CiphertextModulus::new_native(), + encryption_key_choice: EncryptionKeyChoice::Big, + grouping_factor: LweBskGroupingFactor(3), + deterministic_execution: false, +}; +// p-fail = 2^-68.192, algorithmic cost ~ 64, 2-norm = 3 +pub const V0_11_PARAM_GPU_MULTI_BIT_GROUP_3_MESSAGE_1_CARRY_1_KS_PBS_GAUSSIAN_2M64: + MultiBitPBSParameters = MultiBitPBSParameters { + lwe_dimension: LweDimension(768), + glwe_dimension: GlweDimension(3), + polynomial_size: PolynomialSize(512), + lwe_noise_distribution: DynamicDistribution::new_gaussian_from_std_dev(StandardDev( + 1.1098369627275701e-05, + )), + glwe_noise_distribution: DynamicDistribution::new_gaussian_from_std_dev(StandardDev( + 1.9524392655548086e-11, + )), + pbs_base_log: DecompositionBaseLog(17), + pbs_level: DecompositionLevelCount(1), + ks_base_log: DecompositionBaseLog(2), + ks_level: DecompositionLevelCount(7), + message_modulus: MessageModulus(2), + carry_modulus: CarryModulus(2), + max_noise_level: MaxNoiseLevel::new(3), + log2_p_fail: -68.192, + ciphertext_modulus: CiphertextModulus::new_native(), + encryption_key_choice: EncryptionKeyChoice::Big, + grouping_factor: LweBskGroupingFactor(3), + deterministic_execution: false, +}; +// p-fail = 2^-64.655, algorithmic cost ~ 79, 2-norm = 5 +pub const V0_11_PARAM_GPU_MULTI_BIT_GROUP_3_MESSAGE_2_CARRY_2_KS_PBS_GAUSSIAN_2M64: + MultiBitPBSParameters = MultiBitPBSParameters { + lwe_dimension: LweDimension(837), + glwe_dimension: GlweDimension(1), + polynomial_size: PolynomialSize(2048), + lwe_noise_distribution: DynamicDistribution::new_gaussian_from_std_dev(StandardDev( + 3.3747142481837397e-06, + )), + glwe_noise_distribution: DynamicDistribution::new_gaussian_from_std_dev(StandardDev( + 2.845267479601915e-15, + )), + pbs_base_log: DecompositionBaseLog(22), + pbs_level: DecompositionLevelCount(1), + ks_base_log: DecompositionBaseLog(3), + ks_level: DecompositionLevelCount(5), + message_modulus: MessageModulus(4), + carry_modulus: CarryModulus(4), + max_noise_level: MaxNoiseLevel::new(5), + log2_p_fail: -64.655, + ciphertext_modulus: CiphertextModulus::new_native(), + encryption_key_choice: EncryptionKeyChoice::Big, + grouping_factor: LweBskGroupingFactor(3), + deterministic_execution: false, +}; +// p-fail = 2^-64.372, algorithmic cost ~ 641, 2-norm = 9 +pub const V0_11_PARAM_GPU_MULTI_BIT_GROUP_3_MESSAGE_3_CARRY_3_KS_PBS_GAUSSIAN_2M64: MultiBitPBSParameters = MultiBitPBSParameters { lwe_dimension: LweDimension(978), glwe_dimension: GlweDimension(1), @@ -157,3 +231,13 @@ pub const PARAM_GPU_MULTI_BIT_GROUP_3_MESSAGE_3_CARRY_3_KS_PBS_GAUSSIAN_2M64: grouping_factor: LweBskGroupingFactor(3), deterministic_execution: false, }; + +pub const PARAM_GPU_MULTI_BIT_GROUP_3_MESSAGE_1_CARRY_1_KS_PBS_GAUSSIAN_2M64: + MultiBitPBSParameters = + V0_11_PARAM_GPU_MULTI_BIT_GROUP_3_MESSAGE_1_CARRY_1_KS_PBS_GAUSSIAN_2M64; +pub const PARAM_GPU_MULTI_BIT_GROUP_3_MESSAGE_2_CARRY_2_KS_PBS_GAUSSIAN_2M64: + MultiBitPBSParameters = + V0_11_PARAM_GPU_MULTI_BIT_GROUP_3_MESSAGE_2_CARRY_2_KS_PBS_GAUSSIAN_2M64; +pub const PARAM_GPU_MULTI_BIT_GROUP_3_MESSAGE_3_CARRY_3_KS_PBS_GAUSSIAN_2M64: + MultiBitPBSParameters = + V0_11_PARAM_GPU_MULTI_BIT_GROUP_3_MESSAGE_3_CARRY_3_KS_PBS_GAUSSIAN_2M64; diff --git a/tfhe/src/shortint/parameters/multi_bit/gaussian/p_fail_2_minus_64/mod.rs b/tfhe/src/shortint/parameters/multi_bit/gaussian/p_fail_2_minus_64/mod.rs index c5bc259b27..3b07554597 100644 --- a/tfhe/src/shortint/parameters/multi_bit/gaussian/p_fail_2_minus_64/mod.rs +++ b/tfhe/src/shortint/parameters/multi_bit/gaussian/p_fail_2_minus_64/mod.rs @@ -1,2 +1,8 @@ pub mod ks_pbs; pub mod ks_pbs_gpu; + +pub use ks_pbs_gpu::{ + PARAM_GPU_MULTI_BIT_GROUP_3_MESSAGE_1_CARRY_1_KS_PBS_GAUSSIAN_2M64, + PARAM_GPU_MULTI_BIT_GROUP_3_MESSAGE_2_CARRY_2_KS_PBS_GAUSSIAN_2M64, + PARAM_GPU_MULTI_BIT_GROUP_3_MESSAGE_3_CARRY_3_KS_PBS_GAUSSIAN_2M64, +}; diff --git a/tfhe/src/shortint/parameters/multi_bit/tuniform/p_fail_2_minus_64/ks_pbs_gpu.rs b/tfhe/src/shortint/parameters/multi_bit/tuniform/p_fail_2_minus_64/ks_pbs_gpu.rs index e86da2c167..597f8a976f 100644 --- a/tfhe/src/shortint/parameters/multi_bit/tuniform/p_fail_2_minus_64/ks_pbs_gpu.rs +++ b/tfhe/src/shortint/parameters/multi_bit/tuniform/p_fail_2_minus_64/ks_pbs_gpu.rs @@ -2,7 +2,7 @@ use crate::core_crypto::prelude::*; use crate::shortint::ciphertext::MaxNoiseLevel; use crate::shortint::parameters::multi_bit::MultiBitPBSParameters; use crate::shortint::parameters::{CarryModulus, MessageModulus}; -pub const PARAM_GPU_MULTI_BIT_GROUP_3_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M64: +pub const V0_10_PARAM_GPU_MULTI_BIT_GROUP_3_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M64: MultiBitPBSParameters = MultiBitPBSParameters { lwe_dimension: LweDimension(882), glwe_dimension: GlweDimension(1), @@ -22,3 +22,27 @@ pub const PARAM_GPU_MULTI_BIT_GROUP_3_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M64: grouping_factor: LweBskGroupingFactor(3), deterministic_execution: true, }; + +pub const V0_11_PARAM_GPU_MULTI_BIT_GROUP_3_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M64: + MultiBitPBSParameters = MultiBitPBSParameters { + lwe_dimension: LweDimension(879), + glwe_dimension: GlweDimension(1), + polynomial_size: PolynomialSize(2048), + lwe_noise_distribution: DynamicDistribution::new_t_uniform(46), + glwe_noise_distribution: DynamicDistribution::new_t_uniform(17), + pbs_base_log: DecompositionBaseLog(22), + pbs_level: DecompositionLevelCount(1), + ks_base_log: DecompositionBaseLog(3), + ks_level: DecompositionLevelCount(5), + message_modulus: MessageModulus(4), + carry_modulus: CarryModulus(4), + max_noise_level: MaxNoiseLevel::new(5), + log2_p_fail: -64.59, + ciphertext_modulus: CiphertextModulus::new_native(), + encryption_key_choice: EncryptionKeyChoice::Big, + grouping_factor: LweBskGroupingFactor(3), + deterministic_execution: true, +}; +pub const PARAM_GPU_MULTI_BIT_GROUP_3_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M64: + MultiBitPBSParameters = + V0_11_PARAM_GPU_MULTI_BIT_GROUP_3_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M64; diff --git a/tfhe/src/shortint/parameters/multi_bit/tuniform/p_fail_2_minus_64/mod.rs b/tfhe/src/shortint/parameters/multi_bit/tuniform/p_fail_2_minus_64/mod.rs index 174d7fd627..1409dad4f0 100644 --- a/tfhe/src/shortint/parameters/multi_bit/tuniform/p_fail_2_minus_64/mod.rs +++ b/tfhe/src/shortint/parameters/multi_bit/tuniform/p_fail_2_minus_64/mod.rs @@ -1 +1,2 @@ pub mod ks_pbs_gpu; +pub use ks_pbs_gpu::PARAM_GPU_MULTI_BIT_GROUP_3_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M64;