Compare commits

...

9 Commits

Author SHA1 Message Date
Agnes Leroy
fb285b6c6e Put back the full ap 2024-11-21 13:13:39 +01:00
Agnes Leroy
fafe7b5816 Tweak params to test 2-64 pfail 2024-11-21 11:30:17 +01:00
Agnes Leroy
e76906a084 pfail measurement gpu 2024-11-18 17:01:43 +01:00
Agnes Leroy
9b9542149f chore(gpu): add noise test for the classical & multi-bit PBS 2024-11-18 16:31:15 +01:00
Guillermo Oyarzun
3f34300130 fix correct number of blocks per iteration 2024-11-18 16:29:53 +01:00
Guillermo Oyarzun
387f8d92b6 fix encryption each iteration 2024-11-18 16:29:53 +01:00
Guillermo Oyarzun
47fe167466 fix proper output 2024-11-18 16:29:52 +01:00
Guillermo Oyarzun
5fdf942d90 chore(gpu): add pgail gpu test 2024-11-18 16:29:52 +01:00
Arthur Meyre
da1d55e808 wip: pfail 2024-11-18 16:29:52 +01:00
12 changed files with 3052 additions and 3 deletions

86
tfhe/examples/p_fail.rs Normal file
View File

@@ -0,0 +1,86 @@
use rayon::prelude::*;
use tfhe::core_crypto::prelude::*;
use tfhe::shortint::ciphertext::MaxNoiseLevel;
use tfhe::shortint::engine::ShortintEngine;
use tfhe::shortint::gen_keys;
use tfhe::shortint::parameters::multi_bit::MultiBitPBSParameters;
use tfhe::shortint::parameters::{CarryModulus, MessageModulus};
pub const PARAM_MULTI_BIT_GROUP_3_MESSAGE_4_CARRY_2_KS_PBS_GAUSSIAN_2M5_5: MultiBitPBSParameters =
MultiBitPBSParameters {
lwe_dimension: LweDimension(891),
glwe_dimension: GlweDimension(1),
polynomial_size: PolynomialSize(2048),
lwe_noise_distribution: DynamicDistribution::new_gaussian_from_std_dev(StandardDev(
1.3292631075564801e-06,
)),
glwe_noise_distribution: DynamicDistribution::new_gaussian_from_std_dev(StandardDev(
2.845267479601915e-15,
)),
pbs_base_log: DecompositionBaseLog(21),
pbs_level: DecompositionLevelCount(1),
ks_base_log: DecompositionBaseLog(4),
ks_level: DecompositionLevelCount(4),
message_modulus: MessageModulus(8),
carry_modulus: CarryModulus(4),
max_noise_level: MaxNoiseLevel::new(5),
log2_p_fail: -5.5,
ciphertext_modulus: CiphertextModulus::new_native(),
encryption_key_choice: EncryptionKeyChoice::Big,
grouping_factor: LweBskGroupingFactor(3),
deterministic_execution: false,
};
pub fn main() {
let fhe_params = PARAM_MULTI_BIT_GROUP_3_MESSAGE_4_CARRY_2_KS_PBS_GAUSSIAN_2M5_5;
let max_scalar_mul = fhe_params.max_noise_level.get() as u8;
let expected_fails = 500;
println!("running");
// let num_pbs = (1 << 6) * expected_fails;
let num_pbs = (2.0_f32.powf(5.5).ceil() as i32) * expected_fails;
let (cks, sks) = gen_keys(fhe_params);
let lut = sks.generate_lookup_table(|x| x);
let start = std::time::Instant::now();
let actual_fails: u32 = (0..num_pbs)
.into_par_iter()
.map(|_i| {
// let mut engine = ShortintEngine::new();
// let cks = engine.new_client_key(fhe_params.into());
// let sks = engine.new_server_key(&cks);
// let mut ct = engine.encrypt(&cks, 0);
// let lut = sks.generate_lookup_table(|x| x);
let mut ct = cks.encrypt(0);
// Get baseline noise after PBS
sks.unchecked_scalar_mul_assign(&mut ct, max_scalar_mul);
sks.apply_lookup_table_assign(&mut ct, &lut);
// // PBS with baseline noise as input
// sks.unchecked_scalar_mul_assign(&mut ct, max_scalar_mul);
// sks.apply_lookup_table_assign(&mut ct, &lut);
let dec = cks.decrypt(&ct);
if dec != 0 {
1
} else {
0
}
})
.sum();
let elapsed = start.elapsed();
println!("Elapsed: {elapsed:?}");
println!("Expected fails: {expected_fails}");
println!("Got fails: {actual_fails}");
}

