chore: Integrate concrete-cpu/noise-model to the mono repo

This commit is contained in:
Quentin Bourgerie
2023-03-21 15:08:43 +01:00
14 changed files with 567 additions and 0 deletions

View File

@@ -0,0 +1,11 @@
[package]
# see https://doc.rust-lang.org/cargo/reference/manifest.html
name = "concrete-cpu-noise-model"
version = "0.1.0"
authors = [""]
edition = "2021"
[dependencies]
[dev-dependencies]
approx = "0.5"

View File

@@ -0,0 +1,2 @@
pub mod conversion;
pub mod noise;

View File

@@ -0,0 +1,15 @@
fn modular_variance_variance_ratio(ciphertext_modulus_log: u32) -> f64 {
2_f64.powi(2 * ciphertext_modulus_log as i32)
}
pub fn modular_variance_to_variance(modular_variance: f64, ciphertext_modulus_log: u32) -> f64 {
modular_variance / modular_variance_variance_ratio(ciphertext_modulus_log)
}
pub fn variance_to_modular_variance(variance: f64, ciphertext_modulus_log: u32) -> f64 {
variance * modular_variance_variance_ratio(ciphertext_modulus_log)
}
pub fn variance_to_std_dev(variance: f64) -> f64 {
variance.sqrt()
}

View File

@@ -0,0 +1,9 @@
pub mod blind_rotate;
pub mod cmux;
pub mod external_product_glwe;
pub mod keyswitch;
pub mod keyswitch_one_bit;
pub mod modulus_switching;
pub mod multi_bit_blind_rotate;
pub mod multi_bit_external_product_glwe;
pub mod private_packing_keyswitch;

View File

@@ -0,0 +1,101 @@
use super::cmux::variance_cmux;
pub const FFT_SCALING_WEIGHT: f64 = -2.577_224_94;
/// Final reduced noise generated by the final bootstrap step.
/// Note that it does not depends from input noise, assuming the bootstrap is successful
pub fn variance_blind_rotate(
in_lwe_dimension: u64,
out_glwe_dimension: u64,
out_polynomial_size: u64,
log2_base: u64,
level: u64,
ciphertext_modulus_log: u32,
variance_bsk: f64,
) -> f64 {
in_lwe_dimension as f64
* variance_cmux(
out_glwe_dimension,
out_polynomial_size,
log2_base,
level,
ciphertext_modulus_log,
variance_bsk,
)
}
#[cfg(test)]
mod tests {
use crate::gaussian_noise::{
conversion::variance_to_modular_variance, security::minimal_variance_glwe,
};
use super::*;
#[test]
fn security_variance_bootstrap_1() {
let ref_modular_variance = 4.078_296_369_990_673e31;
let polynomial_size = 1 << 12;
let glwe_dimension = 2;
let ciphertext_modulus_log = 64;
let security = 128;
let variance_bsk = minimal_variance_glwe(
glwe_dimension,
polynomial_size,
ciphertext_modulus_log,
security,
);
let actual = variance_blind_rotate(
2048,
glwe_dimension,
polynomial_size,
24,
2,
ciphertext_modulus_log,
variance_bsk,
);
approx::assert_relative_eq!(
variance_to_modular_variance(actual, ciphertext_modulus_log),
ref_modular_variance,
max_relative = 1e-8
);
}
#[test]
fn golden_python_prototype_security_variance_bootstrap_2() {
// golden value include fft correction
let golden_modular_variance = 3.269_722_907_894_341e55;
let polynomial_size = 1 << 12;
let glwe_dimension = 4;
let ciphertext_modulus_log = 128;
let security = 128;
let variance_bsk = minimal_variance_glwe(
glwe_dimension,
polynomial_size,
ciphertext_modulus_log,
security,
);
let actual = variance_blind_rotate(
1024,
glwe_dimension,
polynomial_size,
5,
9,
ciphertext_modulus_log,
variance_bsk,
);
approx::assert_relative_eq!(
variance_to_modular_variance(actual, ciphertext_modulus_log),
golden_modular_variance,
max_relative = 1e-8
);
}
}