1960
tfhe/examples/p_fail_gpu.rs Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,211 @@
use super::*;
use crate::core_crypto::commons::noise_formulas::lwe_multi_bit_programmable_bootstrap::multi_bit_pbs_variance_132_bits_security_gaussian_gf_3;
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<Scalar>(params: MultiBitTestParams<Scalar>)
where
Scalar: UnsignedTorus + Sync + Send + CastFrom<usize> + CastInto<usize>,
{
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 = multi_bit_pbs_variance_132_bits_security_gaussian_gf_3(
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 * <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_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_parametrized_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
});

View File

@@ -2,7 +2,9 @@ use super::*;
mod lwe_encryption_noise;
mod lwe_keyswitch_noise;
mod lwe_multi_bit_programmable_bootstrapping_noise;
mod lwe_programmable_bootstrapping_noise;
mod pfail_multi_bit;
#[allow(clippy::excessive_precision)]
pub const NOISE_TEST_PARAMS_4_BITS_NATIVE_U64_132_BITS_GAUSSIAN: ClassicTestParams<u64> =
@@ -28,3 +30,41 @@ pub const NOISE_TEST_PARAMS_4_BITS_NATIVE_U64_132_BITS_GAUSSIAN: ClassicTestPara
message_modulus_log: MessageModulusLog(4),
ciphertext_modulus: CiphertextModulus::new_native(),
};
#[allow(clippy::excessive_precision)]
pub const NOISE_TEST_PARAMS_MULTI_BIT_GROUP_3_4_BITS_NATIVE_U64_132_BITS_GAUSSIAN:
MultiBitTestParams<u64> = MultiBitTestParams {
input_lwe_dimension: LweDimension(837),
lwe_noise_distribution: DynamicDistribution::new_gaussian_from_std_dev(StandardDev(
3.3747142481837397e-06,
)),
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(8),
};
#[allow(clippy::excessive_precision)]
pub const PFAIL_TEST_PARAMS_MULTI_BIT_GROUP_3_6_BITS_NATIVE_U64_132_BITS_GAUSSIAN:
MultiBitTestParams<u64> = MultiBitTestParams {
input_lwe_dimension: LweDimension(522),
lwe_noise_distribution: DynamicDistribution::new_gaussian_from_std_dev(StandardDev(
0.0007736698118352694,
)),
decomp_base_log: DecompositionBaseLog(21),
decomp_level_count: DecompositionLevelCount(1),
glwe_dimension: GlweDimension(1),
polynomial_size: PolynomialSize(4096),
glwe_noise_distribution: DynamicDistribution::new_gaussian_from_std_dev(StandardDev(
0.0000000000000000002168404344971009,
)),
message_modulus_log: MessageModulusLog(6),
ciphertext_modulus: CiphertextModulus::new_native(),
grouping_factor: LweBskGroupingFactor(3),
thread_count: ThreadCount(8),
};

View File

@@ -0,0 +1,140 @@
use super::*;
use crate::core_crypto::commons::noise_formulas::lwe_multi_bit_programmable_bootstrap::multi_bit_pbs_variance_132_bits_security_gaussian_gf_3;
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 pfail_multi_bit_pbs_group_3<Scalar>(params: MultiBitTestParams<Scalar>)
where
Scalar: UnsignedTorus + Sync + Send + CastFrom<usize> + CastInto<usize>,
{
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 mut rsc = TestResources::new();
let f = |x: Scalar| x;
let delta: Scalar = encoding_with_padding / msg_modulus;
let mut msg = msg_modulus;
let expected_fails = 100;
let pfail = 2.0_f64.powi(-14);
let num_samples = ((expected_fails as f64) / pfail) as usize;
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,
);
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,
);
let msg = Scalar::ZERO;
let fails: i32 = (0..num_samples).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,
);
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,
);
let decrypted = decrypt_lwe_ciphertext(&output_lwe_secret_key, &out_pbs_ct);
let decoded = round_decode(decrypted.0, delta) % msg_modulus;
if decoded == f(msg) {
0
} else {
1
}
}).sum();
panic!("Got fails: {}, expected fails: {}", fails, expected_fails);
}
create_parametrized_test!(pfail_multi_bit_pbs_group_3 {
PFAIL_TEST_PARAMS_MULTI_BIT_GROUP_3_6_BITS_NATIVE_U64_132_BITS_GAUSSIAN
});

View File

@@ -0,0 +1,63 @@
// This file was autogenerated, do not modify by hand.
use crate::core_crypto::commons::dispersion::Variance;
use crate::core_crypto::commons::parameters::*;
/// 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(
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_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_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_f64)
* input_lwe_dimension
* (3.44492863492271e-32
* decomposition_base.powf(2.0)
* decomposition_level_count
* output_polynomial_size.powf(2.0)
* (output_glwe_dimension + 1.0)
+ 8.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 / 12.0) * modulus.powf(-2.0)
+ (1_f64 / 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 / 24.0) * decomposition_base.powf(-2.0 * decomposition_level_count))
}