View File

@@ -0,0 +1,20 @@
use super::external_product_glwe::variance_external_product_glwe;
// only valid in the blind rotate case
pub fn variance_cmux(
glwe_dimension: u64,
polynomial_size: u64,
log2_base: u64,
level: u64,
ciphertext_modulus_log: u32,
variance_ggsw: f64,
) -> f64 {
variance_external_product_glwe(
glwe_dimension,
polynomial_size,
log2_base,
level,
ciphertext_modulus_log,
variance_ggsw,
)
}

View File

@@ -0,0 +1,83 @@
use crate::{gaussian_noise::conversion::modular_variance_to_variance, utils::square};
pub fn variance_external_product_glwe(
glwe_dimension: u64,
polynomial_size: u64,
log2_base: u64,
level: u64,
ciphertext_modulus_log: u32,
variance_ggsw: f64,
) -> f64 {
theoretical_variance_external_product_glwe(
glwe_dimension,
polynomial_size,
log2_base,
level,
ciphertext_modulus_log,
variance_ggsw,
) + fft_noise_variance_external_product_glwe(
glwe_dimension,
polynomial_size,
log2_base,
level,
ciphertext_modulus_log,
)
}
fn theoretical_variance_external_product_glwe(
glwe_dimension: u64,
polynomial_size: u64,
log2_base: u64,
level: u64,
ciphertext_modulus_log: u32,
variance_ggsw: f64,
) -> f64 {
let variance_key_coefficient_binary: f64 =
modular_variance_to_variance(1. / 4., ciphertext_modulus_log);
let square_expectation_key_coefficient_binary: f64 =
modular_variance_to_variance(square(1. / 2.), ciphertext_modulus_log);
let k = glwe_dimension as f64;
let b = 2_f64.powi(log2_base as i32);
let b2l = 2_f64.powi((log2_base * 2 * level) as i32);
let l = level as f64;
let big_n = polynomial_size as f64;
let q_square = 2_f64.powi(2 * ciphertext_modulus_log as i32);
let res_1 = l * (k + 1.) * big_n * (square(b) + 2.) / 12. * variance_ggsw;
let res_2 = (q_square - b2l) / (24. * b2l)
* (modular_variance_to_variance(1., ciphertext_modulus_log)
+ k * big_n
* (variance_key_coefficient_binary + square_expectation_key_coefficient_binary))
+ k * big_n / 8. * variance_key_coefficient_binary
+ 1. / 16. * square(1. - k * big_n) * square_expectation_key_coefficient_binary;
res_1 + res_2
}
const FFT_SCALING_WEIGHT: f64 = -2.577_224_94;
/// Additional noise generated by fft computation
fn fft_noise_variance_external_product_glwe(
glwe_dimension: u64,
polynomial_size: u64,
log2_base: u64,
level: u64,
ciphertext_modulus_log: u32,
) -> f64 {
// https://github.com/zama-ai/concrete-optimizer/blob/prototype/python/optimizer/noise_formulas/bootstrap.py#L25
let b = 2_f64.powi(log2_base as i32);
let l = level as f64;
let big_n = polynomial_size as f64;
let k = glwe_dimension;
assert!(k > 0, "k = {k}");
assert!(k < 7, "k = {k}");
// 22 = 2 x 11, 11 = 64 -53
let scale_margin = (1_u64 << 22) as f64;
let res =
f64::exp2(FFT_SCALING_WEIGHT) * scale_margin * l * b * b * big_n.powi(2) * (k as f64 + 1.);
modular_variance_to_variance(res, ciphertext_modulus_log)
}

View File

@@ -0,0 +1,77 @@
use super::keyswitch_one_bit::variance_keyswitch_one_bit;
/// Additional noise generated by the keyswitch step.
pub fn variance_keyswitch(
input_lwe_dimension: u64, //n_big
log2_base: u64,
level: u64,
ciphertext_modulus_log: u32,
variance_ksk: f64,
) -> f64 {
input_lwe_dimension as f64
* variance_keyswitch_one_bit(log2_base, level, ciphertext_modulus_log, variance_ksk)
}
#[cfg(test)]
mod tests {
use crate::gaussian_noise::{
conversion::variance_to_modular_variance, security::minimal_variance_lwe,
};
use super::*;
#[test]
fn golden_python_prototype_security_variance_keyswitch_1() {
let golden_modular_variance = 5.997_880_135_602_194e68;
let internal_ks_output_lwe_dimension = 1024;
let ciphertext_modulus_log = 128;
let security = 128;
let actual = variance_keyswitch(
4096,
5,
9,
ciphertext_modulus_log,
minimal_variance_lwe(
internal_ks_output_lwe_dimension,
ciphertext_modulus_log,
security,
),
);
approx::assert_relative_eq!(
variance_to_modular_variance(actual, ciphertext_modulus_log),
golden_modular_variance,
max_relative = 1e-8
);
}
#[test]
fn golden_python_prototype_security_variance_keyswitch_2() {
// let golden_modular_variance = 8.580795457940938e+66;
// the full npe implements a part of the full estimation
let golden_modular_variance = 7.407_691_550_271_225e48; // full estimation
let internal_ks_output_lwe_dimension = 512;
let ciphertext_modulus_log = 64;
let security = 128;
let actual = variance_keyswitch(
2048,
24,
2,
ciphertext_modulus_log,
minimal_variance_lwe(
internal_ks_output_lwe_dimension,
ciphertext_modulus_log,
security,
),
);
approx::assert_relative_eq!(
variance_to_modular_variance(actual, ciphertext_modulus_log),
golden_modular_variance,
max_relative = 1e-8
);
}
}

View File

@@ -0,0 +1,31 @@
use crate::{gaussian_noise::conversion::modular_variance_to_variance, utils::square};
/// Additional noise generated by the bit multiplication
pub fn variance_keyswitch_one_bit(
log2_base: u64,
level: u64,
ciphertext_modulus_log: u32,
variance_ksk: f64,
) -> f64 {
let variance_key_coefficient_binary: f64 =
modular_variance_to_variance(1. / 4., ciphertext_modulus_log);
let square_expectation_key_coefficient_binary: f64 =
modular_variance_to_variance(square(1. / 2.), ciphertext_modulus_log);
let base = 2_f64.powi(log2_base as i32);
let b2l = 2_f64.powi((log2_base * 2 * level) as i32);
let q_square = 2_f64.powi((2 * ciphertext_modulus_log) as i32);
// res 2
let res_2 = (q_square / (12. * b2l) - 1. / 12.)
* (variance_key_coefficient_binary + square_expectation_key_coefficient_binary);
// res 3
let res_3 = 1. / 4. * variance_key_coefficient_binary;
// res 4
let res_4 = (level as f64) * variance_ksk * (square(base) + 2.) / 12.;
res_2 + res_3 + res_4
}

View File

@@ -0,0 +1,15 @@
use crate::{gaussian_noise::conversion::modular_variance_to_variance, utils::square};
pub fn estimate_modulus_switching_noise_with_binary_key(
internal_ks_output_lwe_dimension: u64,
glwe_log2_polynomial_size: u64,
ciphertext_modulus_log: u32,
) -> f64 {
let nb_msb = glwe_log2_polynomial_size + 1;
let w = 2_f64.powi(nb_msb as i32);
let n = internal_ks_output_lwe_dimension as f64;
(1. / 12. + n / 24.) / square(w)
+ modular_variance_to_variance(-1. / 12. + n / 48., ciphertext_modulus_log)
}