View File

@@ -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;

View File

@@ -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<Scalar: UnsignedInteger> {
pub lwe_sk: LweSecretKey<Vec<Scalar>>,

View File

@@ -0,0 +1,251 @@
use super::*;
use crate::core_crypto::commons::noise_formulas::lwe_multi_bit_programmable_bootstrap::multi_bit_pbs_variance_132_bits_security_gaussian_gf_3;
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::lwe_multi_bit_bootstrap_key::CudaLweMultiBitBootstrapKey;
use crate::core_crypto::gpu::vec::CudaVec;
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<Scalar>(params: MultiBitTestParams<Scalar>)
where
Scalar: UnsignedTorus + Sync + Send + CastFrom<usize> + CastInto<usize>,
{
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 = 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 = multi_bit_pbs_variance_132_bits_security_gaussian_gf_3(
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 * <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 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<Scalar> = vec![Scalar::ZERO; number_of_messages];
for (i, ind) in test_vector_indexes.iter_mut().enumerate() {
*ind = <usize as CastInto<Scalar>>::cast_into(i);
}
let mut d_test_vector_indexes =
unsafe { CudaVec::<Scalar>::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<usize> = (0..num_blocks).collect_vec();
let lwe_indexes = lwe_indexes_usize
.iter()
.map(|&x| <usize as CastInto<Scalar>>::cast_into(x))
.collect_vec();
let mut d_output_indexes =
unsafe { CudaVec::<Scalar>::new_async(num_blocks, &stream, 0) };
let mut d_input_indexes =
unsafe { CudaVec::<Scalar>::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_parametrized_test!(lwe_encrypt_multi_bit_pbs_decrypt_custom_mod {
NOISE_TEST_PARAMS_GPU_MULTI_BIT_GROUP_3_4_BITS_NATIVE_U64_132_BITS_GAUSSIAN
});

View File

@@ -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;
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<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 number_of_messages = 1;
let gpu_index = 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 * <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 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<Scalar> = vec![Scalar::ZERO; number_of_messages];
for (i, ind) in test_vector_indexes.iter_mut().enumerate() {
*ind = <usize as CastInto<Scalar>>::cast_into(i);
}
let mut d_test_vector_indexes =
unsafe { CudaVec::<Scalar>::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<usize> = (0..num_blocks).collect_vec();
let lwe_indexes = lwe_indexes_usize
.iter()
.map(|&x| <usize as CastInto<Scalar>>::cast_into(x))
.collect_vec();
let mut d_output_indexes =
unsafe { CudaVec::<Scalar>::new_async(num_blocks, &stream, 0) };
let mut d_input_indexes =
unsafe { CudaVec::<Scalar>::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_parametrized_test!(lwe_encrypt_pbs_decrypt_custom_mod {
NOISE_TEST_PARAMS_4_BITS_NATIVE_U64_132_BITS_GAUSSIAN
});

View File

@@ -0,0 +1,48 @@
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<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(),
};
#[allow(clippy::excessive_precision)]
pub const NOISE_TEST_PARAMS_GPU_MULTI_BIT_GROUP_3_4_BITS_NATIVE_U64_132_BITS_GAUSSIAN:
MultiBitTestParams<u64> = MultiBitTestParams {
input_lwe_dimension: LweDimension(837),
lwe_noise_distribution: DynamicDistribution::new_gaussian_from_std_dev(StandardDev(
3.3747142481837397e-06,
)),
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),
};

View File

@@ -734,7 +734,7 @@ impl CudaServerKey {
T::from(CudaRadixCiphertext::new(trimmed_ct_list, trimmed_ct_info))
}
pub(crate) fn generate_lookup_table<F>(&self, f: F) -> LookupTableOwned
pub fn generate_lookup_table<F>(&self, f: F) -> LookupTableOwned
where
F: Fn(u64) -> u64,
{
@@ -826,7 +826,7 @@ impl CudaServerKey {
///
/// - `streams` __must__ be synchronized to guarantee computation has finished, and inputs must
/// not be dropped until streams is synchronised
pub(crate) unsafe fn apply_lookup_table_async(
pub unsafe fn apply_lookup_table_async(
&self,
output: &mut CudaRadixCiphertext,
input: &CudaRadixCiphertext,
@@ -1005,7 +1005,7 @@ impl CudaServerKey {
///
/// - `stream` __must__ be synchronized to guarantee computation has finished, and inputs must
/// not be dropped until stream is synchronised
pub unsafe fn apply_many_lookup_table_async(
pub(crate) unsafe fn apply_many_lookup_table_async(
&self,
input: &CudaRadixCiphertext,
lut: &ManyLookupTableOwned,