View File

@@ -0,0 +1,34 @@
use super::multi_bit_external_product_glwe::variance_multi_bit_external_product_glwe;
/// Final reduced noise generated by the final multi bit bootstrap step.
/// Note that it does not depends from input noise, assuming the bootstrap is successful
#[allow(clippy::too_many_arguments)]
pub fn variance_multi_bit_blind_rotate(
in_lwe_dimension: u64,
out_glwe_dimension: u64,
out_polynomial_size: u64,
log2_base: u64,
level: u64,
ciphertext_modulus_log: u32,
variance_bsk: f64,
grouping_factor: u32,
jit_fft: bool,
) -> f64 {
assert_eq!(
in_lwe_dimension % (grouping_factor as u64),
0,
"in_lwe_dimension ({in_lwe_dimension}) has \
to be a multiple of grouping_factor ({grouping_factor})"
);
(in_lwe_dimension / (grouping_factor as u64)) as f64
* variance_multi_bit_external_product_glwe(
out_glwe_dimension,
out_polynomial_size,
log2_base,
level,
ciphertext_modulus_log,
variance_bsk,
grouping_factor,
jit_fft,
)
}

View File

@@ -0,0 +1,108 @@
use crate::{gaussian_noise::conversion::modular_variance_to_variance, utils::square};
#[allow(clippy::too_many_arguments)]
pub fn variance_multi_bit_external_product_glwe(
glwe_dimension: u64,
polynomial_size: u64,
log2_base: u64,
level: u64,
ciphertext_modulus_log: u32,
variance_ggsw: f64,
grouping_factor: u32,
jit_fft: bool,
) -> f64 {
theoretical_variance_multi_bit_external_product_glwe(
glwe_dimension,
polynomial_size,
log2_base,
level,
ciphertext_modulus_log,
variance_ggsw,
grouping_factor,
) + fft_noise_variance_multi_bit_external_product_glwe(
glwe_dimension,
polynomial_size,
log2_base,
level,
ciphertext_modulus_log,
grouping_factor,
jit_fft,
)
}
fn theoretical_variance_multi_bit_external_product_glwe(
glwe_dimension: u64,
polynomial_size: u64,
log2_base: u64,
level: u64,
ciphertext_modulus_log: u32,
variance_ggsw: f64,
grouping_factor: u32,
) -> f64 {
let variance_key_coefficient_binary: f64 =
modular_variance_to_variance(1. / 4., ciphertext_modulus_log);
let square_expectation_key_coefficient_binary: f64 =
modular_variance_to_variance(square(1. / 2.), ciphertext_modulus_log);
let k = glwe_dimension as f64;
let b = 2_f64.powi(log2_base as i32);
let b2l = 2_f64.powi((log2_base * 2 * level) as i32);
let l = level as f64;
let big_n = polynomial_size as f64;
let q_square = 2_f64.powi(2 * ciphertext_modulus_log as i32);
let res_1 = l * (k + 1.) * big_n * (square(b) + 2.) / 12.
* variance_ggsw
* 2.0f64.powi(grouping_factor as i32);
let res_2 = (q_square - b2l) / (24. * b2l)
* (modular_variance_to_variance(1., ciphertext_modulus_log)
+ k * big_n
* (variance_key_coefficient_binary + square_expectation_key_coefficient_binary))
+ k * big_n / 8. * variance_key_coefficient_binary
+ 1. / 16. * square(1. - k * big_n) * square_expectation_key_coefficient_binary;
res_1 + res_2
}
const FFT_SCALING_WEIGHTS: [(u32, f64); 3] = [
(2, 0.265_753_885_551_084_5),
(3, 1.350_324_550_016_489_8),
(4, 2.475_036_769_207_096),
];
const JIT_FFT_SCALING_WEIGHT: f64 = -2.015_541_494_298_571_7;
/// Additional noise generated by fft computation
fn fft_noise_variance_multi_bit_external_product_glwe(
glwe_dimension: u64,
polynomial_size: u64,
log2_base: u64,
level: u64,
ciphertext_modulus_log: u32,
grouping_factor: u32,
jit_fft: bool,
) -> f64 {
let b = 2_f64.powi(log2_base as i32);
let l = level as f64;
let big_n = polynomial_size as f64;
let k = glwe_dimension;
assert!(k > 0, "k = {k}");
assert!(k < 7, "k = {k}");
let fft_scaling_weight = if jit_fft {
JIT_FFT_SCALING_WEIGHT
} else {
let index = FFT_SCALING_WEIGHTS
.binary_search_by_key(&grouping_factor, |&(factor, _)| factor)
.unwrap_or_else(|_| {
panic!("Could not find fft scaling weight for grouping factor {grouping_factor}.")
});
FFT_SCALING_WEIGHTS[index].1
};
// 22 = 2 x 11, 11 = 64 -53
let scale_margin = (1_u64 << 22) as f64;
let res =
f64::exp2(fft_scaling_weight) * scale_margin * l * b * b * big_n.powi(2) * (k as f64 + 1.);
modular_variance_to_variance(res, ciphertext_modulus_log)
}

View File

@@ -0,0 +1,38 @@
use crate::{gaussian_noise::conversion::modular_variance_to_variance, utils::square};
// packing private keyswitch for WoP-PBS, described in algorithm 3 of https://eprint.iacr.org/2018/421.pdf (TFHE paper)
pub fn estimate_packing_private_keyswitch(
var_glwe: f64,
var_ggsw: f64,
log2_base: u64,
level: u64,
output_glwe_dimension: u64,
output_polynomial_size: u64,
ciphertext_modulus_log: u32,
) -> f64 {
let variance_key_coefficient_binary: f64 = 1. / 4.;
let expectation_key_coefficient_binary: f64 = 1. / 2.;
let l = level as f64;
let b = 2f64.powi(log2_base as i32);
let n = (output_glwe_dimension * output_polynomial_size) as f64; // param.internal_lwe_dimension.0 as f64;
let b2l = f64::powi(b, 2 * level as i32);
let var_s_w = 1. / 4.;
let mean_s_w = 1. / 2.;
let res_1 = l * (n + 1.) * var_ggsw * (square(b) + 2.) / 12.;
#[allow(clippy::cast_possible_wrap)]
let res_3 = (f64::powi(2., 2 * ciphertext_modulus_log as i32) - b2l) / (12. * b2l)
* modular_variance_to_variance(
1. + n * variance_key_coefficient_binary + square(expectation_key_coefficient_binary),
ciphertext_modulus_log,
)
+ n / 4.
* modular_variance_to_variance(variance_key_coefficient_binary, ciphertext_modulus_log)
+ var_glwe * (var_s_w + square(mean_s_w));
let res_5 = modular_variance_to_variance(var_s_w, ciphertext_modulus_log) * 1. / 4.
* square(1. - n * expectation_key_coefficient_binary);
res_1 + res_3 + res_5
}

View File

@@ -0,0 +1,23 @@
#![warn(clippy::nursery)]
#![warn(clippy::pedantic)]
#![warn(clippy::style)]
#![allow(clippy::cast_lossless)]
#![allow(clippy::cast_precision_loss)] // u64 to f64
#![allow(clippy::cast_possible_truncation)] // u64 to usize
#![allow(clippy::missing_panics_doc)]
#![allow(clippy::module_name_repetitions)]
#![allow(clippy::must_use_candidate)]
#![allow(clippy::suboptimal_flops)]
#![allow(clippy::cast_possible_wrap)]
#![warn(unused_results)]
pub mod gaussian_noise;
pub(crate) mod utils {
pub fn square<V>(v: V) -> V
where
V: std::ops::Mul<Output = V> + Copy,
{
v * v
}
}