Compare commits

..

2 Commits

Author SHA1 Message Date
Mayeul@Zama
ffffce8079 feat(core): add common mask algorithms as experimental 2026-04-27 18:04:35 +02:00
Thomas Montaigu
80eb5d712a chore(ci): reduce oprf uniformity test to what it was 2026-04-27 16:15:39 +02:00
51 changed files with 9238 additions and 85 deletions

View File

@@ -1794,6 +1794,13 @@ bench_boolean: install_rs_check_toolchain
--bench boolean \
--features=boolean,internal-keycache -p tfhe-benchmark
.PHONY: bench_common_mask # Run benchmarks for CM-PBS
bench_common_mask: install_rs_check_toolchain
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_CHECK_TOOLCHAIN) bench \
--bench cm-bench \
--features=experimental -p tfhe-benchmark
.PHONY: bench_ks # Run benchmarks for keyswitch
bench_ks: install_rs_check_toolchain
RUSTFLAGS="$(RUSTFLAGS)" __TFHE_RS_PARAM_TYPE=$(BENCH_PARAM_TYPE) __TFHE_RS_PARAMS_SET=$(BENCH_PARAMS_SET) __TFHE_RS_BENCH_TYPE=$(BENCH_TYPE) \

View File

@@ -54,6 +54,7 @@ internal-keycache = ["tfhe/internal-keycache"]
avx512 = ["tfhe/avx512"]
pbs-stats = ["tfhe/pbs-stats"]
zk-pok = ["tfhe/zk-pok", "dep:tfhe-zk-pok"]
experimental = ["tfhe/experimental"]
[[bench]]
name = "boolean"
@@ -230,3 +231,9 @@ required-features = ["integer", "internal-keycache"]
name = "wasm_benchmarks_parser"
path = "src/bin/wasm_benchmarks_parser.rs"
required-features = ["shortint", "internal-keycache"]
[[bench]]
name = "cm-bench"
path = "benches/core_crypto/cm_bench.rs"
harness = false
required-features = ["experimental"]

View File

@@ -0,0 +1,211 @@
use cm_fft64::programmable_bootstrap_cm_lwe_ciphertext;
use criterion::{black_box, criterion_main, Criterion};
use tfhe::core_crypto::experimental::prelude::cm_lwe_keyswitch_key_generation::allocate_and_generate_new_cm_lwe_keyswitch_key;
use tfhe::core_crypto::experimental::prelude::cm_modulus_switch_noise_reduction::improve_lwe_ciphertext_modulus_switch_noise_for_binary_key_cm;
use tfhe::core_crypto::experimental::prelude::*;
use tfhe::core_crypto::prelude::*;
fn cm_bench(c: &mut Criterion) {
let bench_cm_params_2_minus_64: Vec<CmApParams> = vec![
CM_PARAM_2_2_MINUS_64,
CM_PARAM_4_2_MINUS_64,
CM_PARAM_6_2_MINUS_64,
CM_PARAM_8_2_MINUS_64,
CM_PARAM_10_2_MINUS_64,
CM_PARAM_2_4_MINUS_64,
CM_PARAM_4_4_MINUS_64,
CM_PARAM_6_4_MINUS_64,
CM_PARAM_8_4_MINUS_64,
CM_PARAM_10_4_MINUS_64,
CM_PARAM_2_6_MINUS_64,
CM_PARAM_4_6_MINUS_64,
CM_PARAM_6_6_MINUS_64,
CM_PARAM_8_6_MINUS_64,
CM_PARAM_10_6_MINUS_64,
CM_PARAM_2_8_MINUS_64,
CM_PARAM_4_8_MINUS_64,
CM_PARAM_6_8_MINUS_64,
CM_PARAM_8_8_MINUS_64,
CM_PARAM_10_8_MINUS_64,
];
cm_bench_for_pfail(c, &bench_cm_params_2_minus_64, "2^-64");
let bench_cm_params_2_minus_128: Vec<CmApParams> = vec![
CM_PARAM_2_2_MINUS_128,
CM_PARAM_4_2_MINUS_128,
CM_PARAM_6_2_MINUS_128,
CM_PARAM_8_2_MINUS_128,
CM_PARAM_10_2_MINUS_128,
CM_PARAM_2_4_MINUS_128,
CM_PARAM_4_4_MINUS_128,
CM_PARAM_6_4_MINUS_128,
CM_PARAM_8_4_MINUS_128,
CM_PARAM_10_4_MINUS_128,
CM_PARAM_2_6_MINUS_128,
CM_PARAM_4_6_MINUS_128,
CM_PARAM_6_6_MINUS_128,
CM_PARAM_8_6_MINUS_128,
CM_PARAM_10_6_MINUS_128,
CM_PARAM_2_8_MINUS_128,
CM_PARAM_4_8_MINUS_128,
CM_PARAM_6_8_MINUS_128,
CM_PARAM_8_8_MINUS_128,
CM_PARAM_10_8_MINUS_128,
];
cm_bench_for_pfail(c, &bench_cm_params_2_minus_128, "2^-128");
}
fn cm_bench_for_pfail(c: &mut Criterion, bench_cm_params: &[CmApParams], p_fail: &str) {
let mut bench_group = c.benchmark_group("Common Mask Benchmarks");
bench_group.sample_size(10);
// Create the PRNG
let mut seeder = new_seeder();
let seeder = seeder.as_mut();
let mut encryption_generator =
EncryptionRandomGenerator::<DefaultRandomGenerator>::new(seeder.seed(), seeder);
let mut secret_generator = SecretRandomGenerator::<DefaultRandomGenerator>::new(seeder.seed());
for cm_param in bench_cm_params {
let cm_dimension = cm_param.cm_dimension;
let ciphertext_modulus = cm_param.ciphertext_modulus;
let bench_name = format!(
"KS-CM-PBS_p={}_w={}_pfail={p_fail}",
cm_param.precision, cm_dimension.0,
);
let lwe_noise_distribution = cm_param.lwe_noise_distribution;
assert_eq!(
cm_param.ciphertext_modulus,
CiphertextModulus::<u64>::new_native()
);
let encoding_with_padding = 1 << 63;
let glwe_dimension = cm_param.glwe_dimension;
let polynomial_size = cm_param.polynomial_size;
let msg_modulus = 1u64 << cm_param.precision;
let delta = encoding_with_padding / msg_modulus;
let f = |x| x;
let accumulator = cm_generate_programmable_bootstrap_glwe_lut(
polynomial_size,
glwe_dimension,
cm_dimension,
msg_modulus.cast_into(),
cm_param.ciphertext_modulus,
delta,
f,
);
let CmBootstrapKeys {
small_lwe_sk,
big_lwe_sk,
bsk,
fbsk,
} = generate_cm_pbs_keys(cm_param, &mut encryption_generator, &mut secret_generator);
drop(bsk);
let cm_lwe_keyswitch_key = allocate_and_generate_new_cm_lwe_keyswitch_key(
&big_lwe_sk,
&small_lwe_sk,
cm_dimension,
cm_param.base_log_ks,
cm_param.level_ks,
lwe_noise_distribution,
ciphertext_modulus,
&mut encryption_generator,
);
let plaintexts = PlaintextList::from_container(vec![0_u64; cm_dimension.0]);
let ct_in = allocate_and_encrypt_new_cm_lwe_ciphertext(
&big_lwe_sk,
&plaintexts,
lwe_noise_distribution,
ciphertext_modulus,
&mut encryption_generator,
);
let mut ct_after_ks = CmLweCiphertext::new(
0u64,
cm_lwe_keyswitch_key.output_lwe_dimension(),
cm_dimension,
ciphertext_modulus,
);
let mut ct_out = CmLweCiphertext::new(
0u64,
fbsk.output_lwe_dimension(),
cm_dimension,
ciphertext_modulus,
);
let max_nb_zeros_n = cm_param.max_nb_zeros_n.ceil() as usize;
let mut encryptions_of_zero = CmLweCiphertextList::new(
0,
cm_param.lwe_dimension,
cm_dimension,
CmLweCiphertextCount(max_nb_zeros_n),
ciphertext_modulus,
);
let plaintext_list = PlaintextList::new(0, PlaintextCount(cm_dimension.0));
let plaintext_lists: Vec<_> = (0..max_nb_zeros_n)
.map(|_| plaintext_list.clone())
.collect();
encrypt_cm_lwe_ciphertext_list(
&small_lwe_sk,
&mut encryptions_of_zero,
&plaintext_lists,
lwe_noise_distribution,
&mut encryption_generator,
);
let log_modulus = polynomial_size.to_blind_rotation_input_modulus_log();
{
bench_group.bench_function(&bench_name, |b| {
b.iter(|| {
cm_keyswitch_lwe_ciphertext(&cm_lwe_keyswitch_key, &ct_in, &mut ct_after_ks);
improve_lwe_ciphertext_modulus_switch_noise_for_binary_key_cm(
&mut ct_after_ks,
&encryptions_of_zero,
cm_param.r_sigma_factor_n,
cm_param.ms_bound_n,
cm_param.ms_input_variance_n,
log_modulus,
);
programmable_bootstrap_cm_lwe_ciphertext(
&ct_after_ks,
&mut ct_out,
&accumulator.as_view(),
&fbsk,
);
black_box(&mut ct_out);
})
});
}
}
bench_group.finish();
}
pub fn cm_group() {
let mut criterion: Criterion<_> = (Criterion::default()).configure_from_args();
cm_bench(&mut criterion);
}
criterion_main!(cm_group);

View File

@@ -329,13 +329,14 @@ fn encrypt_constant_ggsw_level_matrix_row<Scalar, NoiseDistribution, KeyCont, Ou
OutputCont: ContainerMut<Element = Scalar>,
Gen: ByteRandomGenerator,
{
let mut body = row_as_glwe.get_mut_body();
if row_index < last_row_index {
// Not the last row
let sk_poly_list = glwe_secret_key.as_polynomial_list();
let sk_poly = sk_poly_list.get(row_index);
// Copy the key polynomial to the output body, to avoid allocating a temporary buffer
let mut body = row_as_glwe.get_mut_body();
body.as_mut().copy_from_slice(sk_poly.as_ref());
let ciphertext_modulus = body.ciphertext_modulus();
@@ -352,7 +353,6 @@ fn encrypt_constant_ggsw_level_matrix_row<Scalar, NoiseDistribution, KeyCont, Ou
}
} else {
// The last row needs a slightly different treatment
let mut body = row_as_glwe.get_mut_body();
let ciphertext_modulus = body.ciphertext_modulus();
body.as_mut().fill(Scalar::ZERO);

View File

@@ -90,6 +90,162 @@ impl<Scalar: UnsignedInteger, C: Container<Element = Scalar>> CreateFrom<C> for
Self::from_container(from, ciphertext_modulus)
}
}
pub type GlweBodyOwned<Scalar> = GlweBody<Vec<Scalar>>;
pub type GlweBodyView<'data, Scalar> = GlweBody<&'data [Scalar]>;
pub type GlweBodyMutView<'data, Scalar> = GlweBody<&'data mut [Scalar]>;
#[derive(Clone, Debug)]
pub struct GlweBodyList<C: Container>
where
C::Element: UnsignedInteger,
{
data: C,
polynomial_size: PolynomialSize,
ciphertext_modulus: CiphertextModulus<C::Element>,
}
pub type GlweBodyListView<'data, Scalar> = GlweBodyList<&'data [Scalar]>;
pub type GlweBodyListMutView<'data, Scalar> = GlweBodyList<&'data mut [Scalar]>;
impl<T: UnsignedInteger, C: Container<Element = T>> AsRef<[T]> for GlweBodyList<C> {
fn as_ref(&self) -> &[T] {
self.data.as_ref()
}
}
impl<T: UnsignedInteger, C: ContainerMut<Element = T>> AsMut<[T]> for GlweBodyList<C> {
fn as_mut(&mut self) -> &mut [T] {
self.data.as_mut()
}
}
impl<'data, T: UnsignedInteger> CreateFrom<&'data [T]> for GlweBodyListView<'data, T> {
type Metadata = GlweBodyListCreationMetadata<T>;
#[inline]
fn create_from(from: &[T], meta: Self::Metadata) -> GlweBodyListView<'_, T> {
let GlweBodyListCreationMetadata {
polynomial_size,
ciphertext_modulus,
} = meta;
GlweBodyList {
data: from,
polynomial_size,
ciphertext_modulus,
}
}
}
impl<'data, T: UnsignedInteger> CreateFrom<&'data mut [T]> for GlweBodyListMutView<'data, T> {
type Metadata = GlweBodyListCreationMetadata<T>;
#[inline]
fn create_from(from: &mut [T], meta: Self::Metadata) -> GlweBodyListMutView<'_, T> {
let GlweBodyListCreationMetadata {
polynomial_size,
ciphertext_modulus,
} = meta;
GlweBodyList {
data: from,
ciphertext_modulus,
polynomial_size,
}
}
}
impl<Scalar: UnsignedInteger, C: Container<Element = Scalar>> GlweBodyList<C> {
pub fn from_container(
container: C,
polynomial_size: PolynomialSize,
ciphertext_modulus: CiphertextModulus<Scalar>,
) -> Self {
assert!(container.container_len().is_multiple_of(polynomial_size.0));
Self {
data: container,
polynomial_size,
ciphertext_modulus,
}
}
pub fn into_container(self) -> C {
self.data
}
pub fn polynomial_size(&self) -> PolynomialSize {
self.polynomial_size
}
pub fn ciphertext_modulus(&self) -> CiphertextModulus<Scalar> {
self.ciphertext_modulus
}
}
impl<Scalar: UnsignedInteger, C: ContainerMut<Element = Scalar>> GlweBodyList<C> {
pub fn as_mut_polynomial_list(&mut self) -> PolynomialListMutView<'_, Scalar> {
let polynomial_size = self.polynomial_size();
PolynomialList::from_container(self.as_mut(), polynomial_size)
}
}
/// Metadata used in the [`CreateFrom`] implementation to create [`LweBodyList`] entities.
#[derive(Clone, Copy)]
pub struct GlweBodyListCreationMetadata<Scalar: UnsignedInteger> {
pub polynomial_size: PolynomialSize,
pub ciphertext_modulus: CiphertextModulus<Scalar>,
}
impl<Scalar: UnsignedInteger, C: Container<Element = Scalar>> ContiguousEntityContainer
for GlweBodyList<C>
{
type Element = C::Element;
type EntityViewMetadata = GlweCiphertextCreationMetadata<Self::Element>;
type EntityView<'this>
= GlweBodyView<'this, Self::Element>
where
Self: 'this;
type SelfViewMetadata = GlweBodyListCreationMetadata<Self::Element>;
type SelfView<'this>
= GlweBodyListView<'this, Self::Element>
where
Self: 'this;
fn get_entity_view_creation_metadata(&self) -> Self::EntityViewMetadata {
GlweCiphertextCreationMetadata {
polynomial_size: self.polynomial_size,
ciphertext_modulus: self.ciphertext_modulus(),
}
}
fn get_entity_view_pod_size(&self) -> usize {
self.polynomial_size.0
}
fn get_self_view_creation_metadata(&self) -> Self::SelfViewMetadata {
GlweBodyListCreationMetadata {
polynomial_size: self.polynomial_size,
ciphertext_modulus: self.ciphertext_modulus(),
}
}
}
impl<Scalar: UnsignedInteger, C: ContainerMut<Element = Scalar>> ContiguousEntityContainerMut
for GlweBodyList<C>
{
type EntityMutView<'this>
= GlweBodyMutView<'this, Self::Element>
where
Self: 'this;
type SelfMutView<'this>
= GlweBodyListMutView<'this, Self::Element>
where
Self: 'this;
}
/// A convenience structure to easily manipulate the mask of a [`GlweCiphertext`].
#[derive(Clone, Debug)]

View File

@@ -150,6 +150,10 @@ impl<Scalar: UnsignedInteger, C: Container<Element = Scalar>> LweBodyList<C> {
}
}
pub fn into_container(self) -> C {
self.data
}
pub fn lwe_body_count(&self) -> LweBodyCount {
LweBodyCount(self.data.container_len())
}
@@ -259,6 +263,10 @@ impl<Scalar: UnsignedInteger, C: Container<Element = Scalar>> LweMask<C> {
}
}
pub fn into_container(self) -> C {
self.data
}
/// Return the [`LweDimension`] of the [`LweMask`].
///
/// See [`LweMask::from_container`] for usage.

View File

@@ -0,0 +1,209 @@
use crate::core_crypto::algorithms::polynomial_algorithms::*;
use crate::core_crypto::commons::math::decomposition::SignedDecomposer;
use crate::core_crypto::commons::math::torus::UnsignedTorus;
use crate::core_crypto::commons::numeric::CastInto;
use crate::core_crypto::commons::parameters::{
DecompositionBaseLog, DecompositionLevelCount, GlweDimension, MonomialDegree, PolynomialSize,
};
use crate::core_crypto::commons::traits::{
ContiguousEntityContainer, ContiguousEntityContainerMut,
};
use crate::core_crypto::entities::*;
use crate::core_crypto::experimental::prelude::*;
use crate::core_crypto::fft_impl::common::modulus_switch;
use crate::core_crypto::fft_impl::fft64::math::fft::FftView;
use aligned_vec::CACHELINE_ALIGN;
use dyn_stack::{PodStack, StackReq};
use itertools::{izip, Itertools};
pub fn cm_blind_rotate_requirement<Scalar>(
glwe_dimension: GlweDimension,
cm_dimension: CmDimension,
polynomial_size: PolynomialSize,
fft: FftView<'_>,
) -> StackReq {
StackReq::any_of(&[
// tmp_poly allocation
StackReq::new_aligned::<Scalar>(polynomial_size.0, CACHELINE_ALIGN),
StackReq::all_of(&[
// ct1 allocation
StackReq::new_aligned::<Scalar>(
cm_glwe_ciphertext_size(glwe_dimension, cm_dimension, polynomial_size),
CACHELINE_ALIGN,
),
// external product
cm_add_external_product_assign_requirement::<Scalar>(
glwe_dimension,
cm_dimension,
polynomial_size,
fft,
),
]),
])
}
pub fn cm_bootstrap_requirement<Scalar>(
glwe_dimension: GlweDimension,
cm_dimension: CmDimension,
polynomial_size: PolynomialSize,
fft: FftView<'_>,
) -> StackReq {
cm_blind_rotate_requirement::<Scalar>(glwe_dimension, cm_dimension, polynomial_size, fft).and(
StackReq::new_aligned::<Scalar>(
cm_glwe_ciphertext_size(glwe_dimension, cm_dimension, polynomial_size),
CACHELINE_ALIGN,
),
)
}
pub fn cm_blind_rotate_assign_requirement<Scalar>(
glwe_dimension: GlweDimension,
cm_dimension: CmDimension,
polynomial_size: PolynomialSize,
fft: FftView<'_>,
) -> StackReq {
StackReq::all_of(&[
StackReq::new_aligned::<Scalar>(
cm_glwe_ciphertext_size(glwe_dimension, cm_dimension, polynomial_size),
CACHELINE_ALIGN,
),
cm_cmux_requirement::<Scalar>(glwe_dimension, cm_dimension, polynomial_size, fft),
])
}
// CastInto required for PBS modulus switch which returns a usize
pub fn cm_blind_rotate_assign<InputScalar, OutputScalar>(
cm_bsk: FourierCmLweBootstrapKeyView<'_>,
mut luts: CmGlweCiphertextMutView<'_, OutputScalar>,
lwe: CmLweCiphertextView<'_, InputScalar>,
fft: FftView<'_>,
stack: &mut PodStack,
) where
InputScalar: UnsignedTorus + CastInto<usize>,
OutputScalar: UnsignedTorus,
{
let mask = lwe.get_mask();
let bodies = lwe.get_bodies();
let lut_poly_size = luts.polynomial_size();
let ciphertext_modulus = luts.ciphertext_modulus();
assert!(ciphertext_modulus.is_compatible_with_native_modulus());
let log_modulus = lut_poly_size.to_blind_rotation_input_modulus_log();
luts.get_mut_bodies()
.as_mut_polynomial_list()
.iter_mut()
.zip_eq(bodies.iter())
.for_each(|(mut poly, body)| {
let monomial_degree =
MonomialDegree(modulus_switch((*body.data).cast_into(), log_modulus));
let (tmp_poly, _) = stack.make_aligned_raw(poly.as_ref().len(), CACHELINE_ALIGN);
let mut tmp_poly = Polynomial::from_container(tmp_poly);
tmp_poly.as_mut().copy_from_slice(poly.as_ref());
polynomial_wrapping_monic_monomial_div(&mut poly, &tmp_poly, monomial_degree);
});
// We initialize the ct_0 used for the successive cmuxes
let mut ct0 = luts;
let (ct1, stack) = stack.make_aligned_raw(ct0.as_ref().len(), CACHELINE_ALIGN);
let mut ct1 = CmGlweCiphertextMutView::from_container(
ct1,
cm_bsk.glwe_dimension(),
cm_bsk.cm_dimension(),
lut_poly_size,
ciphertext_modulus,
);
for (lwe_mask_element, bootstrap_key_ggsw) in
izip!(mask.as_ref().iter(), cm_bsk.into_cm_ggsw_iter())
{
if *lwe_mask_element != InputScalar::ZERO {
let monomial_degree =
MonomialDegree(modulus_switch((*lwe_mask_element).cast_into(), log_modulus));
// we effectively inline the body of cmux here, merging the initial subtraction
// operation with the monic polynomial multiplication, then performing the external
// product manually
// We rotate ct_1 and subtract ct_0 (first step of cmux) by performing
// ct_1 <- (ct_0 * X^{a_hat}) - ct_0
for (mut ct1_poly, ct0_poly) in izip!(
ct1.as_mut_polynomial_list().iter_mut(),
ct0.as_polynomial_list().iter(),
) {
polynomial_wrapping_monic_monomial_mul_and_subtract(
&mut ct1_poly,
&ct0_poly,
monomial_degree,
);
}
// as_mut_view is required to keep borrow rules consistent
// second step of cmux
cm_add_external_product_assign(
ct0.as_mut_view(),
bootstrap_key_ggsw,
ct1.as_view(),
fft,
stack,
);
}
}
if !ciphertext_modulus.is_native_modulus() {
// When we convert back from the fourier domain, integer values will contain up to 53
// MSBs with information. In our representation of power of 2 moduli < native modulus we
// fill the MSBs and leave the LSBs empty, this usage of the signed decomposer allows to
// round while keeping the data in the MSBs
let signed_decomposer = SignedDecomposer::new(
DecompositionBaseLog(ciphertext_modulus.get_custom_modulus().ilog2() as usize),
DecompositionLevelCount(1),
);
ct0.as_mut()
.iter_mut()
.for_each(|x| *x = signed_decomposer.closest_representable(*x));
}
}
pub fn cm_bootstrap<InputScalar, OutputScalar>(
cm_bsk: FourierCmLweBootstrapKeyView<'_>,
mut lwe_out: CmLweCiphertextMutView<'_, OutputScalar>,
lwe_in: CmLweCiphertextView<'_, InputScalar>,
accumulator: CmGlweCiphertextView<'_, OutputScalar>,
fft: FftView<'_>,
stack: &mut PodStack,
) where
// CastInto required for PBS modulus switch which returns a usize
InputScalar: UnsignedTorus + CastInto<usize>,
OutputScalar: UnsignedTorus,
{
assert!(lwe_in.ciphertext_modulus().is_power_of_two());
assert!(lwe_out.ciphertext_modulus().is_power_of_two());
assert_eq!(
lwe_out.ciphertext_modulus(),
accumulator.ciphertext_modulus()
);
let (local_accumulator_data, stack) =
stack.collect_aligned(CACHELINE_ALIGN, accumulator.as_ref().iter().copied());
let mut local_accumulator = CmGlweCiphertextMutView::from_container(
local_accumulator_data,
accumulator.glwe_dimension(),
accumulator.cm_dimension(),
accumulator.polynomial_size(),
accumulator.ciphertext_modulus(),
);
cm_blind_rotate_assign(
cm_bsk,
local_accumulator.as_mut_view(),
lwe_in.as_view(),
fft,
stack,
);
extract_lwe_sample_from_cm_glwe_ciphertext(&local_accumulator, &mut lwe_out, MonomialDegree(0));
}

View File

@@ -0,0 +1,249 @@
//! Module containing primitives pertaining to
//! [`CommonMask GGSW ciphertext encryption`](`CmGgswCiphertext`).
use crate::core_crypto::algorithms::slice_algorithms::*;
use crate::core_crypto::algorithms::*;
use crate::core_crypto::commons::ciphertext_modulus::CiphertextModulusKind;
use crate::core_crypto::commons::generators::EncryptionRandomGenerator;
use crate::core_crypto::commons::math::decomposition::DecompositionLevel;
use crate::core_crypto::commons::math::random::{Distribution, Uniform};
use crate::core_crypto::commons::traits::*;
use crate::core_crypto::experimental::entities::*;
use crate::core_crypto::experimental::prelude::encrypt_cm_glwe_ciphertext_assign;
use crate::core_crypto::prelude::{Cleartext, GlweSecretKey};
use itertools::Itertools;
use rayon::prelude::*;
pub fn encrypt_constant_cm_ggsw_ciphertext<Scalar, NoiseDistribution, KeyCont, OutputCont, Gen>(
glwe_secret_keys: &[GlweSecretKey<KeyCont>],
output: &mut CmGgswCiphertext<OutputCont>,
// TODO: Use a CleartextList
cleartexts: &[Cleartext<Scalar>],
noise_distribution: NoiseDistribution,
generator: &mut EncryptionRandomGenerator<Gen>,
) where
Scalar: Encryptable<Uniform, NoiseDistribution>,
NoiseDistribution: Distribution,
KeyCont: Container<Element = Scalar>,
OutputCont: ContainerMut<Element = Scalar>,
Gen: ByteRandomGenerator,
{
for glwe_secret_key in glwe_secret_keys {
assert!(
output.polynomial_size() == glwe_secret_key.polynomial_size(),
"Mismatch between polynomial sizes of output ciphertexts and input secret key. \
Got {:?} in output, and {:?} in secret key.",
output.polynomial_size(),
glwe_secret_key.polynomial_size()
);
assert!(
output.glwe_dimension() == glwe_secret_key.glwe_dimension(),
"Mismatch between GlweDimension of output ciphertexts and input secret key. \
Got {:?} in output, and {:?} in secret key.",
output.glwe_dimension(),
glwe_secret_key.glwe_dimension()
);
}
// Generators used to have same sequential and parallel key generation
let gen_iter = generator
.try_fork_from_config(output.encryption_fork_config(Uniform, noise_distribution))
.expect("Failed to split generator into ggsw levels");
let decomp_base_log = output.decomposition_base_log();
let decomp_level_count = output.decomposition_level_count();
let ciphertext_modulus = output.ciphertext_modulus();
for (level_index, (mut level_matrix, mut generator)) in
output.iter_mut().zip(gen_iter).enumerate()
{
let decomp_level = DecompositionLevel(decomp_level_count.0 - level_index);
let factors = cleartexts
.iter()
.map(|cleartext| {
ggsw_encryption_multiplicative_factor(
ciphertext_modulus,
decomp_level,
decomp_base_log,
*cleartext,
)
})
.collect_vec();
// We iterate over the rows of the level matrix, the last row needs special treatment
let gen_iter = generator
.try_fork_from_config(level_matrix.encryption_fork_config(Uniform, noise_distribution))
.expect("Failed to split generator into glwe");
let last_row_index = level_matrix.glwe_dimension().0;
for ((row_index, mut row_as_glwe), mut generator) in level_matrix
.as_mut_cm_glwe_list()
.iter_mut()
.enumerate()
.zip(gen_iter)
{
encrypt_constant_cm_ggsw_level_matrix_row(
glwe_secret_keys,
(row_index, last_row_index),
&factors,
&mut row_as_glwe,
noise_distribution,
&mut generator,
);
}
}
}
pub fn par_encrypt_constant_cm_ggsw_ciphertext<
Scalar,
NoiseDistribution,
KeyCont,
OutputCont,
Gen,
>(
glwe_secret_keys: &[GlweSecretKey<KeyCont>],
output: &mut CmGgswCiphertext<OutputCont>,
cleartexts: &[Cleartext<Scalar>],
noise_distribution: NoiseDistribution,
generator: &mut EncryptionRandomGenerator<Gen>,
) where
Scalar: Encryptable<Uniform, NoiseDistribution> + Sync + Send,
NoiseDistribution: Distribution + Sync,
KeyCont: Container<Element = Scalar> + Sync,
OutputCont: ContainerMut<Element = Scalar>,
Gen: ParallelByteRandomGenerator,
{
for glwe_secret_key in glwe_secret_keys {
assert!(
output.polynomial_size() == glwe_secret_key.polynomial_size(),
"Mismatch between polynomial sizes of output ciphertexts and input secret key. \
Got {:?} in output, and {:?} in secret key.",
output.polynomial_size(),
glwe_secret_key.polynomial_size()
);
assert!(
output.glwe_dimension() == glwe_secret_key.glwe_dimension(),
"Mismatch between GlweDimension of output ciphertexts and input secret key. \
Got {:?} in output, and {:?} in secret key.",
output.glwe_dimension(),
glwe_secret_key.glwe_dimension()
);
}
// Generators used to have same sequential and parallel key generation
let gen_iter = generator
.par_try_fork_from_config(output.encryption_fork_config(Uniform, noise_distribution))
.expect("Failed to split generator into ggsw levels");
let decomp_base_log = output.decomposition_base_log();
let decomp_level_count = output.decomposition_level_count();
let ciphertext_modulus = output.ciphertext_modulus();
output.par_iter_mut().zip(gen_iter).enumerate().for_each(
|(level_index, (mut level_matrix, mut generator))| {
let decomp_level = DecompositionLevel(decomp_level_count.0 - level_index);
let factors = cleartexts
.iter()
.map(|cleartext| {
ggsw_encryption_multiplicative_factor(
ciphertext_modulus,
decomp_level,
decomp_base_log,
*cleartext,
)
})
.collect_vec();
// We iterate over the rows of the level matrix, the last row needs special treatment
let gen_iter = generator
.par_try_fork_from_config(
level_matrix.encryption_fork_config(Uniform, noise_distribution),
)
.expect("Failed to split generator into glwe");
let last_row_index = level_matrix.glwe_dimension().0;
level_matrix
.as_mut_cm_glwe_list()
.par_iter_mut()
.enumerate()
.zip(gen_iter)
.for_each(|((row_index, mut row_as_glwe), mut generator)| {
encrypt_constant_cm_ggsw_level_matrix_row(
glwe_secret_keys,
(row_index, last_row_index),
&factors,
&mut row_as_glwe,
noise_distribution,
&mut generator,
);
});
},
);
}
fn encrypt_constant_cm_ggsw_level_matrix_row<Scalar, NoiseDistribution, KeyCont, OutputCont, Gen>(
glwe_secret_keys: &[GlweSecretKey<KeyCont>],
(row_index, first_body_row_index): (usize, usize),
factors: &[Scalar],
row_as_glwe: &mut CmGlweCiphertext<OutputCont>,
noise_distribution: NoiseDistribution,
generator: &mut EncryptionRandomGenerator<Gen>,
) where
Scalar: Encryptable<Uniform, NoiseDistribution>,
NoiseDistribution: Distribution,
KeyCont: Container<Element = Scalar>,
OutputCont: ContainerMut<Element = Scalar>,
Gen: ByteRandomGenerator,
{
assert!(matches!(
row_as_glwe.ciphertext_modulus().kind(),
CiphertextModulusKind::Native | CiphertextModulusKind::NonNativePowerOfTwo
));
let mut bodies = row_as_glwe.get_mut_bodies();
if row_index < first_body_row_index {
// Mask row
// The Matrix must encode polynomail list
// [factor1 * sk1[row_index], factor2 * sk2[row_index], ...]
for ((glwe_secret_key, mut body), factor) in glwe_secret_keys
.iter()
.zip_eq(bodies.iter_mut())
.zip_eq(factors.iter())
{
let sk_poly_list = glwe_secret_key.as_polynomial_list();
let sk_poly = sk_poly_list.get(row_index);
// Copy the key polynomial to the output body, to avoid allocating a temporary buffer
body.as_mut().copy_from_slice(sk_poly.as_ref());
slice_wrapping_scalar_mul_assign(body.as_mut(), *factor)
}
} else {
// Body rows
// The Matrix must encrypt polynomial list
// [0, ..., -factor_i * X^0, ..., 0]
// with i = body_row_index
bodies.as_mut().fill(Scalar::ZERO);
let body_row_index = row_index - first_body_row_index;
let encoded = factors[body_row_index].wrapping_neg();
let mut body = bodies.get_mut(body_row_index);
// set the constant coefficient (X^0)
body.as_mut()[0] = encoded;
}
encrypt_cm_glwe_ciphertext_assign(glwe_secret_keys, row_as_glwe, noise_distribution, generator);
}

View File

@@ -0,0 +1,324 @@
use crate::core_crypto::commons::math::decomposition::SignedDecomposer;
use crate::core_crypto::commons::math::torus::UnsignedTorus;
use crate::core_crypto::commons::parameters::{
DecompositionBaseLog, DecompositionLevelCount, GlweDimension, PolynomialSize,
};
use crate::core_crypto::commons::traits::Split;
use crate::core_crypto::experimental::prelude::*;
use crate::core_crypto::fft_impl::fft64::crypto::ggsw::{collect_next_term, update_with_fmadd};
use crate::core_crypto::fft_impl::fft64::math;
use crate::core_crypto::prelude::{ContiguousEntityContainer, ContiguousEntityContainerMut};
use aligned_vec::CACHELINE_ALIGN;
use dyn_stack::{PodStack, StackReq};
use itertools::izip;
use math::decomposition::TensorSignedDecompositionLendingIter;
use math::fft::FftView;
use math::polynomial::FourierPolynomialMutView;
use tfhe_fft::c64;
pub fn cm_add_external_product_assign_requirement<Scalar>(
glwe_dimension: GlweDimension,
cm_dimension: CmDimension,
polynomial_size: PolynomialSize,
fft: FftView<'_>,
) -> StackReq {
let align = CACHELINE_ALIGN;
let standard_scratch = StackReq::new_aligned::<Scalar>(
cm_glwe_ciphertext_size(glwe_dimension, cm_dimension, polynomial_size),
align,
);
let fourier_polynomial_size = polynomial_size.to_fourier_polynomial_size();
let fourier_scratch = StackReq::new_aligned::<c64>(
cm_glwe_ciphertext_fourier_size(glwe_dimension, cm_dimension, fourier_polynomial_size),
align,
);
let fourier_scratch_single = StackReq::new_aligned::<c64>(fourier_polynomial_size.0, align);
let substack3 = fft.forward_scratch();
let substack2 = substack3.and(fourier_scratch_single);
let substack1 = substack2.and(standard_scratch);
let substack0 = StackReq::any_of(&[substack1.and(standard_scratch), fft.backward_scratch()]);
substack0.and(fourier_scratch)
}
#[cfg_attr(feature = "__profiling", inline(never))]
pub fn cm_add_external_product_assign<Scalar>(
mut out: CmGlweCiphertextMutView<'_, Scalar>,
ggsw: FourierCmGgswCiphertextView<'_>,
glwe: CmGlweCiphertextView<Scalar>,
fft: FftView<'_>,
stack: &mut PodStack,
) where
Scalar: UnsignedTorus,
{
// we check that the polynomial sizes match
debug_assert_eq!(ggsw.polynomial_size(), glwe.polynomial_size());
debug_assert_eq!(ggsw.polynomial_size(), out.polynomial_size());
// we check that the glwe sizes match
debug_assert_eq!(ggsw.glwe_dimension(), glwe.glwe_dimension());
debug_assert_eq!(ggsw.glwe_dimension(), out.glwe_dimension());
let align = CACHELINE_ALIGN;
let fourier_poly_size = ggsw.polynomial_size().to_fourier_polynomial_size().0;
// we round the input mask and body
let decomposer = SignedDecomposer::<Scalar>::new(
ggsw.decomposition_base_log(),
ggsw.decomposition_level_count(),
);
let (output_fft_buffer, substack0) = stack.make_aligned_raw::<c64>(
fourier_poly_size * (ggsw.glwe_dimension().0 + ggsw.cm_dimension().0),
align,
);
// output_fft_buffer is initially uninitialized, considered to be implicitly zero, to avoid
// the cost of filling it up with zeros. `is_output_uninit` is set to `false` once
// it has been fully initialized for the first time.
let mut is_output_uninit = true;
{
// ------------------------------------------------------ EXTERNAL PRODUCT IN FOURIER DOMAIN
// In this section, we perform the external product in the fourier domain, and accumulate
// the result in the output_fft_buffer variable.
let (mut decomposition, substack1) = TensorSignedDecompositionLendingIter::new(
glwe.as_ref()
.iter()
.map(|s| decomposer.init_decomposer_state(*s)),
DecompositionBaseLog(decomposer.base_log),
DecompositionLevelCount(decomposer.level_count),
substack0,
);
// We loop through the levels (we reverse to match the order of the decomposition iterator.)
ggsw.into_levels().for_each(|ggsw_decomp_matrix| {
// We retrieve the decomposition of this level.
let (glwe_level, glwe_decomp_term, substack2) =
collect_next_term(&mut decomposition, substack1, align);
let glwe_decomp_term = CmGlweCiphertextView::from_container(
&*glwe_decomp_term,
ggsw.glwe_dimension(),
ggsw.cm_dimension(),
ggsw.polynomial_size(),
out.ciphertext_modulus(),
);
debug_assert_eq!(ggsw_decomp_matrix.decomposition_level(), glwe_level);
// For each level we have to add the result of the vector-matrix product between the
// decomposition of the glwe, and the ggsw level matrix to the output. To do so, we
// iteratively add to the output, the product between every line of the matrix, and
// the corresponding (scalar) polynomial in the glwe decomposition:
//
// ggsw_mat ggsw_mat
// glwe_dec | - - - - | < glwe_dec | - - - - |
// | - - - | x | - - - - | | - - - | x | - - - - | <
// ^ | - - - - | ^ | - - - - |
//
// t = 1 t = 2 ...
izip!(
ggsw_decomp_matrix.into_rows(),
glwe_decomp_term.as_polynomial_list().iter()
)
.for_each(|(ggsw_row, glwe_poly)| {
let (mut fourier, substack3) =
substack2.make_aligned_raw::<c64>(fourier_poly_size, align);
// We perform the forward fft transform for the glwe polynomial
let fourier = fft
.forward_as_integer(
FourierPolynomialMutView { data: &mut fourier },
glwe_poly,
substack3,
)
.data;
// Now we loop through the polynomials of the output, and add the
// corresponding product of polynomials.
update_with_fmadd(
output_fft_buffer,
ggsw_row.data(),
fourier,
is_output_uninit,
fourier_poly_size,
);
// we initialized `output_fft_buffer, so we can set this to false
is_output_uninit = false;
});
});
}
// -------------------------------------------- TRANSFORMATION OF RESULT TO STANDARD DOMAIN
// In this section, we bring the result from the fourier domain, back to the standard
// domain, and add it to the output.
//
// We iterate over the polynomials in the output.
if !is_output_uninit {
izip!(
out.as_mut_polynomial_list().iter_mut(),
output_fft_buffer
.into_chunks(fourier_poly_size)
.map(|slice| FourierPolynomialMutView { data: slice }),
)
.for_each(|(out, fourier)| {
// The fourier buffer is not re-used afterwards so we can use the in-place version of
// the add_backward_as_torus function
fft.add_backward_in_place_as_torus(out, fourier, substack0);
});
}
}
pub fn cm_cmux_requirement<Scalar>(
glwe_dimension: GlweDimension,
cm_dimension: CmDimension,
polynomial_size: PolynomialSize,
fft: FftView<'_>,
) -> StackReq {
cm_add_external_product_assign_requirement::<Scalar>(
glwe_dimension,
cm_dimension,
polynomial_size,
fft,
)
}
pub fn cm_cmux<Scalar: UnsignedTorus>(
ct0: CmGlweCiphertextMutView<'_, Scalar>,
mut ct1: CmGlweCiphertextMutView<'_, Scalar>,
ggsw: FourierCmGgswCiphertextView<'_>,
fft: FftView<'_>,
stack: &mut PodStack,
) {
izip!(ct1.as_mut(), ct0.as_ref()).for_each(|(c1, c0)| {
*c1 = c1.wrapping_sub(*c0);
});
cm_add_external_product_assign(ct0, ggsw, ct1.as_view(), fft, stack);
}
#[cfg(test)]
mod tests {
use dyn_stack::PodBuffer;
use itertools::Itertools;
use super::*;
use crate::core_crypto::prelude::*;
#[test]
fn test_cm_external_product() {
let glwe_dimension = GlweDimension(2);
let cm_dimension = CmDimension(2);
let polynomial_size = PolynomialSize(64);
let decomp_base_log = DecompositionBaseLog(8);
let decomp_level_count = DecompositionLevelCount(3);
let ciphertext_modulus = CiphertextModulus::new_native();
let noise_distribution =
DynamicDistribution::new_gaussian_from_std_dev(StandardDev(0.0000006791658447437413));
let fft = Fft::new(polynomial_size);
let fft = fft.as_view();
let mut mem = PodBuffer::new(StackReq::new_aligned::<u64>(100_000, 512));
let mut seeder = new_seeder();
let seeder = seeder.as_mut();
let mut encryption_generator =
EncryptionRandomGenerator::<DefaultRandomGenerator>::new(seeder.seed(), seeder);
let mut secret_generator =
SecretRandomGenerator::<DefaultRandomGenerator>::new(seeder.seed());
let glwe_secret_keys = (0..cm_dimension.0)
.map(|_| {
allocate_and_generate_new_binary_glwe_secret_key(
glwe_dimension,
polynomial_size,
&mut secret_generator,
)
})
.collect_vec();
let mut ggsw = CmGgswCiphertext::new(
0u64,
glwe_dimension,
cm_dimension,
polynomial_size,
decomp_base_log,
decomp_level_count,
ciphertext_modulus,
);
let cleartexts = (0..cm_dimension.0).map(|_| Cleartext(1)).collect_vec();
encrypt_constant_cm_ggsw_ciphertext(
&glwe_secret_keys,
&mut ggsw,
&cleartexts,
noise_distribution,
&mut encryption_generator,
);
let mut ggsw_fourier = FourierCmGgswCiphertext::new(
glwe_dimension,
cm_dimension,
polynomial_size,
decomp_base_log,
decomp_level_count,
);
let stack = PodStack::new(&mut mem);
ggsw_fourier
.as_mut_view()
.fill_with_forward_fourier(ggsw.as_view(), fft, stack);
let mut glwe_in = CmGlweCiphertext::new(
0u64,
glwe_dimension,
cm_dimension,
polynomial_size,
ciphertext_modulus,
);
let input_plaintext_list = PlaintextList::from_container(
(0..cm_dimension.0 * polynomial_size.0)
.map(|i| (i as u64 + 1) << 60)
.collect_vec(),
);
encrypt_cm_glwe_ciphertext(
&glwe_secret_keys,
&mut glwe_in,
&input_plaintext_list,
noise_distribution,
&mut encryption_generator,
);
let mut glwe_out = CmGlweCiphertext::new(
0u64,
glwe_dimension,
cm_dimension,
polynomial_size,
ciphertext_modulus,
);
let stack = PodStack::new(&mut mem);
super::cm_add_external_product_assign(
glwe_out.as_mut_view(),
ggsw_fourier.as_view(),
glwe_in.as_view(),
fft,
stack,
);
let mut decrypted =
PlaintextList::new(0, PlaintextCount(cm_dimension.0 * polynomial_size.0));
decrypt_cm_glwe_ciphertext(&glwe_secret_keys, &glwe_out, &mut decrypted);
for (i, j) in input_plaintext_list.iter().zip_eq(decrypted.iter()) {
let diff = j.0.wrapping_sub(*i.0) as i64;
assert!(diff.abs() < (1 << 57));
}
}
}

View File

@@ -0,0 +1,363 @@
//! Module containing primitives pertaining to
//! [`CommonMask GLWE ciphertext encryption`](`GlweCiphertext`).
use crate::core_crypto::algorithms::polynomial_algorithms::*;
use crate::core_crypto::algorithms::slice_algorithms::{
slice_wrapping_scalar_div_assign, slice_wrapping_scalar_mul_assign,
};
use crate::core_crypto::commons::ciphertext_modulus::CiphertextModulusKind;
use crate::core_crypto::commons::generators::EncryptionRandomGenerator;
use crate::core_crypto::commons::math::random::{Distribution, Uniform};
use crate::core_crypto::commons::parameters::*;
use crate::core_crypto::commons::traits::*;
use crate::core_crypto::entities::*;
use crate::core_crypto::experimental::entities::*;
use crate::core_crypto::experimental::prelude::CmDimension;
use itertools::Itertools;
pub fn fill_cm_glwe_mask_and_bodies_for_encryption_assign<
Scalar,
NoiseDistribution,
KeyCont,
BodyCont,
MaskCont,
Gen,
>(
glwe_secret_keys: &[GlweSecretKey<KeyCont>],
output_mask: &mut GlweMask<MaskCont>,
output_bodies: &mut GlweBodyList<BodyCont>,
noise_distribution: NoiseDistribution,
generator: &mut EncryptionRandomGenerator<Gen>,
) where
Scalar: Encryptable<Uniform, NoiseDistribution>,
NoiseDistribution: Distribution,
KeyCont: Container<Element = Scalar>,
BodyCont: ContainerMut<Element = Scalar>,
MaskCont: ContainerMut<Element = Scalar>,
Gen: ByteRandomGenerator,
{
assert_eq!(
output_mask.ciphertext_modulus(),
output_bodies.ciphertext_modulus(),
"Mismatched moduli between output_mask ({:?}) and output_body ({:?})",
output_mask.ciphertext_modulus(),
output_bodies.ciphertext_modulus()
);
let ciphertext_modulus = output_bodies.ciphertext_modulus();
assert!(ciphertext_modulus.is_compatible_with_native_modulus());
generator
.fill_slice_with_random_uniform_mask_custom_mod(output_mask.as_mut(), ciphertext_modulus);
generator.unsigned_integer_slice_wrapping_add_random_noise_from_distribution_custom_mod_assign(
output_bodies.as_mut(),
noise_distribution,
ciphertext_modulus,
);
if !ciphertext_modulus.is_native_modulus() {
let torus_scaling = ciphertext_modulus.get_power_of_two_scaling_to_native_torus();
slice_wrapping_scalar_mul_assign(output_mask.as_mut(), torus_scaling);
slice_wrapping_scalar_mul_assign(output_bodies.as_mut(), torus_scaling);
}
for (glwe_secret_key, mut output_body) in
glwe_secret_keys.iter().zip_eq(output_bodies.iter_mut())
{
polynomial_wrapping_add_multisum_assign(
&mut output_body.as_mut_polynomial(),
&output_mask.as_polynomial_list(),
&glwe_secret_key.as_polynomial_list(),
);
}
}
pub fn encrypt_cm_glwe_ciphertext_assign<Scalar, NoiseDistribution, KeyCont, OutputCont, Gen>(
glwe_secret_keys: &[GlweSecretKey<KeyCont>],
output: &mut CmGlweCiphertext<OutputCont>,
noise_distribution: NoiseDistribution,
generator: &mut EncryptionRandomGenerator<Gen>,
) where
Scalar: Encryptable<Uniform, NoiseDistribution>,
NoiseDistribution: Distribution,
KeyCont: Container<Element = Scalar>,
OutputCont: ContainerMut<Element = Scalar>,
Gen: ByteRandomGenerator,
{
for glwe_secret_key in glwe_secret_keys {
assert!(
output.glwe_dimension() == glwe_secret_key.glwe_dimension(),
"Mismatch between GlweDimension of output ciphertext and input secret key. \
Got {:?} in output, and {:?} in secret key.",
output.glwe_dimension(),
glwe_secret_key.glwe_dimension()
);
assert!(
output.polynomial_size() == glwe_secret_key.polynomial_size(),
"Mismatch between PolynomialSize of output ciphertext and input secret key. \
Got {:?} in output, and {:?} in secret key.",
output.polynomial_size(),
glwe_secret_key.polynomial_size()
);
}
let (mut mask, mut bodies) = output.get_mut_mask_and_bodies();
fill_cm_glwe_mask_and_bodies_for_encryption_assign(
glwe_secret_keys,
&mut mask,
&mut bodies,
noise_distribution,
generator,
);
}
pub fn fill_cm_glwe_mask_and_bodies_for_encryption<
Scalar,
NoiseDistribution,
KeyCont,
InputCont,
BodyCont,
MaskCont,
Gen,
>(
glwe_secret_keys: &[GlweSecretKey<KeyCont>],
output_mask: &mut GlweMask<MaskCont>,
output_bodies: &mut GlweBodyList<BodyCont>,
encoded: &PlaintextList<InputCont>,
noise_distribution: NoiseDistribution,
generator: &mut EncryptionRandomGenerator<Gen>,
) where
Scalar: Encryptable<Uniform, NoiseDistribution>,
NoiseDistribution: Distribution,
KeyCont: Container<Element = Scalar>,
InputCont: Container<Element = Scalar>,
BodyCont: ContainerMut<Element = Scalar>,
MaskCont: ContainerMut<Element = Scalar>,
Gen: ByteRandomGenerator,
{
assert_eq!(
output_mask.ciphertext_modulus(),
output_bodies.ciphertext_modulus()
);
let ciphertext_modulus = output_bodies.ciphertext_modulus();
let polynomial_size = output_bodies.polynomial_size();
assert!(ciphertext_modulus.is_compatible_with_native_modulus());
generator
.fill_slice_with_random_uniform_mask_custom_mod(output_mask.as_mut(), ciphertext_modulus);
generator.fill_slice_with_random_noise_from_distribution_custom_mod(
output_bodies.as_mut(),
noise_distribution,
ciphertext_modulus,
);
for ((glwe_secret_key, mut output_body), encoded) in glwe_secret_keys
.iter()
.zip_eq(output_bodies.iter_mut())
.zip_eq(encoded.chunks_exact(polynomial_size.0))
{
polynomial_wrapping_add_assign(
&mut output_body.as_mut_polynomial(),
&encoded.as_polynomial(),
);
if !ciphertext_modulus.is_native_modulus() {
let torus_scaling = ciphertext_modulus.get_power_of_two_scaling_to_native_torus();
slice_wrapping_scalar_mul_assign(output_mask.as_mut(), torus_scaling);
slice_wrapping_scalar_mul_assign(output_body.as_mut(), torus_scaling);
}
polynomial_wrapping_add_multisum_assign(
&mut output_body.as_mut_polynomial(),
&output_mask.as_polynomial_list(),
&glwe_secret_key.as_polynomial_list(),
);
}
}
pub fn encrypt_cm_glwe_ciphertext<Scalar, NoiseDistribution, KeyCont, InputCont, OutputCont, Gen>(
glwe_secret_keys: &[GlweSecretKey<KeyCont>],
output_glwe_ciphertext: &mut CmGlweCiphertext<OutputCont>,
input_plaintext_list: &PlaintextList<InputCont>,
noise_distribution: NoiseDistribution,
generator: &mut EncryptionRandomGenerator<Gen>,
) where
Scalar: Encryptable<Uniform, NoiseDistribution>,
NoiseDistribution: Distribution,
KeyCont: Container<Element = Scalar>,
InputCont: Container<Element = Scalar>,
OutputCont: ContainerMut<Element = Scalar>,
Gen: ByteRandomGenerator,
{
assert!(
output_glwe_ciphertext.polynomial_size().0 * output_glwe_ciphertext.cm_dimension().0
== input_plaintext_list.plaintext_count().0,
"Mismatch between PolynomialSize of output ciphertext PlaintextCount of input. \
Got {:?} in output, and {:?} in input.",
output_glwe_ciphertext.polynomial_size(),
input_plaintext_list.plaintext_count()
);
for glwe_secret_key in glwe_secret_keys {
assert!(
output_glwe_ciphertext.glwe_dimension() == glwe_secret_key.glwe_dimension(),
"Mismatch between GlweDimension of output ciphertext and input secret key. \
Got {:?} in output, and {:?} in secret key.",
output_glwe_ciphertext.glwe_dimension(),
glwe_secret_key.glwe_dimension()
);
assert!(
output_glwe_ciphertext.polynomial_size() == glwe_secret_key.polynomial_size(),
"Mismatch between PolynomialSize of output ciphertext and input secret key. \
Got {:?} in output, and {:?} in secret key.",
output_glwe_ciphertext.polynomial_size(),
glwe_secret_key.polynomial_size()
);
}
let (mut mask, mut bodies) = output_glwe_ciphertext.get_mut_mask_and_bodies();
fill_cm_glwe_mask_and_bodies_for_encryption(
glwe_secret_keys,
&mut mask,
&mut bodies,
input_plaintext_list,
noise_distribution,
generator,
);
}
pub fn decrypt_cm_glwe_ciphertext<Scalar, KeyCont, InputCont, OutputCont>(
glwe_secret_keys: &[GlweSecretKey<KeyCont>],
input_cm_glwe_ciphertext: &CmGlweCiphertext<InputCont>,
output_plaintext_list: &mut PlaintextList<OutputCont>,
) where
Scalar: UnsignedTorus,
KeyCont: Container<Element = Scalar>,
InputCont: Container<Element = Scalar>,
OutputCont: ContainerMut<Element = Scalar>,
{
assert!(
output_plaintext_list.plaintext_count().0
== input_cm_glwe_ciphertext.polynomial_size().0 * glwe_secret_keys.len(),
"Mismatched output PlaintextCount {:?} and input PolynomialSize {:?}",
output_plaintext_list.plaintext_count(),
input_cm_glwe_ciphertext.polynomial_size()
);
for glwe_secret_key in glwe_secret_keys {
assert!(
glwe_secret_key.glwe_dimension() == input_cm_glwe_ciphertext.glwe_dimension(),
"Mismatched GlweDimension between glwe_secret_key {:?} and input_glwe_ciphertext {:?}",
glwe_secret_key.glwe_dimension(),
input_cm_glwe_ciphertext.glwe_dimension()
);
assert!(
glwe_secret_key.polynomial_size() == input_cm_glwe_ciphertext.polynomial_size(),
"Mismatched PolynomialSize between glwe_secret_key {:?} and input_glwe_ciphertext {:?}",
glwe_secret_key.polynomial_size(),
input_cm_glwe_ciphertext.polynomial_size()
);
}
let polynomial_size = input_cm_glwe_ciphertext.polynomial_size();
let ciphertext_modulus = input_cm_glwe_ciphertext.ciphertext_modulus();
assert!(ciphertext_modulus.is_compatible_with_native_modulus());
let (mask, bodies) = input_cm_glwe_ciphertext.get_mask_and_bodies();
for ((glwe_secret_key, body), mut output_plaintext_list) in glwe_secret_keys
.iter()
.zip_eq(bodies.iter())
.zip_eq(output_plaintext_list.chunks_exact_mut(polynomial_size.0))
{
output_plaintext_list
.as_mut()
.copy_from_slice(body.as_ref());
polynomial_wrapping_sub_multisum_assign(
&mut output_plaintext_list.as_mut_polynomial(),
&mask.as_polynomial_list(),
&glwe_secret_key.as_polynomial_list(),
);
}
if !ciphertext_modulus.is_native_modulus() {
slice_wrapping_scalar_div_assign(
output_plaintext_list.as_mut(),
ciphertext_modulus.get_power_of_two_scaling_to_native_torus(),
);
}
}
pub fn trivially_encrypt_cm_glwe_ciphertext<Scalar, InputCont, OutputCont>(
output: &mut CmGlweCiphertext<OutputCont>,
encoded: &PlaintextList<InputCont>,
) where
Scalar: UnsignedTorus,
OutputCont: ContainerMut<Element = Scalar>,
InputCont: Container<Element = Scalar>,
{
assert!(
encoded.plaintext_count().0 == output.polynomial_size().0 * output.cm_dimension().0,
"Mismatched input PlaintextCount {:?} and output PolynomialSize * CmDimension {:?}",
encoded.plaintext_count(),
output.polynomial_size().0 * output.cm_dimension().0
);
let (mut mask, mut body) = output.get_mut_mask_and_bodies();
mask.as_mut().fill(Scalar::ZERO);
body.as_mut().copy_from_slice(encoded.as_ref());
let ciphertext_modulus = body.ciphertext_modulus();
match ciphertext_modulus.kind() {
CiphertextModulusKind::Native | CiphertextModulusKind::Other => (),
CiphertextModulusKind::NonNativePowerOfTwo => {
slice_wrapping_scalar_mul_assign(
body.as_mut(),
ciphertext_modulus.get_power_of_two_scaling_to_native_torus(),
);
}
}
}
pub fn allocate_and_trivially_encrypt_new_cm_glwe_ciphertext<Scalar, InputCont>(
glwe_dimension: GlweDimension,
cm_dimension: CmDimension,
polynomial_size: PolynomialSize,
encoded: &PlaintextList<InputCont>,
ciphertext_modulus: CiphertextModulus<Scalar>,
) -> CmGlweCiphertextOwned<Scalar>
where
Scalar: UnsignedTorus,
InputCont: Container<Element = Scalar>,
{
let mut new_ct = CmGlweCiphertextOwned::new(
Scalar::ZERO,
glwe_dimension,
cm_dimension,
polynomial_size,
ciphertext_modulus,
);
let mut bodies = new_ct.get_mut_bodies();
bodies.as_mut().copy_from_slice(encoded.as_ref());
// Manage the non native power of 2 encoding
if ciphertext_modulus.kind() == CiphertextModulusKind::NonNativePowerOfTwo {
slice_wrapping_scalar_mul_assign(
bodies.as_mut(),
ciphertext_modulus.get_power_of_two_scaling_to_native_torus(),
);
}
new_ct
}

View File

@@ -0,0 +1,217 @@
//! Module containing primitives pertaining to the operation usually referred to as a
//! _sample extract_ in the literature. Allowing to extract a single
//! [`CommonMask LWE Ciphertext`](`CmLweCiphertext`) from a given [`CommonMask GLWE
//! ciphertext`](`CmGlweCiphertext`).
use crate::core_crypto::algorithms::slice_algorithms::*;
use crate::core_crypto::commons::parameters::*;
use crate::core_crypto::commons::traits::*;
use crate::core_crypto::experimental::entities::*;
use itertools::Itertools;
use rayon::prelude::*;
pub fn extract_lwe_sample_from_cm_glwe_ciphertext<Scalar, InputCont, OutputCont>(
input_glwe: &CmGlweCiphertext<InputCont>,
output_lwe: &mut CmLweCiphertext<OutputCont>,
nth: MonomialDegree,
) where
Scalar: UnsignedInteger,
InputCont: Container<Element = Scalar>,
OutputCont: ContainerMut<Element = Scalar>,
{
let in_lwe_dim = input_glwe
.glwe_dimension()
.to_equivalent_lwe_dimension(input_glwe.polynomial_size());
let out_lwe_dim = output_lwe.lwe_dimension();
assert_eq!(
in_lwe_dim, out_lwe_dim,
"Mismatch between equivalent LweDimension of input ciphertext and output ciphertext. \
Got {in_lwe_dim:?} for input and {out_lwe_dim:?} for output.",
);
assert_eq!(
input_glwe.ciphertext_modulus(),
output_lwe.ciphertext_modulus(),
"Mismatched moduli between input_glwe ({:?}) and output_lwe ({:?})",
input_glwe.ciphertext_modulus(),
output_lwe.ciphertext_modulus()
);
// We retrieve the bodies and masks of the two ciphertexts.
let (mut lwe_mask, mut lwe_bodies) = output_lwe.get_mut_mask_and_bodies();
let (glwe_mask, glwe_bodies) = input_glwe.get_mask_and_bodies();
// We copy the body
for (lwe_body, glwe_body) in lwe_bodies.iter_mut().zip_eq(glwe_bodies.iter()) {
*lwe_body.data = glwe_body.as_ref()[nth.0];
}
// We copy the mask (each polynomial is in the wrong order)
lwe_mask.as_mut().copy_from_slice(glwe_mask.as_ref());
// We compute the number of elements which must be
// turned into their opposite
let opposite_count = input_glwe.polynomial_size().0 - nth.0 - 1;
let ciphertext_modulus = input_glwe.ciphertext_modulus();
if ciphertext_modulus.is_compatible_with_native_modulus() {
// We loop through the polynomials
for lwe_mask_poly in lwe_mask
.as_mut()
.chunks_exact_mut(input_glwe.polynomial_size().0)
{
// We reverse the polynomial
lwe_mask_poly.reverse();
// We compute the opposite of the proper coefficients
slice_wrapping_opposite_assign(&mut lwe_mask_poly[0..opposite_count]);
// We rotate the polynomial properly
lwe_mask_poly.rotate_left(opposite_count);
}
} else {
let modulus: Scalar = ciphertext_modulus.get_custom_modulus().cast_into();
// We loop through the polynomials
for lwe_mask_poly in lwe_mask
.as_mut()
.chunks_exact_mut(input_glwe.polynomial_size().0)
{
// We reverse the polynomial
lwe_mask_poly.reverse();
// We compute the opposite of the proper coefficients
slice_wrapping_opposite_assign_custom_mod(
&mut lwe_mask_poly[0..opposite_count],
modulus,
);
// We rotate the polynomial properly
lwe_mask_poly.rotate_left(opposite_count);
}
}
}
pub fn par_extract_lwe_sample_from_cm_glwe_ciphertext<Scalar, InputCont, OutputCont>(
input_glwe: &CmGlweCiphertext<InputCont>,
output_lwe_list: &mut CmLweCiphertextList<OutputCont>,
) where
Scalar: UnsignedInteger + Send + Sync,
InputCont: Container<Element = Scalar> + Sync,
OutputCont: ContainerMut<Element = Scalar>,
{
let thread_count = ThreadCount(rayon::current_num_threads());
par_extract_lwe_sample_from_cm_glwe_ciphertext_with_thread_count(
input_glwe,
output_lwe_list,
thread_count,
);
}
pub fn par_extract_lwe_sample_from_cm_glwe_ciphertext_with_thread_count<
Scalar,
InputCont,
OutputCont,
>(
input_glwe: &CmGlweCiphertext<InputCont>,
output_lwe_list: &mut CmLweCiphertextList<OutputCont>,
thread_count: ThreadCount,
) where
Scalar: UnsignedInteger + Send + Sync,
InputCont: Container<Element = Scalar> + Sync,
OutputCont: ContainerMut<Element = Scalar>,
{
let in_lwe_dim = input_glwe
.glwe_dimension()
.to_equivalent_lwe_dimension(input_glwe.polynomial_size());
let out_lwe_dim = output_lwe_list.lwe_dimension();
assert_eq!(
in_lwe_dim, out_lwe_dim,
"Mismatch between equivalent LweDimension of input ciphertext and output ciphertext. \
Got {in_lwe_dim:?} for input and {out_lwe_dim:?} for output.",
);
assert!(
input_glwe.polynomial_size().0 <= output_lwe_list.cm_lwe_ciphertext_count().0,
"The output LweCiphertextList does not have enough space ({:?}) \
to extract all input CmGlweCiphertext coefficients ({})",
output_lwe_list.cm_lwe_ciphertext_count(),
input_glwe.polynomial_size().0
);
assert_eq!(
input_glwe.ciphertext_modulus(),
output_lwe_list.ciphertext_modulus(),
"Mismatched moduli between input_glwe ({:?}) and output_lwe ({:?})",
input_glwe.ciphertext_modulus(),
output_lwe_list.ciphertext_modulus()
);
let polynomial_size = input_glwe.polynomial_size();
let (glwe_mask, glwe_body) = input_glwe.get_mask_and_bodies();
let thread_count = thread_count.0.min(rayon::current_num_threads());
let chunk_size = polynomial_size.0.div_ceil(thread_count);
glwe_body
.as_ref()
.par_chunks(chunk_size)
.zip(output_lwe_list.par_chunks_mut(chunk_size))
.enumerate()
.for_each(
|(chunk_idx, (glwe_body_chunk, mut output_lwe_list_chunk))| {
for (coeff_idx, (glwe_coeff, mut output_lwe)) in glwe_body_chunk
.iter()
.zip(output_lwe_list_chunk.iter_mut())
.enumerate()
{
let nth = chunk_idx * chunk_size + coeff_idx;
let (mut lwe_mask, mut lwe_body) = output_lwe.get_mut_mask_and_bodies();
// We copy the body
lwe_body.as_mut().fill(*glwe_coeff);
// We copy the mask (each polynomial is in the wrong order)
lwe_mask.as_mut().copy_from_slice(glwe_mask.as_ref());
// We compute the number of elements which must be
// turned into their opposite
let opposite_count = input_glwe.polynomial_size().0 - nth - 1;
let ciphertext_modulus = input_glwe.ciphertext_modulus();
if ciphertext_modulus.is_compatible_with_native_modulus() {
// We loop through the polynomials
for lwe_mask_poly in lwe_mask
.as_mut()
.chunks_exact_mut(input_glwe.polynomial_size().0)
{
// We reverse the polynomial
lwe_mask_poly.reverse();
// We compute the opposite of the proper coefficients
slice_wrapping_opposite_assign(&mut lwe_mask_poly[0..opposite_count]);
// We rotate the polynomial properly
lwe_mask_poly.rotate_left(opposite_count);
}
} else {
let modulus: Scalar = ciphertext_modulus.get_custom_modulus().cast_into();
// We loop through the polynomials
for lwe_mask_poly in lwe_mask
.as_mut()
.chunks_exact_mut(input_glwe.polynomial_size().0)
{
// We reverse the polynomial
lwe_mask_poly.reverse();
// We compute the opposite of the proper coefficients
slice_wrapping_opposite_assign_custom_mod(
&mut lwe_mask_poly[0..opposite_count],
modulus,
);
// We rotate the polynomial properly
lwe_mask_poly.rotate_left(opposite_count);
}
}
}
},
);
}

View File

@@ -0,0 +1,152 @@
//! Module containing primitives pertaining to the conversion of
//! [`CommonMask LWE bootstrap keys`](`CmLweBootstrapKey`) to various representations/numerical
//! domains like the Fourier domain.
use crate::core_crypto::commons::computation_buffers::ComputationBuffers;
use crate::core_crypto::commons::traits::*;
use crate::core_crypto::experimental::entities::*;
use crate::core_crypto::fft_impl::fft64::math::fft::{Fft, FftView};
use dyn_stack::{PodStack, StackReq};
use tfhe_fft::c64;
pub fn convert_standard_cm_lwe_bootstrap_key_to_fourier<Scalar, InputCont, OutputCont>(
input_bsk: &CmLweBootstrapKey<InputCont>,
output_bsk: &mut FourierCmLweBootstrapKey<OutputCont>,
) where
Scalar: UnsignedTorus,
InputCont: Container<Element = Scalar>,
OutputCont: ContainerMut<Element = c64>,
{
let mut buffers = ComputationBuffers::new();
let fft = Fft::new(input_bsk.polynomial_size());
let fft = fft.as_view();
buffers.resize(
convert_standard_cm_lwe_bootstrap_key_to_fourier_mem_optimized_requirement(fft)
.unaligned_bytes_required(),
);
let stack = buffers.stack();
convert_standard_cm_lwe_bootstrap_key_to_fourier_mem_optimized(
input_bsk, output_bsk, fft, stack,
);
}
pub fn convert_standard_cm_lwe_bootstrap_key_to_fourier_mem_optimized<
Scalar,
InputCont,
OutputCont,
>(
input_bsk: &CmLweBootstrapKey<InputCont>,
output_bsk: &mut FourierCmLweBootstrapKey<OutputCont>,
fft: FftView<'_>,
stack: &mut PodStack,
) where
Scalar: UnsignedTorus,
InputCont: Container<Element = Scalar>,
OutputCont: ContainerMut<Element = c64>,
{
assert_eq!(
input_bsk.polynomial_size(),
output_bsk.polynomial_size(),
"Mismatched PolynomialSize between input_bsk {:?} and output_bsk {:?}",
input_bsk.polynomial_size(),
output_bsk.polynomial_size(),
);
assert_eq!(
input_bsk.glwe_dimension(),
output_bsk.glwe_dimension(),
"Mismatched GlweSize"
);
assert_eq!(
input_bsk.decomposition_base_log(),
output_bsk.decomposition_base_log(),
"Mismatched DecompositionBaseLog between input_bsk {:?} and output_bsk {:?}",
input_bsk.decomposition_base_log(),
output_bsk.decomposition_base_log(),
);
assert_eq!(
input_bsk.decomposition_level_count(),
output_bsk.decomposition_level_count(),
"Mismatched DecompositionLevelCount between input_bsk {:?} and output_bsk {:?}",
input_bsk.decomposition_level_count(),
output_bsk.decomposition_level_count(),
);
assert_eq!(
input_bsk.input_lwe_dimension(),
output_bsk.input_lwe_dimension(),
"Mismatched input LweDimension between input_bsk {:?} and output_bsk {:?}",
input_bsk.input_lwe_dimension(),
output_bsk.input_lwe_dimension(),
);
output_bsk
.as_mut_view()
.fill_with_forward_fourier(input_bsk.as_view(), fft, stack);
}
pub fn par_convert_standard_cm_lwe_bootstrap_key_to_fourier<Scalar, InputCont, OutputCont>(
input_bsk: &CmLweBootstrapKey<InputCont>,
output_bsk: &mut FourierCmLweBootstrapKey<OutputCont>,
) where
Scalar: UnsignedTorus,
InputCont: Container<Element = Scalar>,
OutputCont: ContainerMut<Element = c64>,
{
assert_eq!(
input_bsk.polynomial_size(),
output_bsk.polynomial_size(),
"Mismatched PolynomialSize between input_bsk {:?} and output_bsk {:?}",
input_bsk.polynomial_size(),
output_bsk.polynomial_size(),
);
assert_eq!(
input_bsk.glwe_dimension(),
output_bsk.glwe_dimension(),
"Mismatched GlweSize"
);
assert_eq!(
input_bsk.decomposition_base_log(),
output_bsk.decomposition_base_log(),
"Mismatched DecompositionBaseLog between input_bsk {:?} and output_bsk {:?}",
input_bsk.decomposition_base_log(),
output_bsk.decomposition_base_log(),
);
assert_eq!(
input_bsk.decomposition_level_count(),
output_bsk.decomposition_level_count(),
"Mismatched DecompositionLevelCount between input_bsk {:?} and output_bsk {:?}",
input_bsk.decomposition_level_count(),
output_bsk.decomposition_level_count(),
);
assert_eq!(
input_bsk.input_lwe_dimension(),
output_bsk.input_lwe_dimension(),
"Mismatched input LweDimension between input_bsk {:?} and output_bsk {:?}",
input_bsk.input_lwe_dimension(),
output_bsk.input_lwe_dimension(),
);
let fft = Fft::new(input_bsk.polynomial_size());
let fft = fft.as_view();
output_bsk
.as_mut_view()
.par_fill_with_forward_fourier(input_bsk.as_view(), fft);
}
pub fn convert_standard_cm_lwe_bootstrap_key_to_fourier_mem_optimized_requirement(
fft: FftView<'_>,
) -> StackReq {
fft.forward_scratch()
}

View File

@@ -0,0 +1,170 @@
//! Module containing primitives pertaining to the generation of
//! [`standard CommonMask LWE bootstrap keys`](`CmLweBootstrapKey`).
use crate::core_crypto::algorithms::*;
use crate::core_crypto::commons::generators::EncryptionRandomGenerator;
use crate::core_crypto::commons::math::random::{Distribution, Uniform};
use crate::core_crypto::commons::traits::*;
use crate::core_crypto::experimental::prelude::*;
use crate::core_crypto::prelude::*;
use itertools::Itertools;
use rayon::prelude::*;
pub fn par_generate_cm_lwe_bootstrap_key<
Scalar,
NoiseDistribution,
InputKeyCont,
OutputKeyCont,
OutputCont,
Gen,
>(
input_lwe_secret_keys: &[LweSecretKey<InputKeyCont>],
output_glwe_secret_keys: &[GlweSecretKey<OutputKeyCont>],
output: &mut CmLweBootstrapKey<OutputCont>,
noise_distribution: NoiseDistribution,
generator: &mut EncryptionRandomGenerator<Gen>,
) where
Scalar: Encryptable<Uniform, NoiseDistribution> + Sync + Send,
NoiseDistribution: Distribution + Sync,
InputKeyCont: Container<Element = Scalar> + std::fmt::Debug,
OutputKeyCont: Container<Element = Scalar> + Sync,
OutputCont: ContainerMut<Element = Scalar>,
Gen: ParallelByteRandomGenerator,
{
for input_lwe_secret_key in input_lwe_secret_keys {
assert!(
output.input_lwe_dimension() == input_lwe_secret_key.lwe_dimension(),
"Mismatched LweDimension between input LWE secret key and LWE bootstrap key. \
Input LWE secret key LweDimension: {:?}, LWE bootstrap key input LweDimension {:?}.",
input_lwe_secret_key.lwe_dimension(),
output.input_lwe_dimension()
);
}
let key_len = input_lwe_secret_keys[0].as_view().into_container().len();
for output_glwe_secret_key in output_glwe_secret_keys {
assert!(
output.glwe_dimension() == output_glwe_secret_key.glwe_dimension(),
"Mismatched GlweSize between output GLWE secret key and LWE bootstrap key. \
Output GLWE secret key GlweSize: {:?}, LWE bootstrap key GlweSize {:?}.",
output_glwe_secret_key.glwe_dimension(),
output.glwe_dimension()
);
assert!(
output.polynomial_size() == output_glwe_secret_key.polynomial_size(),
"Mismatched PolynomialSize between output GLWE secret key and LWE bootstrap key. \
Output GLWE secret key PolynomialSize: {:?}, LWE bootstrap key PolynomialSize {:?}.",
output_glwe_secret_key.polynomial_size(),
output.polynomial_size()
);
}
let gen_iter = generator
.par_try_fork_from_config(output.encryption_fork_config(Uniform, noise_distribution))
.unwrap();
let transposed_keys = (0..key_len)
.map(|i| {
input_lwe_secret_keys
.iter()
.map(|key| Cleartext(key.as_view().into_container()[i]))
.collect_vec()
})
.collect_vec();
output
.par_iter_mut()
.zip(transposed_keys.par_iter())
.zip(gen_iter)
.for_each(|((mut ggsw, input_key_element), mut generator)| {
par_encrypt_constant_cm_ggsw_ciphertext(
output_glwe_secret_keys,
&mut ggsw,
input_key_element,
noise_distribution,
&mut generator,
);
});
}
#[derive(Clone, Debug, PartialEq)]
pub struct CmBootstrapKeys<Scalar: UnsignedInteger> {
pub small_lwe_sk: Vec<LweSecretKey<Vec<Scalar>>>,
pub big_lwe_sk: Vec<LweSecretKey<Vec<Scalar>>>,
pub bsk: CmLweBootstrapKeyOwned<Scalar>,
pub fbsk: FourierCmLweBootstrapKeyOwned,
}
pub fn generate_cm_pbs_keys(
params: &CmApParams,
encryption_random_generator: &mut EncryptionRandomGenerator<DefaultRandomGenerator>,
secret_random_generator: &mut SecretRandomGenerator<DefaultRandomGenerator>,
) -> CmBootstrapKeys<u64> {
let ciphertext_modulus = params.ciphertext_modulus;
let cm_dimension = params.cm_dimension;
let glwe_noise_distribution = params.glwe_noise_distribution;
// Create the LweSecretKey
let input_lwe_secret_keys = (0..cm_dimension.0)
.map(|_| {
allocate_and_generate_new_binary_lwe_secret_key(
params.lwe_dimension,
secret_random_generator,
)
})
.collect_vec();
let output_glwe_secret_keys = (0..cm_dimension.0)
.map(|_| {
allocate_and_generate_new_binary_glwe_secret_key(
params.glwe_dimension,
params.polynomial_size,
secret_random_generator,
)
})
.collect_vec();
let output_lwe_secret_keys = output_glwe_secret_keys
.iter()
.map(|a| a.clone().into_lwe_secret_key())
.collect_vec();
let mut bsk = CmLweBootstrapKey::new(
0,
params.glwe_dimension,
cm_dimension,
params.polynomial_size,
params.base_log_bs,
params.level_bs,
params.lwe_dimension,
ciphertext_modulus,
);
par_generate_cm_lwe_bootstrap_key(
&input_lwe_secret_keys,
&output_glwe_secret_keys,
&mut bsk,
glwe_noise_distribution,
encryption_random_generator,
);
let mut fbsk = FourierCmLweBootstrapKey::new(
params.lwe_dimension,
params.glwe_dimension,
cm_dimension,
params.polynomial_size,
params.base_log_bs,
params.level_bs,
);
par_convert_standard_cm_lwe_bootstrap_key_to_fourier(&bsk, &mut fbsk);
CmBootstrapKeys {
small_lwe_sk: input_lwe_secret_keys,
big_lwe_sk: output_lwe_secret_keys,
bsk,
fbsk,
}
}

View File

@@ -0,0 +1,202 @@
//! Module containing primitives pertaining to `CommonMask LWE ciphertext encryption and
//! decryption`.
use crate::core_crypto::algorithms::slice_algorithms::*;
use crate::core_crypto::commons::generators::EncryptionRandomGenerator;
use crate::core_crypto::commons::math::random::{Distribution, RandomGenerable};
use crate::core_crypto::commons::parameters::*;
use crate::core_crypto::commons::traits::*;
use crate::core_crypto::entities::*;
use crate::core_crypto::experimental::prelude::*;
use itertools::Itertools;
pub fn fill_cm_lwe_mask_and_bodies_for_encryption<
Scalar,
NoiseDistribution,
KeyCont,
EncodedCont,
OutputMaskCont,
OutputBodyCont,
Gen,
>(
lwe_secret_keys: &[LweSecretKey<KeyCont>],
output_mask: &mut LweMask<OutputMaskCont>,
output_bodies: &mut LweBodyList<OutputBodyCont>,
encoded: &PlaintextList<EncodedCont>,
noise_distribution: NoiseDistribution,
generator: &mut EncryptionRandomGenerator<Gen>,
) where
Scalar: UnsignedTorus + RandomGenerable<NoiseDistribution, CustomModulus = Scalar>,
NoiseDistribution: Distribution,
KeyCont: Container<Element = Scalar>,
EncodedCont: Container<Element = Scalar>,
OutputMaskCont: ContainerMut<Element = Scalar>,
OutputBodyCont: ContainerMut<Element = Scalar>,
Gen: ByteRandomGenerator,
{
assert_eq!(
output_mask.ciphertext_modulus(),
output_bodies.ciphertext_modulus(),
"Mismatched moduli between mask ({:?}) and body ({:?})",
output_mask.ciphertext_modulus(),
output_bodies.ciphertext_modulus()
);
let ciphertext_modulus = output_mask.ciphertext_modulus();
assert!(ciphertext_modulus.is_compatible_with_native_modulus());
// generate a randomly uniform mask
generator
.fill_slice_with_random_uniform_mask_custom_mod(output_mask.as_mut(), ciphertext_modulus);
for ((sk, body), encoded) in lwe_secret_keys
.iter()
.zip_eq(output_bodies.iter_mut())
.zip_eq(encoded.iter())
{
// generate an error from the given noise_distribution
let noise = generator
.random_noise_from_distribution_custom_mod(noise_distribution, ciphertext_modulus);
// compute the multisum between the secret key and the mask
let mask_key_dot_product = slice_wrapping_dot_product(output_mask.as_ref(), sk.as_ref());
// Store sum(ai * si) + delta * m + e in the body
*body.data = mask_key_dot_product
.wrapping_add(*encoded.0)
.wrapping_add(noise);
}
}
pub fn encrypt_cm_lwe_ciphertext<Scalar, NoiseDistribution, KeyCont, EncodedCont, OutputCont, Gen>(
lwe_secret_keys: &[LweSecretKey<KeyCont>],
output: &mut CmLweCiphertext<OutputCont>,
encoded: &PlaintextList<EncodedCont>,
noise_distribution: NoiseDistribution,
generator: &mut EncryptionRandomGenerator<Gen>,
) where
Scalar: UnsignedTorus + RandomGenerable<NoiseDistribution, CustomModulus = Scalar>,
NoiseDistribution: Distribution,
KeyCont: Container<Element = Scalar>,
EncodedCont: Container<Element = Scalar>,
OutputCont: ContainerMut<Element = Scalar>,
Gen: ByteRandomGenerator,
{
let (mut mask, mut body) = output.get_mut_mask_and_bodies();
fill_cm_lwe_mask_and_bodies_for_encryption(
lwe_secret_keys,
&mut mask,
&mut body,
encoded,
noise_distribution,
generator,
);
}
pub fn decrypt_cm_lwe_ciphertext<Scalar, KeyCont, InputCont>(
lwe_secret_keys: &[LweSecretKey<KeyCont>],
cm_lwe_ciphertext: &CmLweCiphertext<InputCont>,
) -> Vec<Plaintext<Scalar>>
where
Scalar: UnsignedInteger,
KeyCont: Container<Element = Scalar>,
InputCont: Container<Element = Scalar>,
{
for lwe_secret_key in lwe_secret_keys {
assert!(
cm_lwe_ciphertext.lwe_dimension() == lwe_secret_key.lwe_dimension(),
"Mismatch between LweDimension of output ciphertext and input secret key. \
Got {:?} in output, and {:?} in secret key.",
cm_lwe_ciphertext.lwe_dimension(),
lwe_secret_key.lwe_dimension()
);
}
let ciphertext_modulus = cm_lwe_ciphertext.ciphertext_modulus();
assert!(ciphertext_modulus.is_native_modulus());
let (mask, bodies) = cm_lwe_ciphertext.get_mask_and_bodies();
bodies
.iter()
.zip_eq(lwe_secret_keys.iter())
.map(|(body, lwe_secret_key)| {
let mask_key_dot_product =
slice_wrapping_dot_product(mask.as_ref(), lwe_secret_key.as_ref());
Plaintext(body.data.wrapping_sub(mask_key_dot_product))
})
.collect_vec()
}
pub fn allocate_and_encrypt_new_cm_lwe_ciphertext<
Scalar,
NoiseDistribution,
KeyCont,
EncodedCont,
Gen,
>(
lwe_secret_keys: &[LweSecretKey<KeyCont>],
encoded: &PlaintextList<EncodedCont>,
noise_distribution: NoiseDistribution,
ciphertext_modulus: CiphertextModulus<Scalar>,
generator: &mut EncryptionRandomGenerator<Gen>,
) -> CmLweCiphertextOwned<Scalar>
where
Scalar: UnsignedTorus + RandomGenerable<NoiseDistribution, CustomModulus = Scalar>,
NoiseDistribution: Distribution,
KeyCont: Container<Element = Scalar>,
EncodedCont: Container<Element = Scalar>,
Gen: ByteRandomGenerator,
{
let mut new_ct = CmLweCiphertextOwned::new(
Scalar::ZERO,
lwe_secret_keys[0].lwe_dimension(),
CmDimension(encoded.as_ref().len()),
ciphertext_modulus,
);
encrypt_cm_lwe_ciphertext(
lwe_secret_keys,
&mut new_ct,
encoded,
noise_distribution,
generator,
);
new_ct
}
pub fn encrypt_cm_lwe_ciphertext_list<
Scalar,
NoiseDistribution,
KeyCont,
OutputCont,
InputCont,
Gen,
>(
lwe_secret_keys: &[LweSecretKey<KeyCont>],
output: &mut CmLweCiphertextList<OutputCont>,
encoded: &[PlaintextList<InputCont>],
noise_distribution: NoiseDistribution,
generator: &mut EncryptionRandomGenerator<Gen>,
) where
Scalar: UnsignedTorus + RandomGenerable<NoiseDistribution, CustomModulus = Scalar>,
NoiseDistribution: Distribution,
KeyCont: Container<Element = Scalar>,
OutputCont: ContainerMut<Element = Scalar>,
InputCont: Container<Element = Scalar>,
Gen: ByteRandomGenerator,
{
for (mut output, encoded) in output.iter_mut().zip_eq(encoded.iter()) {
encrypt_cm_lwe_ciphertext(
lwe_secret_keys,
&mut output,
encoded,
noise_distribution,
generator,
);
}
}

View File

@@ -0,0 +1,281 @@
//! Module containing primitives pertaining to [`CommonMask LWE ciphertext
//! keyswitch`](`CmLweKeyswitchKey`).
use crate::core_crypto::algorithms::slice_algorithms::*;
use crate::core_crypto::commons::math::decomposition::SignedDecomposer;
use crate::core_crypto::commons::parameters::ThreadCount;
use crate::core_crypto::commons::traits::*;
use crate::core_crypto::experimental::prelude::*;
use itertools::Itertools;
use rayon::prelude::*;
pub fn cm_keyswitch_lwe_ciphertext<Scalar, KSKCont, InputCont, OutputCont>(
cm_lwe_keyswitch_key: &CmLweKeyswitchKey<KSKCont>,
input_lwe_ciphertext: &CmLweCiphertext<InputCont>,
output_lwe_ciphertext: &mut CmLweCiphertext<OutputCont>,
) where
Scalar: UnsignedInteger,
KSKCont: Container<Element = Scalar>,
InputCont: Container<Element = Scalar>,
OutputCont: ContainerMut<Element = Scalar>,
{
assert!(
cm_lwe_keyswitch_key.input_lwe_dimension() == input_lwe_ciphertext.lwe_dimension(),
"Mismatched input LweDimension. \
CmLweKeyswitchKey input LweDimension: {:?}, input CmLweCiphertext LweDimension {:?}.",
cm_lwe_keyswitch_key.input_lwe_dimension(),
input_lwe_ciphertext.lwe_dimension(),
);
assert!(
cm_lwe_keyswitch_key.output_lwe_dimension() == output_lwe_ciphertext.lwe_dimension(),
"Mismatched output LweDimension. \
CmLweKeyswitchKey output LweDimension: {:?}, output CmLweCiphertext LweDimension {:?}.",
cm_lwe_keyswitch_key.output_lwe_dimension(),
output_lwe_ciphertext.lwe_dimension(),
);
assert!(
cm_lwe_keyswitch_key.cm_dimension() == input_lwe_ciphertext.cm_dimension(),
"Mismatched CmDimension. \
CmLweKeyswitchKey CmDimension: {:?}, input CmLweCiphertext CmDimension {:?}.",
cm_lwe_keyswitch_key.cm_dimension(),
input_lwe_ciphertext.cm_dimension(),
);
assert!(
cm_lwe_keyswitch_key.cm_dimension() == output_lwe_ciphertext.cm_dimension(),
"Mismatched CmDimension. \
CmLweKeyswitchKey CmDimension: {:?}, output CmLweCiphertext CmDimension {:?}.",
cm_lwe_keyswitch_key.cm_dimension(),
output_lwe_ciphertext.cm_dimension(),
);
let output_ciphertext_modulus = output_lwe_ciphertext.ciphertext_modulus();
assert_eq!(
cm_lwe_keyswitch_key.ciphertext_modulus(),
output_ciphertext_modulus,
"Mismatched CiphertextModulus. \
CmLweKeyswitchKey CiphertextModulus: {:?}, output CmLweCiphertext CiphertextModulus {:?}.",
cm_lwe_keyswitch_key.ciphertext_modulus(),
output_ciphertext_modulus
);
assert!(
output_ciphertext_modulus.is_compatible_with_native_modulus(),
"This operation currently only supports power of 2 moduli"
);
let input_ciphertext_modulus = input_lwe_ciphertext.ciphertext_modulus();
assert!(
input_ciphertext_modulus.is_compatible_with_native_modulus(),
"This operation currently only supports power of 2 moduli"
);
// Clear the output ciphertext, as it will get updated gradually
output_lwe_ciphertext.as_mut().fill(Scalar::ZERO);
// Copy the input body to the output ciphertext
output_lwe_ciphertext
.get_mut_bodies()
.as_mut()
.copy_from_slice(input_lwe_ciphertext.get_bodies().as_ref());
// We instantiate a decomposer
let decomposer = SignedDecomposer::new(
cm_lwe_keyswitch_key.decomposition_base_log(),
cm_lwe_keyswitch_key.decomposition_level_count(),
);
for (keyswitch_key_block, &input_mask_element) in cm_lwe_keyswitch_key
.iter()
.zip(input_lwe_ciphertext.get_mask().as_ref())
{
let decomposition_iter = decomposer.decompose(input_mask_element);
// Loop over the levels
for (level_key_ciphertext, decomposed) in keyswitch_key_block.iter().zip(decomposition_iter)
{
slice_wrapping_sub_scalar_mul_assign(
output_lwe_ciphertext.as_mut(),
level_key_ciphertext.as_ref(),
decomposed.value(),
);
}
}
}
pub fn par_cm_keyswitch_lwe_ciphertext<Scalar, KSKCont, InputCont, OutputCont>(
cm_lwe_keyswitch_key: &CmLweKeyswitchKey<KSKCont>,
input_lwe_ciphertext: &CmLweCiphertext<InputCont>,
output_lwe_ciphertext: &mut CmLweCiphertext<OutputCont>,
) where
Scalar: UnsignedInteger + Send + Sync,
KSKCont: Container<Element = Scalar>,
InputCont: Container<Element = Scalar>,
OutputCont: ContainerMut<Element = Scalar>,
{
let thread_count = ThreadCount(rayon::current_num_threads());
par_cm_keyswitch_lwe_ciphertext_with_thread_count(
cm_lwe_keyswitch_key,
input_lwe_ciphertext,
output_lwe_ciphertext,
thread_count,
);
}
pub fn par_cm_keyswitch_lwe_ciphertext_with_thread_count<Scalar, KSKCont, InputCont, OutputCont>(
cm_lwe_keyswitch_key: &CmLweKeyswitchKey<KSKCont>,
input_lwe_ciphertext: &CmLweCiphertext<InputCont>,
output_lwe_ciphertext: &mut CmLweCiphertext<OutputCont>,
thread_count: ThreadCount,
) where
Scalar: UnsignedInteger + Send + Sync,
KSKCont: Container<Element = Scalar>,
InputCont: Container<Element = Scalar>,
OutputCont: ContainerMut<Element = Scalar>,
{
assert!(
cm_lwe_keyswitch_key.input_lwe_dimension() == input_lwe_ciphertext.lwe_dimension(),
"Mismatched input LweDimension. \
CmLweKeyswitchKey input LweDimension: {:?}, input CmLweCiphertext LweDimension {:?}.",
cm_lwe_keyswitch_key.input_lwe_dimension(),
input_lwe_ciphertext.lwe_dimension(),
);
assert!(
cm_lwe_keyswitch_key.output_lwe_dimension() == output_lwe_ciphertext.lwe_dimension(),
"Mismatched output LweDimension. \
CmLweKeyswitchKey output LweDimension: {:?}, output CmLweCiphertext LweDimension {:?}.",
cm_lwe_keyswitch_key.output_lwe_dimension(),
output_lwe_ciphertext.lwe_dimension(),
);
assert!(
cm_lwe_keyswitch_key.cm_dimension() == input_lwe_ciphertext.cm_dimension(),
"Mismatched CmDimension. \
CmLweKeyswitchKey CmDimension: {:?}, input CmLweCiphertext CmDimension {:?}.",
cm_lwe_keyswitch_key.cm_dimension(),
input_lwe_ciphertext.cm_dimension(),
);
assert!(
cm_lwe_keyswitch_key.cm_dimension() == output_lwe_ciphertext.cm_dimension(),
"Mismatched CmDimension. \
CmLweKeyswitchKey CmDimension: {:?}, output CmLweCiphertext CmDimension {:?}.",
cm_lwe_keyswitch_key.cm_dimension(),
output_lwe_ciphertext.cm_dimension(),
);
let output_ciphertext_modulus = output_lwe_ciphertext.ciphertext_modulus();
assert_eq!(
cm_lwe_keyswitch_key.ciphertext_modulus(),
output_ciphertext_modulus,
"Mismatched CiphertextModulus. \
CmLweKeyswitchKey CiphertextModulus: {:?}, output CmLweCiphertext CiphertextModulus {:?}.",
cm_lwe_keyswitch_key.ciphertext_modulus(),
output_ciphertext_modulus
);
assert!(
output_ciphertext_modulus.is_compatible_with_native_modulus(),
"This operation currently only supports power of 2 moduli"
);
let input_ciphertext_modulus = input_lwe_ciphertext.ciphertext_modulus();
assert!(
input_ciphertext_modulus.is_compatible_with_native_modulus(),
"This operation currently only supports power of 2 moduli"
);
assert!(
thread_count.0 != 0,
"Got thread_count == 0, this is not supported"
);
// Clear the output ciphertext, as it will get updated gradually
output_lwe_ciphertext.as_mut().fill(Scalar::ZERO);
let output_lwe_dimension = output_lwe_ciphertext.lwe_dimension();
let cm_dimension = output_lwe_ciphertext.cm_dimension();
// We instantiate a decomposer
let decomposer = SignedDecomposer::new(
cm_lwe_keyswitch_key.decomposition_base_log(),
cm_lwe_keyswitch_key.decomposition_level_count(),
);
// Don't go above the current number of threads
let thread_count = thread_count.0.min(rayon::current_num_threads());
let mut intermediate_accumulators = Vec::with_capacity(thread_count);
// Smallest chunk_size such that thread_count * chunk_size >= input_lwe_dimension
let chunk_size = input_lwe_ciphertext
.lwe_dimension()
.0
.div_ceil(thread_count);
cm_lwe_keyswitch_key
.par_chunks(chunk_size)
.zip(
input_lwe_ciphertext
.get_mask()
.as_ref()
.par_chunks(chunk_size),
)
.map(|(keyswitch_key_block_chunk, input_mask_element_chunk)| {
let mut buffer = CmLweCiphertext::new(
Scalar::ZERO,
output_lwe_dimension,
cm_dimension,
output_ciphertext_modulus,
);
for (keyswitch_key_block, &input_mask_element) in keyswitch_key_block_chunk
.iter()
.zip(input_mask_element_chunk.iter())
{
let decomposition_iter = decomposer.decompose(input_mask_element);
// Loop over the levels
for (level_key_ciphertext, decomposed) in
keyswitch_key_block.iter().zip(decomposition_iter)
{
slice_wrapping_sub_scalar_mul_assign(
buffer.as_mut(),
level_key_ciphertext.as_ref(),
decomposed.value(),
);
}
}
buffer
})
.collect_into_vec(&mut intermediate_accumulators);
let reduced = intermediate_accumulators
.par_iter_mut()
.reduce_with(|lhs, rhs| {
lhs.as_mut()
.iter_mut()
.zip(rhs.as_ref().iter())
.for_each(|(dst, &src)| *dst = (*dst).wrapping_add(src));
lhs
})
.unwrap();
let reduced = reduced.as_view();
output_lwe_ciphertext
.get_mut_mask()
.as_mut()
.copy_from_slice(reduced.get_mask().as_ref());
let reduced_ksed_bodies = reduced.get_bodies();
// Add the reduced body of the keyswitch to the output body to complete the keyswitch
for ((out, in1), in2) in output_lwe_ciphertext
.get_mut_bodies()
.as_mut()
.iter_mut()
.zip_eq(input_lwe_ciphertext.get_bodies().as_ref())
.zip_eq(reduced_ksed_bodies.as_ref())
{
*out = in1.wrapping_add(*in2);
}
}

View File

@@ -0,0 +1,144 @@
//! Module containing primitives pertaining to [`CommonMask LWE keyswitch keys
//! generation`](`CmLweKeyswitchKey`)
use itertools::Itertools;
use crate::core_crypto::commons::generators::EncryptionRandomGenerator;
use crate::core_crypto::commons::math::decomposition::{DecompositionLevel, DecompositionTerm};
use crate::core_crypto::commons::math::random::{Distribution, Uniform};
use crate::core_crypto::commons::parameters::*;
use crate::core_crypto::commons::traits::*;
use crate::core_crypto::entities::*;
use crate::core_crypto::experimental::algorithms::*;
use crate::core_crypto::experimental::prelude::*;
pub fn generate_cm_lwe_keyswitch_key<
Scalar,
NoiseDistribution,
InputKeyCont,
OutputKeyCont,
KSKeyCont,
Gen,
>(
input_cm_lwe_sks: &[LweSecretKey<InputKeyCont>],
output_cm_lwe_sks: &[LweSecretKey<OutputKeyCont>],
cm_lwe_keyswitch_key: &mut CmLweKeyswitchKey<KSKeyCont>,
noise_distribution: NoiseDistribution,
generator: &mut EncryptionRandomGenerator<Gen>,
) where
Scalar: Encryptable<Uniform, NoiseDistribution> + UnsignedTorus,
NoiseDistribution: Distribution,
InputKeyCont: Container<Element = Scalar>,
OutputKeyCont: Container<Element = Scalar>,
KSKeyCont: ContainerMut<Element = Scalar>,
Gen: ByteRandomGenerator,
{
for input_cm_lwe_sk in input_cm_lwe_sks {
assert!(
cm_lwe_keyswitch_key.input_lwe_dimension() == input_cm_lwe_sk.lwe_dimension(),
"The destination CmLweKeyswitchKey input LweDimension is not equal \
to the input LweSecretKey LweDimension. Destination: {:?}, input: {:?}",
cm_lwe_keyswitch_key.input_lwe_dimension(),
input_cm_lwe_sk.lwe_dimension()
);
}
for output_cm_lwe_sk in output_cm_lwe_sks {
assert!(
cm_lwe_keyswitch_key.output_lwe_dimension() == output_cm_lwe_sk.lwe_dimension(),
"The destination CmLweKeyswitchKey output LweDimension is not equal \
to the output LweSecretKey LweDimension. Destination: {:?}, output: {:?}",
cm_lwe_keyswitch_key.output_lwe_dimension(),
output_cm_lwe_sk.lwe_dimension()
);
}
let decomp_base_log = cm_lwe_keyswitch_key.decomposition_base_log();
let decomp_level_count = cm_lwe_keyswitch_key.decomposition_level_count();
let cm_dimension = cm_lwe_keyswitch_key.cm_dimension();
let ciphertext_modulus = cm_lwe_keyswitch_key.ciphertext_modulus();
assert!(ciphertext_modulus.is_compatible_with_native_modulus());
// The plaintexts used to encrypt a key element will be stored in this buffer
let mut decomposition_plaintexts_buffers = (0..decomp_level_count.0)
.map(|_| PlaintextListOwned::new(Scalar::ZERO, PlaintextCount(cm_dimension.0)))
.collect_vec();
// Iterate over the input key elements and the destination lwe_keyswitch_key memory
for (input_key_bit_index, mut keyswitch_key_block) in
cm_lwe_keyswitch_key.iter_mut().enumerate()
{
for (pt_index, input_cm_lwe_sk) in input_cm_lwe_sks.iter().enumerate() {
// We fill the buffer with the powers of the key elements
for (level, message) in (1..=decomp_level_count.0)
.rev()
.map(DecompositionLevel)
.zip_eq(decomposition_plaintexts_buffers.iter_mut())
{
// Here we take the decomposition term from the native torus, bring it to the torus
// we are working with by dividing by the scaling factor and the
// encryption will take care of mapping that back to the native
// torus
message.as_mut()[pt_index] = DecompositionTerm::new(
level,
decomp_base_log,
input_cm_lwe_sk.as_ref()[input_key_bit_index],
)
.to_recomposition_summand()
.wrapping_div(ciphertext_modulus.get_power_of_two_scaling_to_native_torus());
}
}
encrypt_cm_lwe_ciphertext_list(
output_cm_lwe_sks,
&mut keyswitch_key_block,
&decomposition_plaintexts_buffers,
noise_distribution,
generator,
);
}
}
#[allow(clippy::too_many_arguments)]
pub fn allocate_and_generate_new_cm_lwe_keyswitch_key<
Scalar,
NoiseDistribution,
InputKeyCont,
OutputKeyCont,
Gen,
>(
input_cm_lwe_sks: &[LweSecretKey<InputKeyCont>],
output_cm_lwe_sks: &[LweSecretKey<OutputKeyCont>],
cm_dimension: CmDimension,
decomp_base_log: DecompositionBaseLog,
decomp_level_count: DecompositionLevelCount,
noise_distribution: NoiseDistribution,
ciphertext_modulus: CiphertextModulus<Scalar>,
generator: &mut EncryptionRandomGenerator<Gen>,
) -> CmLweKeyswitchKeyOwned<Scalar>
where
Scalar: Encryptable<Uniform, NoiseDistribution> + UnsignedTorus,
NoiseDistribution: Distribution,
InputKeyCont: Container<Element = Scalar>,
OutputKeyCont: Container<Element = Scalar>,
Gen: ByteRandomGenerator,
{
let mut new_cm_lwe_keyswitch_key = CmLweKeyswitchKeyOwned::new(
Scalar::ZERO,
decomp_base_log,
decomp_level_count,
input_cm_lwe_sks[0].lwe_dimension(),
output_cm_lwe_sks[0].lwe_dimension(),
cm_dimension,
ciphertext_modulus,
);
generate_cm_lwe_keyswitch_key(
input_cm_lwe_sks,
output_cm_lwe_sks,
&mut new_cm_lwe_keyswitch_key,
noise_distribution,
generator,
);
new_cm_lwe_keyswitch_key
}

View File

@@ -0,0 +1,300 @@
//! Module containing primitives pertaining to [`CommonMask LWE ciphertext`](`CmLweCiphertext`)
//! linear algebra, like addition, multiplication, etc.
use crate::core_crypto::algorithms::slice_algorithms::*;
use crate::core_crypto::commons::ciphertext_modulus::CiphertextModulusKind;
use crate::core_crypto::commons::traits::*;
use crate::core_crypto::entities::*;
use crate::core_crypto::experimental::prelude::CmLweCiphertext;
pub fn cm_lwe_ciphertext_add_assign<Scalar, LhsCont, RhsCont>(
lhs: &mut CmLweCiphertext<LhsCont>,
rhs: &CmLweCiphertext<RhsCont>,
) where
Scalar: UnsignedInteger,
LhsCont: ContainerMut<Element = Scalar>,
RhsCont: Container<Element = Scalar>,
{
let ciphertext_modulus = rhs.ciphertext_modulus();
if ciphertext_modulus.is_compatible_with_native_modulus() {
cm_lwe_ciphertext_add_assign_native_mod_compatible(lhs, rhs);
} else {
cm_lwe_ciphertext_add_assign_other_mod(lhs, rhs);
}
}
pub fn cm_lwe_ciphertext_add_assign_native_mod_compatible<Scalar, LhsCont, RhsCont>(
lhs: &mut CmLweCiphertext<LhsCont>,
rhs: &CmLweCiphertext<RhsCont>,
) where
Scalar: UnsignedInteger,
LhsCont: ContainerMut<Element = Scalar>,
RhsCont: Container<Element = Scalar>,
{
assert_eq!(
lhs.ciphertext_modulus(),
rhs.ciphertext_modulus(),
"Mismatched moduli between lhs ({:?}) and rhs ({:?}) CmLweCiphertext",
lhs.ciphertext_modulus(),
rhs.ciphertext_modulus()
);
let ciphertext_modulus = rhs.ciphertext_modulus();
assert!(ciphertext_modulus.is_compatible_with_native_modulus());
slice_wrapping_add_assign(lhs.as_mut(), rhs.as_ref());
}
pub fn cm_lwe_ciphertext_add_assign_other_mod<Scalar, LhsCont, RhsCont>(
lhs: &mut CmLweCiphertext<LhsCont>,
rhs: &CmLweCiphertext<RhsCont>,
) where
Scalar: UnsignedInteger,
LhsCont: ContainerMut<Element = Scalar>,
RhsCont: Container<Element = Scalar>,
{
assert_eq!(
lhs.ciphertext_modulus(),
rhs.ciphertext_modulus(),
"Mismatched moduli between lhs ({:?}) and rhs ({:?}) CmLweCiphertext",
lhs.ciphertext_modulus(),
rhs.ciphertext_modulus()
);
let ciphertext_modulus = rhs.ciphertext_modulus();
assert!(!ciphertext_modulus.is_compatible_with_native_modulus());
slice_wrapping_add_assign_custom_mod(
lhs.as_mut(),
rhs.as_ref(),
ciphertext_modulus.get_custom_modulus().cast_into(),
);
}
pub fn cm_lwe_ciphertext_add<Scalar, OutputCont, LhsCont, RhsCont>(
output: &mut CmLweCiphertext<OutputCont>,
lhs: &CmLweCiphertext<LhsCont>,
rhs: &CmLweCiphertext<RhsCont>,
) where
Scalar: UnsignedInteger,
OutputCont: ContainerMut<Element = Scalar>,
LhsCont: Container<Element = Scalar>,
RhsCont: Container<Element = Scalar>,
{
assert_eq!(
lhs.ciphertext_modulus(),
rhs.ciphertext_modulus(),
"Mismatched moduli between lhs ({:?}) and rhs ({:?}) CmLweCiphertext",
lhs.ciphertext_modulus(),
rhs.ciphertext_modulus()
);
assert_eq!(
output.ciphertext_modulus(),
rhs.ciphertext_modulus(),
"Mismatched moduli between output ({:?}) and rhs ({:?}) CmLweCiphertext",
output.ciphertext_modulus(),
rhs.ciphertext_modulus()
);
slice_wrapping_add(output.as_mut(), lhs.as_ref(), rhs.as_ref());
}
pub fn cm_lwe_ciphertext_plaintext_add_assign<Scalar, InCont>(
lhs: &mut CmLweCiphertext<InCont>,
rhs: Plaintext<Scalar>,
) where
Scalar: UnsignedInteger,
InCont: ContainerMut<Element = Scalar>,
{
let ciphertext_modulus = lhs.ciphertext_modulus();
if ciphertext_modulus.is_compatible_with_native_modulus() {
cm_lwe_ciphertext_plaintext_add_assign_native_mod_compatible(lhs, rhs);
} else {
cm_lwe_ciphertext_plaintext_add_assign_other_mod(lhs, rhs);
}
}
pub fn cm_lwe_ciphertext_plaintext_add_assign_native_mod_compatible<Scalar, InCont>(
lhs: &mut CmLweCiphertext<InCont>,
rhs: Plaintext<Scalar>,
) where
Scalar: UnsignedInteger,
InCont: ContainerMut<Element = Scalar>,
{
let mut bodies = lhs.get_mut_bodies();
let ciphertext_modulus = bodies.ciphertext_modulus();
assert!(ciphertext_modulus.is_compatible_with_native_modulus());
let plaintext = match ciphertext_modulus.kind() {
CiphertextModulusKind::Native => rhs.0,
// Manage power of 2 encoding
CiphertextModulusKind::NonNativePowerOfTwo => rhs
.0
.wrapping_mul(ciphertext_modulus.get_power_of_two_scaling_to_native_torus()),
CiphertextModulusKind::Other => unreachable!(),
};
for body in bodies.as_mut() {
*body = body.wrapping_add(plaintext);
}
}
pub fn cm_lwe_ciphertext_plaintext_add_assign_other_mod<Scalar, InCont>(
lhs: &mut CmLweCiphertext<InCont>,
rhs: Plaintext<Scalar>,
) where
Scalar: UnsignedInteger,
InCont: ContainerMut<Element = Scalar>,
{
let mut bodies = lhs.get_mut_bodies();
let ciphertext_modulus = bodies.ciphertext_modulus();
assert!(!ciphertext_modulus.is_compatible_with_native_modulus());
for body in bodies.as_mut() {
*body = body
.wrapping_add_custom_mod(rhs.0, ciphertext_modulus.get_custom_modulus().cast_into());
}
}
pub fn cm_lwe_ciphertext_plaintext_sub_assign<Scalar, InCont>(
lhs: &mut CmLweCiphertext<InCont>,
rhs: Plaintext<Scalar>,
) where
Scalar: UnsignedInteger,
InCont: ContainerMut<Element = Scalar>,
{
let ciphertext_modulus = lhs.ciphertext_modulus();
if ciphertext_modulus.is_compatible_with_native_modulus() {
cm_lwe_ciphertext_plaintext_sub_assign_native_mod_compatible(lhs, rhs);
} else {
cm_lwe_ciphertext_plaintext_sub_assign_other_mod(lhs, rhs);
}
}
pub fn cm_lwe_ciphertext_plaintext_sub_assign_native_mod_compatible<Scalar, InCont>(
lhs: &mut CmLweCiphertext<InCont>,
rhs: Plaintext<Scalar>,
) where
Scalar: UnsignedInteger,
InCont: ContainerMut<Element = Scalar>,
{
let mut bodies = lhs.get_mut_bodies();
let ciphertext_modulus = bodies.ciphertext_modulus();
assert!(ciphertext_modulus.is_compatible_with_native_modulus());
let plaintext = match ciphertext_modulus.kind() {
CiphertextModulusKind::Native => rhs.0,
// Manage power of 2 encoding
CiphertextModulusKind::NonNativePowerOfTwo => rhs
.0
.wrapping_mul(ciphertext_modulus.get_power_of_two_scaling_to_native_torus()),
CiphertextModulusKind::Other => unreachable!(),
};
for body in bodies.as_mut() {
*body = body.wrapping_sub(plaintext);
}
}
pub fn cm_lwe_ciphertext_plaintext_sub_assign_other_mod<Scalar, InCont>(
lhs: &mut CmLweCiphertext<InCont>,
rhs: Plaintext<Scalar>,
) where
Scalar: UnsignedInteger,
InCont: ContainerMut<Element = Scalar>,
{
let mut bodies = lhs.get_mut_bodies();
let ciphertext_modulus = bodies.ciphertext_modulus();
assert!(!ciphertext_modulus.is_compatible_with_native_modulus());
for body in bodies.as_mut() {
*body = body
.wrapping_sub_custom_mod(rhs.0, ciphertext_modulus.get_custom_modulus().cast_into());
}
}
pub fn cm_lwe_ciphertext_opposite_assign<Scalar, InCont>(ct: &mut CmLweCiphertext<InCont>)
where
Scalar: UnsignedInteger,
InCont: ContainerMut<Element = Scalar>,
{
slice_wrapping_opposite_assign(ct.as_mut());
}
pub fn cm_lwe_ciphertext_cleartext_mul_assign<Scalar, InCont>(
lhs: &mut CmLweCiphertext<InCont>,
rhs: Cleartext<Scalar>,
) where
Scalar: UnsignedInteger,
InCont: ContainerMut<Element = Scalar>,
{
slice_wrapping_scalar_mul_assign(lhs.as_mut(), rhs.0);
}
pub fn cm_lwe_ciphertext_cleartext_mul<Scalar, InputCont, OutputCont>(
output: &mut CmLweCiphertext<OutputCont>,
lhs: &CmLweCiphertext<InputCont>,
rhs: Cleartext<Scalar>,
) where
Scalar: UnsignedInteger,
InputCont: Container<Element = Scalar>,
OutputCont: ContainerMut<Element = Scalar>,
{
assert_eq!(
output.ciphertext_modulus(),
lhs.ciphertext_modulus(),
"Mismatched moduli between output ({:?}) and lhs ({:?}) CmLweCiphertext",
output.ciphertext_modulus(),
lhs.ciphertext_modulus()
);
output.as_mut().copy_from_slice(lhs.as_ref());
cm_lwe_ciphertext_cleartext_mul_assign(output, rhs);
}
pub fn cm_lwe_ciphertext_sub_assign<Scalar, LhsCont, RhsCont>(
lhs: &mut CmLweCiphertext<LhsCont>,
rhs: &CmLweCiphertext<RhsCont>,
) where
Scalar: UnsignedInteger,
LhsCont: ContainerMut<Element = Scalar>,
RhsCont: Container<Element = Scalar>,
{
assert_eq!(
lhs.ciphertext_modulus(),
rhs.ciphertext_modulus(),
"Mismatched moduli between lhs ({:?}) and rhs ({:?}) CmLweCiphertext",
lhs.ciphertext_modulus(),
rhs.ciphertext_modulus()
);
slice_wrapping_sub_assign(lhs.as_mut(), rhs.as_ref());
}
pub fn cm_lwe_ciphertext_sub<Scalar, OutputCont, LhsCont, RhsCont>(
output: &mut CmLweCiphertext<OutputCont>,
lhs: &CmLweCiphertext<LhsCont>,
rhs: &CmLweCiphertext<RhsCont>,
) where
Scalar: UnsignedInteger,
OutputCont: ContainerMut<Element = Scalar>,
LhsCont: Container<Element = Scalar>,
RhsCont: Container<Element = Scalar>,
{
assert_eq!(
lhs.ciphertext_modulus(),
rhs.ciphertext_modulus(),
"Mismatched moduli between lhs ({:?}) and rhs ({:?}) CmLweCiphertext",
lhs.ciphertext_modulus(),
rhs.ciphertext_modulus()
);
assert_eq!(
output.ciphertext_modulus(),
rhs.ciphertext_modulus(),
"Mismatched moduli between output ({:?}) and rhs ({:?}) CmLweCiphertext",
output.ciphertext_modulus(),
rhs.ciphertext_modulus()
);
output.as_mut().copy_from_slice(lhs.as_ref());
cm_lwe_ciphertext_sub_assign(output, rhs);
}

View File

@@ -0,0 +1,117 @@
//! Module containing primitives pertaining to `CommonMask LWE ciphertext
//! packing`.
use crate::core_crypto::algorithms::slice_algorithms::*;
use crate::core_crypto::commons::math::decomposition::SignedDecomposer;
use crate::core_crypto::commons::numeric::UnsignedInteger;
use crate::core_crypto::commons::traits::*;
use crate::core_crypto::entities::*;
use crate::core_crypto::experimental::prelude::{CmLweCiphertext, CmLwePackingKey};
use itertools::Itertools;
pub fn pack_lwe_ciphertexts_into_cm<Scalar, KSKCont, InputCont, OutputCont>(
cm_lwe_packing_key: &CmLwePackingKey<KSKCont>,
// TODO: support both &[LweCiphertext] and &LweCiphertextList
input_lwe_ciphertexts: &[LweCiphertext<InputCont>],
output_cm_lwe_ciphertext: &mut CmLweCiphertext<OutputCont>,
) where
Scalar: UnsignedInteger,
KSKCont: Container<Element = Scalar>,
InputCont: Container<Element = Scalar>,
OutputCont: ContainerMut<Element = Scalar>,
{
let input_ciphertext_modulus = input_lwe_ciphertexts[0].ciphertext_modulus();
for input_lwe_ciphertext in input_lwe_ciphertexts {
assert_eq!(
cm_lwe_packing_key.input_key_lwe_dimension(),
input_lwe_ciphertext.lwe_size().to_lwe_dimension(),
"Mismatched input LweDimension. \
CmLwePackingKey input LweDimension: {:?}, input CmLweCiphertext LweDimension {:?}.",
cm_lwe_packing_key.input_key_lwe_dimension(),
input_lwe_ciphertext.lwe_size().to_lwe_dimension(),
);
assert_eq!(
input_lwe_ciphertext.ciphertext_modulus(),
input_ciphertext_modulus,
);
}
assert_eq!(
cm_lwe_packing_key.output_lwe_dimension(),
output_cm_lwe_ciphertext.lwe_dimension(),
"Mismatched output LweDimension. \
CmLwePackingKey output LweDimension: {:?}, output CmLweCiphertext LweDimension {:?}.",
cm_lwe_packing_key.output_lwe_dimension(),
output_cm_lwe_ciphertext.lwe_dimension(),
);
assert_eq!(
cm_lwe_packing_key.output_cm_dimension(),
output_cm_lwe_ciphertext.cm_dimension(),
"Mismatched output LweDimension. \
CmLwePackingKey output LweDimension: {:?}, output CmLweCiphertext LweDimension {:?}.",
cm_lwe_packing_key.output_lwe_dimension(),
output_cm_lwe_ciphertext.lwe_dimension(),
);
let output_ciphertext_modulus = output_cm_lwe_ciphertext.ciphertext_modulus();
assert_eq!(
cm_lwe_packing_key.ciphertext_modulus(),
output_ciphertext_modulus,
"Mismatched CiphertextModulus. \
CmLwePackingKey CiphertextModulus: {:?}, output CmLweCiphertext CiphertextModulus {:?}.",
cm_lwe_packing_key.ciphertext_modulus(),
output_ciphertext_modulus
);
assert!(
output_ciphertext_modulus.is_compatible_with_native_modulus(),
"This operation currently only supports power of 2 moduli"
);
assert!(
input_ciphertext_modulus.is_compatible_with_native_modulus(),
"This operation currently only supports power of 2 moduli"
);
// Clear the output ciphertext, as it will get updated gradually
output_cm_lwe_ciphertext.as_mut().fill(Scalar::ZERO);
for (i, (key_part, input_lwe_ciphertext)) in cm_lwe_packing_key
.iter()
.zip_eq(input_lwe_ciphertexts.iter())
.enumerate()
{
let mut bodies = output_cm_lwe_ciphertext.get_mut_bodies();
let body = &mut bodies.as_mut()[i];
*body = body.wrapping_add(*input_lwe_ciphertext.get_body().data);
// We instantiate a decomposer
let decomposer = SignedDecomposer::new(
cm_lwe_packing_key.decomposition_base_log(),
cm_lwe_packing_key.decomposition_level_count(),
);
for (keyswitch_key_block, &input_mask_element) in key_part
.iter()
.zip_eq(input_lwe_ciphertext.get_mask().as_ref())
{
let decomposition_iter = decomposer.decompose(input_mask_element);
// Loop over the levels
for (level_key_ciphertext, decomposed) in
keyswitch_key_block.iter().zip_eq(decomposition_iter)
{
slice_wrapping_sub_scalar_mul_assign(
output_cm_lwe_ciphertext.as_mut(),
level_key_ciphertext.into_container(),
decomposed.value(),
);
}
}
}
}

View File

@@ -0,0 +1,158 @@
//! Module containing primitives pertaining to
//! [`CommonMask LWE packing keys generation`](`CmLwePackingKey`)
use crate::core_crypto::commons::generators::EncryptionRandomGenerator;
use crate::core_crypto::commons::math::decomposition::{DecompositionLevel, DecompositionTerm};
use crate::core_crypto::commons::math::random::{Distribution, RandomGenerable};
use crate::core_crypto::commons::parameters::*;
use crate::core_crypto::commons::traits::*;
use crate::core_crypto::entities::*;
use crate::core_crypto::experimental::algorithms::*;
use crate::core_crypto::experimental::prelude::{
CmDimension, CmLwePackingKey, CmLwePackingKeyOwned,
};
use itertools::Itertools;
pub fn generate_cm_lwe_packing_key<
Scalar,
NoiseDistribution,
InputKeyCont,
OutputKeyCont,
KSKeyCont,
Gen,
>(
input_lwe_sk: &LweSecretKey<InputKeyCont>,
output_lwe_sks: &[LweSecretKey<OutputKeyCont>],
lwe_keyswitch_key: &mut CmLwePackingKey<KSKeyCont>,
noise_distribution: NoiseDistribution,
generator: &mut EncryptionRandomGenerator<Gen>,
) where
Scalar: UnsignedTorus + RandomGenerable<NoiseDistribution, CustomModulus = Scalar>,
NoiseDistribution: Distribution,
InputKeyCont: Container<Element = Scalar>,
OutputKeyCont: Container<Element = Scalar>,
KSKeyCont: ContainerMut<Element = Scalar>,
Gen: ByteRandomGenerator,
{
assert!(
lwe_keyswitch_key.input_key_lwe_dimension() == input_lwe_sk.lwe_dimension(),
"The destination CmLwePackingKey input LweDimension is not equal \
to the input LweSecretKey LweDimension. Destination: {:?}, input: {:?}",
lwe_keyswitch_key.input_key_lwe_dimension(),
input_lwe_sk.lwe_dimension()
);
for output_lwe_sk in output_lwe_sks {
assert!(
lwe_keyswitch_key.output_lwe_dimension() == output_lwe_sk.lwe_dimension(),
"The destination CmLwePackingKey output LweDimension is not equal \
to the output LweSecretKey LweDimension. Destination: {:?}, output: {:?}",
lwe_keyswitch_key.output_lwe_dimension(),
output_lwe_sk.lwe_dimension()
);
}
let cm_dimension = CmDimension(output_lwe_sks.len());
let decomp_base_log = lwe_keyswitch_key.decomposition_base_log();
let decomp_level_count = lwe_keyswitch_key.decomposition_level_count();
let ciphertext_modulus = lwe_keyswitch_key.ciphertext_modulus();
assert!(ciphertext_modulus.is_compatible_with_native_modulus());
// The plaintexts used to encrypt a key element will be stored in this buffer
let mut decomposition_plaintexts_buffer =
PlaintextListOwned::new(Scalar::ZERO, PlaintextCount(decomp_level_count.0));
// Iterate over the input key elements and the destination lwe_keyswitch_key memory
for (i, mut keyswitch_key_block) in lwe_keyswitch_key.iter_mut().enumerate() {
for (input_key_element, mut keyswitch_key_block_block) in input_lwe_sk
.as_ref()
.iter()
.zip_eq(keyswitch_key_block.iter_mut())
{
// We fill the buffer with the powers of the key elements
for (level, message) in (1..=decomp_level_count.0)
.rev()
.map(DecompositionLevel)
.zip_eq(decomposition_plaintexts_buffer.iter_mut())
{
// Here we take the decomposition term from the native torus, bring it to the
// torus
// we are working with by dividing by the scaling factor and the
// encryption will take care of mapping that back to the native
// torus
*message.0 = DecompositionTerm::new(level, decomp_base_log, *input_key_element)
.to_recomposition_summand()
.wrapping_div(ciphertext_modulus.get_power_of_two_scaling_to_native_torus());
}
let list = decomposition_plaintexts_buffer
.iter()
.map(|decomposition_plaintext| {
PlaintextList::from_container(
(0..cm_dimension.0)
.map(|j| {
if i == j {
*decomposition_plaintext.0
} else {
Scalar::ZERO
}
})
.collect_vec(),
)
})
.collect_vec();
encrypt_cm_lwe_ciphertext_list(
output_lwe_sks,
&mut keyswitch_key_block_block,
&list,
noise_distribution,
generator,
);
}
}
}
pub fn allocate_and_generate_new_cm_lwe_packing_key<
Scalar,
NoiseDistribution,
InputKeyCont,
OutputKeyCont,
Gen,
>(
input_lwe_sk: &LweSecretKey<InputKeyCont>,
output_lwe_sk: &[LweSecretKey<OutputKeyCont>],
decomp_base_log: DecompositionBaseLog,
decomp_level_count: DecompositionLevelCount,
noise_distribution: NoiseDistribution,
ciphertext_modulus: CiphertextModulus<Scalar>,
generator: &mut EncryptionRandomGenerator<Gen>,
) -> CmLwePackingKeyOwned<Scalar>
where
Scalar: UnsignedTorus + RandomGenerable<NoiseDistribution, CustomModulus = Scalar>,
NoiseDistribution: Distribution,
InputKeyCont: Container<Element = Scalar>,
OutputKeyCont: Container<Element = Scalar>,
Gen: ByteRandomGenerator,
{
let mut new_lwe_keyswitch_key = CmLwePackingKeyOwned::new(
Scalar::ZERO,
decomp_base_log,
decomp_level_count,
input_lwe_sk.lwe_dimension(),
output_lwe_sk[0].lwe_dimension(),
CmDimension(output_lwe_sk.len()),
ciphertext_modulus,
);
generate_cm_lwe_packing_key(
input_lwe_sk,
output_lwe_sk,
&mut new_lwe_keyswitch_key,
noise_distribution,
generator,
);
new_lwe_keyswitch_key
}

View File

@@ -0,0 +1,141 @@
//! Module containing primitives pertaining to the `CommonMask LWE programmable
//! bootstrap` using 64 bits FFT for polynomial multiplication.
use crate::core_crypto::commons::computation_buffers::ComputationBuffers;
use crate::core_crypto::commons::traits::*;
use crate::core_crypto::experimental::entities::*;
use crate::core_crypto::experimental::prelude::*;
use crate::core_crypto::fft_impl::fft64::math::fft::{Fft, FftView};
use dyn_stack::PodStack;
use tfhe_fft::c64;
pub fn programmable_bootstrap_cm_lwe_ciphertext<
InputScalar,
OutputScalar,
InputCont,
OutputCont,
AccCont,
KeyCont,
>(
input: &CmLweCiphertext<InputCont>,
output: &mut CmLweCiphertext<OutputCont>,
accumulator: &CmGlweCiphertext<AccCont>,
fourier_bsk: &FourierCmLweBootstrapKey<KeyCont>,
) where
// CastInto required for PBS modulus switch which returns a usize
InputScalar: UnsignedTorus + CastInto<usize>,
OutputScalar: UnsignedTorus,
InputCont: Container<Element = InputScalar>,
OutputCont: ContainerMut<Element = OutputScalar>,
AccCont: Container<Element = OutputScalar>,
KeyCont: Container<Element = c64>,
{
assert!(
input.ciphertext_modulus().is_power_of_two(),
"This operation requires the input to have a power of two modulus."
);
assert_eq!(
output.ciphertext_modulus(),
accumulator.ciphertext_modulus()
);
let mut buffers = ComputationBuffers::new();
let fft = Fft::new(fourier_bsk.polynomial_size());
let fft = fft.as_view();
buffers.resize(
cm_bootstrap_requirement::<OutputScalar>(
fourier_bsk.glwe_dimension(),
fourier_bsk.cm_dimension(),
fourier_bsk.polynomial_size(),
fft,
)
.unaligned_bytes_required(),
);
let stack = buffers.stack();
cm_programmable_bootstrap_lwe_ciphertext_mem_optimized(
input,
output,
accumulator,
fourier_bsk,
fft,
stack,
);
}
pub fn cm_programmable_bootstrap_lwe_ciphertext_mem_optimized<
InputScalar,
OutputScalar,
InputCont,
OutputCont,
AccCont,
KeyCont,
>(
input: &CmLweCiphertext<InputCont>,
output: &mut CmLweCiphertext<OutputCont>,
accumulator: &CmGlweCiphertext<AccCont>,
fourier_bsk: &FourierCmLweBootstrapKey<KeyCont>,
fft: FftView<'_>,
stack: &mut PodStack,
) where
// CastInto required for PBS modulus switch which returns a usize
InputScalar: UnsignedTorus + CastInto<usize>,
OutputScalar: UnsignedTorus,
InputCont: Container<Element = InputScalar>,
OutputCont: ContainerMut<Element = OutputScalar>,
AccCont: Container<Element = OutputScalar>,
KeyCont: Container<Element = c64>,
{
assert_eq!(
accumulator.ciphertext_modulus(),
output.ciphertext_modulus(),
"Mismatched moduli between accumulator ({:?}) and output ({:?})",
accumulator.ciphertext_modulus(),
output.ciphertext_modulus()
);
assert_eq!(
fourier_bsk.input_lwe_dimension(),
input.lwe_dimension(),
"Mismatched input LweDimension. \
FourierLweBootstrapKey input LweDimension: {:?}, input CmLweCiphertext LweDimension {:?}.",
fourier_bsk.input_lwe_dimension(),
input.lwe_dimension(),
);
assert_eq!(
fourier_bsk.output_lwe_dimension(),
output.lwe_dimension(),
"Mismatched output LweDimension. \
FourierLweBootstrapKey input LweDimension: {:?}, input CmLweCiphertext LweDimension {:?}.",
fourier_bsk.output_lwe_dimension(),
output.lwe_dimension(),
);
assert_eq!(
fourier_bsk.cm_dimension(),
input.cm_dimension(),
"Mismatched input CmDimension. \
FourierLweBootstrapKey CmDimension: {:?}, input CmLweCiphertext CmDimension {:?}.",
fourier_bsk.cm_dimension(),
input.cm_dimension(),
);
assert_eq!(
fourier_bsk.cm_dimension(),
output.cm_dimension(),
"Mismatched output CmDimension. \
FourierLweBootstrapKey CmDimension: {:?}, output CmLweCiphertext CmDimension {:?}.",
fourier_bsk.cm_dimension(),
output.cm_dimension(),
);
cm_bootstrap(
fourier_bsk.as_view(),
output.as_mut_view(),
input.as_view(),
accumulator.as_view(),
fft,
stack,
);
}

View File

@@ -0,0 +1,93 @@
pub mod cm_fft64;
use itertools::Itertools;
use super::allocate_and_trivially_encrypt_new_cm_glwe_ciphertext;
use crate::core_crypto::commons::parameters::*;
use crate::core_crypto::commons::traits::*;
use crate::core_crypto::entities::*;
use crate::core_crypto::experimental::prelude::*;
// TODO: add support for one function per cm slot
pub fn cm_generate_programmable_bootstrap_glwe_lut<F, Scalar: UnsignedTorus + CastFrom<usize>>(
polynomial_size: PolynomialSize,
glwe_dimension: GlweDimension,
cm_dimension: CmDimension,
message_modulus: usize,
ciphertext_modulus: CiphertextModulus<Scalar>,
delta: Scalar,
f: F,
) -> CmGlweCiphertextOwned<Scalar>
where
F: Fn(Scalar) -> Scalar,
{
// N/(p/2) = size of each block, to correct noise from the input we introduce the
// notion of box, which manages redundancy to yield a denoised value
// for several noisy values around a true input value.
let box_size = polynomial_size.0 / message_modulus;
let accumulator_scalar = build_lut(
polynomial_size,
message_modulus,
ciphertext_modulus,
delta,
f,
box_size,
);
let accumulator_scalar = (0..cm_dimension.0)
.flat_map(|_| accumulator_scalar.iter())
.copied()
.collect_vec();
let accumulator_plaintext = PlaintextList::from_container(accumulator_scalar);
allocate_and_trivially_encrypt_new_cm_glwe_ciphertext(
glwe_dimension,
cm_dimension,
polynomial_size,
&accumulator_plaintext,
ciphertext_modulus,
)
}
fn build_lut<F, Scalar: UnsignedTorus + CastFrom<usize>>(
polynomial_size: PolynomialSize,
message_modulus: usize,
ciphertext_modulus: CiphertextModulus<Scalar>,
delta: Scalar,
f: F,
box_size: usize,
) -> Vec<Scalar>
where
F: Fn(Scalar) -> Scalar,
{
// Create the accumulator
let mut accumulator_scalar = vec![Scalar::ZERO; polynomial_size.0];
// Fill each box with the encoded denoised value
for i in 0..message_modulus {
let index = i * box_size;
accumulator_scalar[index..index + box_size]
.iter_mut()
.for_each(|a| *a = f(Scalar::cast_from(i)) * delta);
}
let half_box_size = box_size / 2;
if ciphertext_modulus.is_compatible_with_native_modulus() {
// Negate the first half_box_size coefficients to manage negacyclicity and rotate
for a_i in accumulator_scalar[0..half_box_size].iter_mut() {
*a_i = (*a_i).wrapping_neg();
}
} else {
let modulus: Scalar = ciphertext_modulus.get_custom_modulus().cast_into();
for a_i in accumulator_scalar[0..half_box_size].iter_mut() {
*a_i = (*a_i).wrapping_neg_custom_mod(modulus);
}
}
// Rotate the accumulator
accumulator_scalar.rotate_left(half_box_size);
accumulator_scalar
}

View File

@@ -0,0 +1,148 @@
use crate::core_crypto::commons::dispersion::Variance;
use crate::core_crypto::commons::parameters::{NoiseEstimationMeasureBound, RSigmaFactor};
use crate::core_crypto::commons::traits::{Container, ContainerMut, UnsignedInteger};
use crate::core_crypto::experimental::entities::{CmLweCiphertext, CmLweCiphertextList};
use crate::core_crypto::experimental::prelude::cm_lwe_ciphertext_add_assign;
use crate::core_crypto::prelude::modulus_switch_noise_reduction::{
measure_modulus_switch_noise_estimation_for_binary_key, Candidate, CandidateResult,
};
use crate::core_crypto::prelude::{
CiphertextModulus, CiphertextModulusLog, ContiguousEntityContainer, DispersionParameter,
};
use itertools::Itertools;
pub fn choose_candidate_to_improve_modulus_switch_noise_for_binary_key<Scalar, C1, C2>(
lwe: &CmLweCiphertext<C1>,
encryptions_of_zero: &CmLweCiphertextList<C2>,
r_sigma_factor: RSigmaFactor,
bound: NoiseEstimationMeasureBound,
input_variance: Variance,
log_modulus: CiphertextModulusLog,
) -> CandidateResult
where
Scalar: UnsignedInteger,
C1: Container<Element = Scalar>,
C2: Container<Element = Scalar>,
{
assert_eq!(
lwe.lwe_dimension(),
encryptions_of_zero.lwe_dimension(),
"input lwe size (={:?}) != encryptions of zero lwe size (={:?})",
lwe.lwe_dimension(),
encryptions_of_zero.lwe_dimension(),
);
assert_eq!(
lwe.ciphertext_modulus(),
encryptions_of_zero.ciphertext_modulus(),
"input ciphertext_modulus (={:?}) != encryptions of zero ciphertext_modulus (={:?})",
lwe.ciphertext_modulus(),
encryptions_of_zero.ciphertext_modulus(),
);
assert_ne!(
encryptions_of_zero.cm_lwe_ciphertext_count().0,
0,
"Expected at least one encryption of zero"
);
assert_eq!(
lwe.ciphertext_modulus(),
CiphertextModulus::new_native(),
"Non native modulus are not supported, got {}",
lwe.ciphertext_modulus(),
);
let modulus = lwe.ciphertext_modulus().raw_modulus_float();
let input_variance = input_variance.get_modular_variance(modulus);
let mask = lwe.get_mask();
let mask = mask.as_ref();
let base_measure = measure_modulus_switch_noise_estimation_for_binary_key(
r_sigma_factor,
input_variance,
log_modulus,
mask.iter().copied(),
Scalar::ZERO,
);
let mut best_candidate = Candidate::NoAddition;
let mut best_measure = base_measure;
if base_measure <= bound.0 {
return CandidateResult::SatisfyingBound(best_candidate);
}
for (index, encryption_of_zero) in encryptions_of_zero.iter().enumerate() {
let encryption_of_zero_mask = encryption_of_zero.get_mask();
let encryption_of_zero_mask = encryption_of_zero_mask.as_ref();
let mask_sum = mask
.iter()
.zip_eq(encryption_of_zero_mask.iter())
.map(|(a, b)| a.wrapping_add(*b));
let measure = measure_modulus_switch_noise_estimation_for_binary_key(
r_sigma_factor,
input_variance,
log_modulus,
mask_sum,
Scalar::ZERO,
);
if measure < best_measure {
best_measure = measure;
best_candidate = Candidate::AddEncryptionOfZero { index };
}
if measure <= bound.0 {
return CandidateResult::SatisfyingBound(best_candidate);
}
}
CandidateResult::BestNotSatisfyingBound(best_candidate)
}
pub fn improve_lwe_ciphertext_modulus_switch_noise_for_binary_key_cm<Scalar, C1, C2>(
lwe: &mut CmLweCiphertext<C1>,
encryptions_of_zero: &CmLweCiphertextList<C2>,
r_sigma_factor: RSigmaFactor,
bound: NoiseEstimationMeasureBound,
input_variance: Variance,
log_modulus: CiphertextModulusLog,
) where
Scalar: UnsignedInteger,
C1: ContainerMut<Element = Scalar>,
C2: Container<Element = Scalar>,
{
let candidate = choose_candidate_to_improve_modulus_switch_noise_for_binary_key(
lwe,
encryptions_of_zero,
r_sigma_factor,
bound,
input_variance,
log_modulus,
);
#[cfg(test)]
assert!(
matches!(candidate, CandidateResult::SatisfyingBound(_)),
"MS noise reduction bound not reached for any candidate"
);
let candidate = match candidate {
CandidateResult::SatisfyingBound(candidate) => candidate,
CandidateResult::BestNotSatisfyingBound(candidate) => candidate,
};
match candidate {
Candidate::NoAddition => {}
Candidate::AddEncryptionOfZero { index } => {
let encryption_of_zero = encryptions_of_zero.get(index);
cm_lwe_ciphertext_add_assign(lwe, &encryption_of_zero);
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,31 @@
pub mod cm_bootstrap;
pub mod cm_ggsw_encryption;
pub mod cm_ggsw_external_product;
pub mod cm_glwe_encryption;
pub mod cm_glwe_sample_extraction;
pub mod cm_lwe_bootstrap_key_conversion;
pub mod cm_lwe_bootstrap_key_generation;
pub mod cm_lwe_encryption;
pub mod cm_lwe_keyswitch;
pub mod cm_lwe_keyswitch_key_generation;
pub mod cm_lwe_linear_algebra;
pub mod cm_lwe_packing;
pub mod cm_lwe_packing_key_generation;
pub mod cm_lwe_programmable_bootstrapping;
pub mod cm_modulus_switch_noise_reduction;
pub mod cm_params;
pub use cm_bootstrap::*;
pub use cm_ggsw_encryption::*;
pub use cm_ggsw_external_product::*;
pub use cm_glwe_encryption::*;
pub use cm_glwe_sample_extraction::*;
pub use cm_lwe_bootstrap_key_conversion::*;
pub use cm_lwe_bootstrap_key_generation::*;
pub use cm_lwe_encryption::*;
pub use cm_lwe_keyswitch::*;
pub use cm_lwe_linear_algebra::*;
pub use cm_lwe_packing::*;
pub use cm_lwe_packing_key_generation::*;
pub use cm_lwe_programmable_bootstrapping::*;
pub use cm_params::*;

View File

@@ -1,3 +1,4 @@
pub mod common_mask_algorithms;
pub mod glwe_fast_keyswitch;
pub mod glwe_partial_sample_extraction;
pub mod lwe_shrinking_keyswitch;
@@ -8,6 +9,7 @@ pub mod pseudo_ggsw_encryption;
pub mod shared_glwe_secret_key_generation;
pub mod shared_lwe_secret_key_generation;
pub use common_mask_algorithms::*;
pub use glwe_fast_keyswitch::*;
pub use glwe_partial_sample_extraction::*;
pub use lwe_shrinking_keyswitch::*;

View File

@@ -4,9 +4,7 @@
use crate::core_crypto::commons::computation_buffers::ComputationBuffers;
use crate::core_crypto::commons::traits::*;
use crate::core_crypto::experimental::entities::fourier_pseudo_ggsw_ciphertext::{
fill_with_forward_fourier_scratch, PseudoFourierGgswCiphertext,
};
use crate::core_crypto::experimental::entities::fourier_pseudo_ggsw_ciphertext::PseudoFourierGgswCiphertext;
use crate::core_crypto::experimental::entities::pseudo_ggsw_ciphertext::PseudoGgswCiphertext;
use crate::core_crypto::fft_impl::fft64::math::fft::{Fft, FftView};
use dyn_stack::{PodStack, StackReq};
@@ -67,5 +65,5 @@ pub fn convert_standard_pseudo_ggsw_ciphertext_to_fourier_mem_optimized<
pub fn convert_standard_pseudo_ggsw_ciphertext_to_fourier_mem_optimized_requirement(
fft: FftView<'_>,
) -> StackReq {
fill_with_forward_fourier_scratch(fft)
fft.forward_scratch()
}

View File

@@ -0,0 +1,61 @@
use crate::core_crypto::experimental::prelude::{
allocate_and_encrypt_new_cm_lwe_ciphertext, CmDimension,
};
use crate::core_crypto::prelude::*;
use itertools::Itertools;
#[test]
fn cm_encryption() {
let lwe_dimension = LweDimension(742);
let lwe_noise_distribution =
Gaussian::from_dispersion_parameter(StandardDev(0.0000000007069849454709433), 0.);
let ciphertext_modulus = CiphertextModulus::new_native();
let cm_dimension = CmDimension(10);
// Create the PRNG
let mut seeder = new_seeder();
let seeder = seeder.as_mut();
let mut encryption_generator =
EncryptionRandomGenerator::<DefaultRandomGenerator>::new(seeder.seed(), seeder);
let mut secret_generator = SecretRandomGenerator::<DefaultRandomGenerator>::new(seeder.seed());
// Create the LweSecretKey
let lwe_secret_keys = (0..cm_dimension.0)
.map(|_| {
allocate_and_generate_new_binary_lwe_secret_key(lwe_dimension, &mut secret_generator)
})
.collect_vec();
// Create the plaintext
let plaintext = (0..cm_dimension.0).map(|i| (i as u64) << 55).collect_vec();
let plaintext_list = PlaintextList::from_container(plaintext.as_slice());
// Create a new LweCiphertext
let lwe = allocate_and_encrypt_new_cm_lwe_ciphertext(
&lwe_secret_keys,
&plaintext_list,
lwe_noise_distribution,
ciphertext_modulus,
&mut encryption_generator,
);
for i in 0..cm_dimension.0 {
let decrypted_plaintext =
decrypt_lwe_ciphertext(&lwe_secret_keys[i], &lwe.extract_lwe_ciphertext(i));
// Round and remove encoding
// First create a decomposer working on the high 4 bits corresponding to our encoding.
let decomposer = SignedDecomposer::new(DecompositionBaseLog(9), DecompositionLevelCount(1));
let rounded = decomposer.closest_representable(decrypted_plaintext.0);
// Remove the encoding
let cleartext = rounded >> 55;
// Check we recovered the original message
assert_eq!(cleartext, plaintext[i] >> 55);
}
}

View File

@@ -0,0 +1,110 @@
use super::super::cm_params::{CmApParams, CM_PARAM_2_2_MINUS_64};
use super::super::*;
use crate::core_crypto::experimental::prelude::cm_lwe_keyswitch_key_generation::allocate_and_generate_new_cm_lwe_keyswitch_key;
use crate::core_crypto::prelude::misc::check_encrypted_content_respects_mod;
use crate::core_crypto::prelude::*;
use itertools::Itertools;
const NB_TESTS: usize = 10;
#[test]
fn cm_keyswitch() {
cm_keyswitch_generic(&CM_PARAM_2_2_MINUS_64);
}
fn cm_keyswitch_generic(params: &CmApParams) {
let in_lwe_dimension = params
.glwe_dimension
.to_equivalent_lwe_dimension(params.polynomial_size);
let out_lwe_dimension = params.lwe_dimension;
let in_lwe_noise_distribution = params.glwe_noise_distribution;
let key_lwe_noise_distribution = params.lwe_noise_distribution;
let ciphertext_modulus = params.ciphertext_modulus;
let encoding_with_padding = get_encoding_with_padding(ciphertext_modulus);
let ks_decomp_base_log = params.base_log_ks;
let ks_decomp_level_count = params.level_ks;
let cm_dimension = params.cm_dimension;
let mut rsc = TestResources::new();
let msg_modulus = 1 << params.precision;
let mut msg = msg_modulus;
let delta: u64 = encoding_with_padding / msg_modulus;
for _ in 0..NB_TESTS {
let lwe_sks_in = (0..cm_dimension.0)
.map(|_| {
allocate_and_generate_new_binary_lwe_secret_key(
in_lwe_dimension,
&mut rsc.secret_random_generator,
)
})
.collect_vec();
let lwe_sks_out = (0..cm_dimension.0)
.map(|_| {
allocate_and_generate_new_binary_lwe_secret_key(
out_lwe_dimension,
&mut rsc.secret_random_generator,
)
})
.collect_vec();
let cm_lwe_keyswitch_key = allocate_and_generate_new_cm_lwe_keyswitch_key(
&lwe_sks_in,
&lwe_sks_out,
cm_dimension,
ks_decomp_base_log,
ks_decomp_level_count,
key_lwe_noise_distribution,
ciphertext_modulus,
&mut rsc.encryption_random_generator,
);
assert!(check_encrypted_content_respects_mod(
&cm_lwe_keyswitch_key,
ciphertext_modulus
));
while msg != 0 {
msg = msg.wrapping_sub(1);
let pts = PlaintextList::from_container(
(0..cm_dimension.0).map(|_| msg * delta).collect_vec(),
);
let ct = allocate_and_encrypt_new_cm_lwe_ciphertext(
&lwe_sks_in,
&pts,
in_lwe_noise_distribution,
ciphertext_modulus,
&mut rsc.encryption_random_generator,
);
let mut output_ct =
CmLweCiphertext::new(0, out_lwe_dimension, cm_dimension, ciphertext_modulus);
cm_keyswitch_lwe_ciphertext(&cm_lwe_keyswitch_key, &ct, &mut output_ct);
for (i, lwe_sk_out) in lwe_sks_out.iter().enumerate() {
let output_ct = output_ct.extract_lwe_ciphertext(i);
assert!(check_encrypted_content_respects_mod(
&output_ct,
ciphertext_modulus
));
let decrypted = decrypt_lwe_ciphertext(lwe_sk_out, &output_ct);
let decoded = round_decode(decrypted.0, delta) % msg_modulus;
assert_eq!(msg, decoded);
}
}
}
}

View File

@@ -0,0 +1,102 @@
use crate::core_crypto::experimental::prelude::*;
use crate::core_crypto::prelude::misc::check_encrypted_content_respects_mod;
use crate::core_crypto::prelude::test::{
get_encoding_with_padding, round_decode, ClassicTestParams, TestResources,
TEST_PARAMS_4_BITS_NATIVE_U64,
};
use crate::core_crypto::prelude::*;
use itertools::Itertools;
const NB_TESTS: usize = 10;
#[test]
fn cm_packing() {
cm_packing_generic(TEST_PARAMS_4_BITS_NATIVE_U64);
}
fn cm_packing_generic(params: ClassicTestParams<u64>) {
let in_lwe_dimension = LweDimension(13);
let out_lwe_dimension = LweDimension(10);
let lwe_noise_distribution = params.lwe_noise_distribution;
let ciphertext_modulus = params.ciphertext_modulus;
let message_modulus_log = params.message_modulus_log;
let encoding_with_padding = get_encoding_with_padding(ciphertext_modulus);
let ks_decomp_base_log = params.ks_base_log;
let ks_decomp_level_count = params.ks_level;
let cm_dimension = CmDimension(10);
let mut rsc = TestResources::new();
let msg_modulus = 1 << message_modulus_log.0;
let mut msg = msg_modulus;
let delta: u64 = encoding_with_padding / msg_modulus;
for _ in 0..NB_TESTS {
let lwe_sk_in = allocate_and_generate_new_binary_lwe_secret_key(
in_lwe_dimension,
&mut rsc.secret_random_generator,
);
let lwe_sks_out = (0..cm_dimension.0)
.map(|_| {
allocate_and_generate_new_binary_lwe_secret_key(
out_lwe_dimension,
&mut rsc.secret_random_generator,
)
})
.collect_vec();
let packing_key = allocate_and_generate_new_cm_lwe_packing_key(
&lwe_sk_in,
&lwe_sks_out,
ks_decomp_base_log,
ks_decomp_level_count,
lwe_noise_distribution,
ciphertext_modulus,
&mut rsc.encryption_random_generator,
);
assert!(check_encrypted_content_respects_mod(
&packing_key,
ciphertext_modulus
));
while msg != 0 {
msg = msg.wrapping_sub(1);
let ct = (0..cm_dimension.0)
.map(|_| {
allocate_and_encrypt_new_lwe_ciphertext(
&lwe_sk_in,
Plaintext(msg * delta),
lwe_noise_distribution,
ciphertext_modulus,
&mut rsc.encryption_random_generator,
)
})
.collect_vec();
let mut output_ct =
CmLweCiphertext::new(0, out_lwe_dimension, cm_dimension, ciphertext_modulus);
pack_lwe_ciphertexts_into_cm(&packing_key, &ct, &mut output_ct);
for (i, lwe_sk_out) in lwe_sks_out.iter().enumerate() {
let output_ct = output_ct.extract_lwe_ciphertext(i);
assert!(check_encrypted_content_respects_mod(
&output_ct,
ciphertext_modulus
));
let decrypted = decrypt_lwe_ciphertext(lwe_sk_out, &output_ct);
let decoded = round_decode(decrypted.0, delta) % msg_modulus;
assert_eq!(msg, decoded);
}
}
}
}

View File

@@ -0,0 +1,390 @@
use super::super::cm_lwe_keyswitch_key_generation::allocate_and_generate_new_cm_lwe_keyswitch_key;
use super::super::cm_params::{CmApParams, CM_PARAM_2_2_MINUS_64};
use super::super::*;
use crate::core_crypto::commons::test_tools::{
check_both_ratio_under, normality_test_f64, random_uint_between, variance,
};
use crate::core_crypto::experimental::prelude::cm_modulus_switch_noise_reduction::improve_lwe_ciphertext_modulus_switch_noise_for_binary_key_cm;
use crate::core_crypto::prelude::misc::torus_modular_diff;
use crate::core_crypto::prelude::*;
use cm_fft64::programmable_bootstrap_cm_lwe_ciphertext;
use itertools::Itertools;
use rayon::iter::{IntoParallelIterator, ParallelIterator};
#[cfg(not(tarpaulin))]
const NB_TESTS: usize = 100;
#[cfg(tarpaulin)]
const NB_TESTS: usize = 1;
#[test]
fn test_cm_pbs_lut_application_test_param() {
test_cm_pbs_lut_application(&CM_PARAM_2_2_MINUS_64)
}
fn test_cm_pbs_lut_application(params: &CmApParams) {
let cm_dimension = params.cm_dimension;
let ciphertext_modulus = params.ciphertext_modulus;
let lwe_noise_distribution = params.lwe_noise_distribution;
let encoding_with_padding = get_encoding_with_padding(ciphertext_modulus);
let glwe_dimension = params.glwe_dimension;
let polynomial_size = params.polynomial_size;
let mut rsc = TestResources::new();
let msg_modulus = 1u64 << params.precision;
let delta = encoding_with_padding / msg_modulus;
let CmBootstrapKeys {
small_lwe_sk,
big_lwe_sk,
bsk,
fbsk,
} = generate_cm_pbs_keys(
params,
&mut rsc.encryption_random_generator,
&mut rsc.secret_random_generator,
);
drop(bsk);
for _ in 0..NB_TESTS {
let random_lut: Vec<u64> = (0..msg_modulus)
.map(|_| random_uint_between(0u64..msg_modulus))
.collect();
let f = |x: u64| random_lut[x as usize];
let accumulator = cm_generate_programmable_bootstrap_glwe_lut(
polynomial_size,
glwe_dimension,
cm_dimension,
msg_modulus.cast_into(),
ciphertext_modulus,
delta,
f,
);
let msgs: Vec<u64> = (0..cm_dimension.0)
.map(|_| random_uint_between(0u64..msg_modulus))
.collect();
let plaintexts =
PlaintextList::from_container(msgs.iter().map(|&m| m * delta).collect_vec());
let ct = allocate_and_encrypt_new_cm_lwe_ciphertext(
&small_lwe_sk,
&plaintexts,
lwe_noise_distribution,
ciphertext_modulus,
&mut rsc.encryption_random_generator,
);
let mut output_ct = CmLweCiphertext::new(
0u64,
fbsk.output_lwe_dimension(),
cm_dimension,
ciphertext_modulus,
);
programmable_bootstrap_cm_lwe_ciphertext(&ct, &mut output_ct, &accumulator, &fbsk);
let decrypted = decrypt_cm_lwe_ciphertext(&big_lwe_sk, &output_ct);
for (decrypted, &input) in decrypted.iter().zip(msgs.iter()) {
let decoded = round_decode(decrypted.0, delta) % msg_modulus;
assert_eq!(decoded, f(input));
}
}
}
#[test]
fn test_cm_pbs_ap_sequence_test_param() {
test_cm_pbs_ap_sequence(&CM_PARAM_2_2_MINUS_64)
}
fn test_cm_pbs_ap_sequence(params: &CmApParams) {
let cm_dimension = params.cm_dimension;
let ciphertext_modulus = params.ciphertext_modulus;
let lwe_noise_distribution = params.lwe_noise_distribution;
let glwe_noise_distribution = params.glwe_noise_distribution;
let encoding_with_padding = get_encoding_with_padding(ciphertext_modulus);
let glwe_dimension = params.glwe_dimension;
let polynomial_size = params.polynomial_size;
let mut rsc = TestResources::new();
let msg_modulus = 1u64 << params.precision;
let delta = encoding_with_padding / msg_modulus;
let f = |x| x;
let accumulator = cm_generate_programmable_bootstrap_glwe_lut(
polynomial_size,
glwe_dimension,
cm_dimension,
msg_modulus.cast_into(),
ciphertext_modulus,
delta,
f,
);
let CmBootstrapKeys {
small_lwe_sk,
big_lwe_sk,
bsk,
fbsk,
} = generate_cm_pbs_keys(
params,
&mut rsc.encryption_random_generator,
&mut rsc.secret_random_generator,
);
drop(bsk);
let cm_lwe_keyswitch_key = allocate_and_generate_new_cm_lwe_keyswitch_key(
&big_lwe_sk,
&small_lwe_sk,
cm_dimension,
params.base_log_ks,
params.level_ks,
lwe_noise_distribution,
ciphertext_modulus,
&mut rsc.encryption_random_generator,
);
let mut ct_big;
let mut ct_small = CmLweCiphertext::new(
0u64,
small_lwe_sk[0].lwe_dimension(),
cm_dimension,
ciphertext_modulus,
);
let max_nb_zeros_n = params.max_nb_zeros_n;
let mut encryptions_of_zero = CmLweCiphertextList::new(
0,
params.lwe_dimension,
cm_dimension,
max_nb_zeros_n,
ciphertext_modulus,
);
let plaintext_list = PlaintextList::new(0, PlaintextCount(cm_dimension.0));
let plaintext_lists: Vec<_> = (0..max_nb_zeros_n.0)
.map(|_| plaintext_list.clone())
.collect();
encrypt_cm_lwe_ciphertext_list(
&small_lwe_sk,
&mut encryptions_of_zero,
&plaintext_lists,
lwe_noise_distribution,
&mut rsc.encryption_random_generator,
);
let log_modulus = polynomial_size.to_blind_rotation_input_modulus_log();
for _ in 0..NB_TESTS {
// Use zero messages so that multiplication by nu does not change the encrypted value
let msgs: Vec<u64> = vec![0u64; cm_dimension.0];
let plaintexts =
PlaintextList::from_container(msgs.iter().map(|&m| m * delta).collect_vec());
ct_big = allocate_and_encrypt_new_cm_lwe_ciphertext(
&big_lwe_sk,
&plaintexts,
glwe_noise_distribution,
ciphertext_modulus,
&mut rsc.encryption_random_generator,
);
// depth to bootstrap non clean cts
for _ in 0..3 {
cm_lwe_ciphertext_cleartext_mul_assign(&mut ct_big, Cleartext(params.nu as u64));
cm_keyswitch_lwe_ciphertext(&cm_lwe_keyswitch_key, &ct_big, &mut ct_small);
improve_lwe_ciphertext_modulus_switch_noise_for_binary_key_cm(
&mut ct_small,
&encryptions_of_zero,
params.r_sigma_factor_n,
params.ms_bound_n,
params.ms_input_variance_n,
log_modulus,
);
programmable_bootstrap_cm_lwe_ciphertext(&ct_small, &mut ct_big, &accumulator, &fbsk);
let decrypted = decrypt_cm_lwe_ciphertext(&big_lwe_sk, &ct_big);
for (decrypted, &input) in decrypted.iter().zip(msgs.iter()) {
let decoded = round_decode(decrypted.0, delta) % msg_modulus;
assert_eq!(decoded, 0);
}
}
}
}
#[test]
fn test_cm_pbs_noise_analysis_test_param() {
test_cm_pbs_noise_analysis(&CM_PARAM_2_2_MINUS_64)
}
fn test_cm_pbs_noise_analysis(params: &CmApParams) {
let cm_dimension = params.cm_dimension;
let ciphertext_modulus = params.ciphertext_modulus;
let small_lwe_noise_distribution = params.lwe_noise_distribution;
let encoding_with_padding = get_encoding_with_padding(ciphertext_modulus);
let glwe_dimension = params.glwe_dimension;
let polynomial_size = params.polynomial_size;
let mut rsc = TestResources::new();
let msg_modulus = 1u64 << params.precision;
let delta = encoding_with_padding / msg_modulus;
let f = |x| x;
let accumulator = cm_generate_programmable_bootstrap_glwe_lut(
polynomial_size,
glwe_dimension,
cm_dimension,
msg_modulus.cast_into(),
ciphertext_modulus,
delta,
f,
);
let CmBootstrapKeys {
small_lwe_sk,
big_lwe_sk,
bsk,
fbsk,
} = generate_cm_pbs_keys(
params,
&mut rsc.encryption_random_generator,
&mut rsc.secret_random_generator,
);
drop(bsk);
let noises: Vec<Vec<f64>> = (0..2000)
.into_par_iter()
.map(move |i| {
let msg = i % msg_modulus;
let mut rsc = TestResources::new();
let plaintexts = PlaintextList::from_container(
(0..cm_dimension.0).map(|_| msg * delta).collect_vec(),
);
let ct = allocate_and_encrypt_new_cm_lwe_ciphertext(
&small_lwe_sk,
&plaintexts,
small_lwe_noise_distribution,
ciphertext_modulus,
&mut rsc.encryption_random_generator,
);
let mut output_ct = CmLweCiphertext::new(
0u64,
fbsk.output_lwe_dimension(),
cm_dimension,
ciphertext_modulus,
);
programmable_bootstrap_cm_lwe_ciphertext(&ct, &mut output_ct, &accumulator, &fbsk);
let decrypted = decrypt_cm_lwe_ciphertext(&big_lwe_sk, &output_ct);
let mut noises = vec![];
for (decrypted, plaintext) in decrypted.iter().zip(plaintexts.iter()) {
let decoded = round_decode(decrypted.0, delta) % msg_modulus;
assert_eq!(f(plaintext.0 / delta), decoded);
let noise = torus_modular_diff(
decrypted.0,
f(plaintext.0 / delta) * delta,
ciphertext_modulus,
);
noises.push(noise);
}
noises
})
.collect();
// Transpose from [[pbs0_slot0, pbs0_slot1, ...], [pbs1_slot0, pbs1_slot1, ...], ...]
// into [[pbs0_slot0, pbs1_slot0, ...], [pbs0_slot1, pbs1_slot1, ...], ...]
// To analyse noise for each slot independently
let noises_separated: Vec<Vec<f64>> = (0..cm_dimension.0)
.map(|i| noises.iter().map(|list| list[i]).collect_vec())
.collect_vec();
let expected_variance = cm_pbs_variance_132_bits_security_gaussian_impl(
params.lwe_dimension.0 as f64,
params.glwe_dimension.0 as f64,
params.polynomial_size.0 as f64,
(1_u64 << params.base_log_bs.0) as f64,
params.level_bs.0 as f64,
2_f64.powi(64),
);
println!("expected_variance: {expected_variance:e}");
for noises in &noises_separated {
let variance = variance(noises);
println!("variance: {:?}", variance.0);
let test_result = normality_test_f64(noises, 0.001);
assert!(test_result.null_hypothesis_is_valid);
println!("p_value normality: {}", test_result.p_value);
// TODO: confidence interval to reconsider
assert!(check_both_ratio_under(expected_variance, variance.0, 1.1));
}
}
pub fn cm_pbs_variance_132_bits_security_gaussian_impl(
input_lwe_dimension: f64,
output_glwe_dimension: f64,
output_polynomial_size: f64,
decomposition_base: f64,
decomposition_level_count: f64,
modulus: f64,
) -> f64 {
input_lwe_dimension
* (2.06537277069845e-33
* decomposition_base.powf(2.0)
* decomposition_level_count
* output_polynomial_size.powf(2.0)
* (output_glwe_dimension + 2.0)
+ decomposition_level_count
* output_polynomial_size
* ((-0.0497829131652661 * output_glwe_dimension * output_polynomial_size
+ 5.31469187675068)
.exp2()
+ 16.0 * modulus.powf(-2.0))
* ((1_f64 / 12.0) * decomposition_base.powf(2.0) + 0.166666666666667)
* (output_glwe_dimension + 2.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

@@ -0,0 +1,3 @@
mod cm_lwe_keyswitch;
mod cm_lwe_packing;
mod cm_lwe_programmable_bootstrapping;

View File

@@ -1,5 +1,7 @@
use crate::core_crypto::algorithms::test::*;
use crate::core_crypto::experimental::prelude::*;
mod cm_lwe_encryption;
mod common_mask;
mod lwe_fast_keyswitch;
mod lwe_stair_keyswitch;

View File

@@ -43,3 +43,11 @@ impl crate::core_crypto::commons::parameters::LweDimension {
LweSecretKeyUnsharedCoefCount(self.0 - shared_coef_count.0)
}
}
/// The number of ciphertexts in an lwe ciphertext list.
#[derive(Copy, Clone, Eq, PartialEq, Debug, Serialize, Deserialize)]
pub struct CmLweCiphertextCount(pub usize);
/// The number of scalar in an LWE mask, or the length of an LWE secret key.
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Serialize, Deserialize)]
pub struct CmDimension(pub usize);

View File

@@ -0,0 +1,210 @@
use crate::core_crypto::commons::parameters::{
DecompositionBaseLog, DecompositionLevelCount, GlweDimension, LweDimension, PolynomialSize,
};
use crate::core_crypto::commons::traits::{Container, IntoContainerOwned, Split};
use crate::core_crypto::experimental::prelude::*;
use crate::core_crypto::fft_impl::fft64::math::fft::{
par_convert_polynomials_list_to_fourier, FftView, FourierPolynomialList,
};
use crate::core_crypto::prelude::{ContiguousEntityContainer, UnsignedTorus};
use aligned_vec::{avec, ABox};
use dyn_stack::PodStack;
use itertools::izip;
use tfhe_fft::c64;
#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(bound(deserialize = "C: IntoContainerOwned"))]
pub struct FourierCmLweBootstrapKey<C: Container<Element = c64>> {
fourier: FourierPolynomialList<C>,
input_lwe_dimension: LweDimension,
glwe_dimension: GlweDimension,
cm_dimension: CmDimension,
decomposition_base_log: DecompositionBaseLog,
decomposition_level_count: DecompositionLevelCount,
}
pub type FourierCmLweBootstrapKeyView<'a> = FourierCmLweBootstrapKey<&'a [c64]>;
pub type FourierCmLweBootstrapKeyMutView<'a> = FourierCmLweBootstrapKey<&'a mut [c64]>;
impl<C: Container<Element = c64>> FourierCmLweBootstrapKey<C> {
pub fn from_container(
data: C,
input_lwe_dimension: LweDimension,
glwe_dimension: GlweDimension,
cm_dimension: CmDimension,
polynomial_size: PolynomialSize,
decomposition_base_log: DecompositionBaseLog,
decomposition_level_count: DecompositionLevelCount,
) -> Self {
assert_eq!(
data.container_len(),
input_lwe_dimension.0
* decomposition_level_count.0
* (glwe_dimension.0 + cm_dimension.0)
* cm_glwe_ciphertext_fourier_size(
glwe_dimension,
cm_dimension,
polynomial_size.to_fourier_polynomial_size()
)
);
Self {
fourier: FourierPolynomialList {
data,
polynomial_size,
},
input_lwe_dimension,
glwe_dimension,
cm_dimension,
decomposition_base_log,
decomposition_level_count,
}
}
pub fn into_cm_ggsw_iter(self) -> impl DoubleEndedIterator<Item = FourierCmGgswCiphertext<C>>
where
C: Split,
{
self.fourier
.data
.split_into(self.input_lwe_dimension.0)
.map(move |slice| {
FourierCmGgswCiphertext::from_container(
slice,
self.glwe_dimension,
self.cm_dimension,
self.fourier.polynomial_size,
self.decomposition_base_log,
self.decomposition_level_count,
)
})
}
pub fn input_lwe_dimension(&self) -> LweDimension {
self.input_lwe_dimension
}
pub fn polynomial_size(&self) -> PolynomialSize {
self.fourier.polynomial_size
}
pub fn glwe_dimension(&self) -> GlweDimension {
self.glwe_dimension
}
pub fn cm_dimension(&self) -> CmDimension {
self.cm_dimension
}
pub fn decomposition_base_log(&self) -> DecompositionBaseLog {
self.decomposition_base_log
}
pub fn decomposition_level_count(&self) -> DecompositionLevelCount {
self.decomposition_level_count
}
pub fn output_lwe_dimension(&self) -> LweDimension {
self.glwe_dimension
.to_equivalent_lwe_dimension(self.polynomial_size())
}
pub fn data(self) -> C {
self.fourier.data
}
pub fn as_view(&self) -> FourierCmLweBootstrapKeyView<'_> {
FourierCmLweBootstrapKeyView {
fourier: FourierPolynomialList {
data: self.fourier.data.as_ref(),
polynomial_size: self.fourier.polynomial_size,
},
input_lwe_dimension: self.input_lwe_dimension,
glwe_dimension: self.glwe_dimension,
cm_dimension: self.cm_dimension,
decomposition_base_log: self.decomposition_base_log,
decomposition_level_count: self.decomposition_level_count,
}
}
pub fn as_mut_view(&mut self) -> FourierCmLweBootstrapKeyMutView<'_>
where
C: AsMut<[c64]>,
{
FourierCmLweBootstrapKeyMutView {
fourier: FourierPolynomialList {
data: self.fourier.data.as_mut(),
polynomial_size: self.fourier.polynomial_size,
},
input_lwe_dimension: self.input_lwe_dimension,
glwe_dimension: self.glwe_dimension,
cm_dimension: self.cm_dimension,
decomposition_base_log: self.decomposition_base_log,
decomposition_level_count: self.decomposition_level_count,
}
}
}
pub type FourierCmLweBootstrapKeyOwned = FourierCmLweBootstrapKey<ABox<[c64]>>;
impl FourierCmLweBootstrapKey<ABox<[c64]>> {
pub fn new(
input_lwe_dimension: LweDimension,
glwe_dimension: GlweDimension,
cm_dimension: CmDimension,
polynomial_size: PolynomialSize,
decomposition_base_log: DecompositionBaseLog,
decomposition_level_count: DecompositionLevelCount,
) -> Self {
let boxed = avec![
c64::default();
input_lwe_dimension.0
* decomposition_level_count.0
* (glwe_dimension.0 + cm_dimension.0)
* cm_glwe_ciphertext_fourier_size(
glwe_dimension,
cm_dimension,
polynomial_size.to_fourier_polynomial_size()
)
]
.into_boxed_slice();
FourierCmLweBootstrapKey::from_container(
boxed,
input_lwe_dimension,
glwe_dimension,
cm_dimension,
polynomial_size,
decomposition_base_log,
decomposition_level_count,
)
}
}
impl FourierCmLweBootstrapKeyMutView<'_> {
pub fn fill_with_forward_fourier<Scalar: UnsignedTorus>(
mut self,
coef_bsk: CmLweBootstrapKey<&'_ [Scalar]>,
fft: FftView<'_>,
stack: &mut PodStack,
) {
for (fourier_ggsw, standard_ggsw) in
izip!(self.as_mut_view().into_cm_ggsw_iter(), coef_bsk.iter())
{
fourier_ggsw.fill_with_forward_fourier(standard_ggsw, fft, stack);
}
}
pub fn par_fill_with_forward_fourier<Scalar: UnsignedTorus>(
self,
coef_bsk: CmLweBootstrapKey<&'_ [Scalar]>,
fft: FftView<'_>,
) {
let polynomial_size = self.polynomial_size();
par_convert_polynomials_list_to_fourier(
self.data(),
coef_bsk.into_container(),
polynomial_size,
fft,
);
}
}

View File

@@ -0,0 +1,489 @@
use crate::core_crypto::commons::math::decomposition::DecompositionLevel;
use crate::core_crypto::commons::math::torus::UnsignedTorus;
use crate::core_crypto::commons::parameters::{
DecompositionBaseLog, DecompositionLevelCount, GlweDimension, PolynomialSize,
};
use crate::core_crypto::commons::traits::{Container, IntoContainerOwned, Split};
use crate::core_crypto::experimental::prelude::*;
use crate::core_crypto::fft_impl::fft64::math;
use crate::core_crypto::prelude::ContiguousEntityContainer;
use aligned_vec::{avec, ABox};
use dyn_stack::PodStack;
use itertools::izip;
use math::fft::{FftView, FourierPolynomialList};
use math::polynomial::FourierPolynomialMutView;
use tfhe_fft::c64;
#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(bound(deserialize = "C: IntoContainerOwned"))]
pub struct FourierCmGgswCiphertext<C: Container<Element = c64>> {
fourier: FourierPolynomialList<C>,
glwe_dimension: GlweDimension,
cm_dimension: CmDimension,
decomposition_base_log: DecompositionBaseLog,
decomposition_level_count: DecompositionLevelCount,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct FourierCmGgswLevelMatrix<C: Container<Element = c64>> {
data: C,
glwe_dimension: GlweDimension,
cm_dimension: CmDimension,
polynomial_size: PolynomialSize,
decomposition_level: DecompositionLevel,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct FourierCmGgswLevelRow<C: Container<Element = c64>> {
data: C,
glwe_dimension: GlweDimension,
cm_dimension: CmDimension,
polynomial_size: PolynomialSize,
decomposition_level: DecompositionLevel,
}
pub type FourierCmGgswCiphertextView<'a> = FourierCmGgswCiphertext<&'a [c64]>;
pub type FourierCmGgswCiphertextMutView<'a> = FourierCmGgswCiphertext<&'a mut [c64]>;
pub type FourierCmGgswLevelMatrixView<'a> = FourierCmGgswLevelMatrix<&'a [c64]>;
pub type FourierCmGgswLevelMatrixMutView<'a> = FourierCmGgswLevelMatrix<&'a mut [c64]>;
pub type FourierCmGgswLevelRowView<'a> = FourierCmGgswLevelRow<&'a [c64]>;
pub type FourierCmGgswLevelRowMutView<'a> = FourierCmGgswLevelRow<&'a mut [c64]>;
impl<C: Container<Element = c64>> FourierCmGgswCiphertext<C> {
pub fn from_container(
data: C,
glwe_dimension: GlweDimension,
cm_dimension: CmDimension,
polynomial_size: PolynomialSize,
decomposition_base_log: DecompositionBaseLog,
decomposition_level_count: DecompositionLevelCount,
) -> Self {
assert_eq!(
data.container_len(),
(glwe_dimension.0 + cm_dimension.0)
* decomposition_level_count.0
* cm_glwe_ciphertext_fourier_size(
glwe_dimension,
cm_dimension,
polynomial_size.to_fourier_polynomial_size()
)
);
Self {
fourier: FourierPolynomialList {
data,
polynomial_size,
},
glwe_dimension,
cm_dimension,
decomposition_base_log,
decomposition_level_count,
}
}
pub fn polynomial_size(&self) -> PolynomialSize {
self.fourier.polynomial_size
}
pub fn glwe_dimension(&self) -> GlweDimension {
self.glwe_dimension
}
pub fn cm_dimension(&self) -> CmDimension {
self.cm_dimension
}
pub fn decomposition_base_log(&self) -> DecompositionBaseLog {
self.decomposition_base_log
}
pub fn decomposition_level_count(&self) -> DecompositionLevelCount {
self.decomposition_level_count
}
pub fn data(self) -> C {
self.fourier.data
}
pub fn as_view(&self) -> FourierCmGgswCiphertextView<'_>
where
C: AsRef<[c64]>,
{
FourierCmGgswCiphertextView {
fourier: FourierPolynomialList {
data: self.fourier.data.as_ref(),
polynomial_size: self.fourier.polynomial_size,
},
glwe_dimension: self.glwe_dimension,
cm_dimension: self.cm_dimension,
decomposition_base_log: self.decomposition_base_log,
decomposition_level_count: self.decomposition_level_count,
}
}
pub fn as_mut_view(&mut self) -> FourierCmGgswCiphertextMutView<'_>
where
C: AsMut<[c64]>,
{
FourierCmGgswCiphertextMutView {
fourier: FourierPolynomialList {
data: self.fourier.data.as_mut(),
polynomial_size: self.fourier.polynomial_size,
},
glwe_dimension: self.glwe_dimension,
cm_dimension: self.cm_dimension,
decomposition_base_log: self.decomposition_base_log,
decomposition_level_count: self.decomposition_level_count,
}
}
}
impl<C: Container<Element = c64>> FourierCmGgswLevelMatrix<C> {
pub fn new(
data: C,
glwe_dimension: GlweDimension,
cm_dimension: CmDimension,
polynomial_size: PolynomialSize,
decomposition_level: DecompositionLevel,
) -> Self {
assert_eq!(
data.container_len(),
cm_fourier_ggsw_level_matrix_size(
glwe_dimension,
cm_dimension,
polynomial_size.to_fourier_polynomial_size()
),
);
Self {
data,
glwe_dimension,
cm_dimension,
polynomial_size,
decomposition_level,
}
}
pub fn into_rows(self) -> impl DoubleEndedIterator<Item = FourierCmGgswLevelRow<C>>
where
C: Split,
{
let row_count = self.row_count();
self.data
.split_into(row_count)
.map(move |slice| FourierCmGgswLevelRow {
data: slice,
polynomial_size: self.polynomial_size,
glwe_dimension: self.glwe_dimension,
cm_dimension: self.cm_dimension,
decomposition_level: self.decomposition_level,
})
}
pub fn polynomial_size(&self) -> PolynomialSize {
self.polynomial_size
}
pub fn glwe_dimension(&self) -> GlweDimension {
self.glwe_dimension
}
pub fn cm_dimension(&self) -> CmDimension {
self.cm_dimension
}
pub fn row_count(&self) -> usize {
self.glwe_dimension.0 + self.cm_dimension.0
}
pub fn decomposition_level(&self) -> DecompositionLevel {
self.decomposition_level
}
pub fn data(self) -> C {
self.data
}
}
impl<C: Container<Element = c64>> FourierCmGgswLevelRow<C> {
pub fn new(
data: C,
glwe_dimension: GlweDimension,
cm_dimension: CmDimension,
polynomial_size: PolynomialSize,
decomposition_level: DecompositionLevel,
) -> Self {
assert_eq!(
data.container_len(),
cm_glwe_ciphertext_fourier_size(
glwe_dimension,
cm_dimension,
polynomial_size.to_fourier_polynomial_size()
)
);
Self {
data,
glwe_dimension,
cm_dimension,
polynomial_size,
decomposition_level,
}
}
pub fn polynomial_size(&self) -> PolynomialSize {
self.polynomial_size
}
pub fn glwe_dimension(&self) -> GlweDimension {
self.glwe_dimension
}
pub fn cm_dimension(&self) -> CmDimension {
self.cm_dimension
}
pub fn decomposition_level(&self) -> DecompositionLevel {
self.decomposition_level
}
pub fn data(self) -> C {
self.data
}
}
impl<'a> FourierCmGgswCiphertextView<'a> {
pub fn into_levels(self) -> impl DoubleEndedIterator<Item = FourierCmGgswLevelMatrixView<'a>> {
let decomposition_level_count = self.decomposition_level_count.0;
self.fourier
.data
.split_into(decomposition_level_count)
.enumerate()
.map(move |(i, slice)| {
FourierCmGgswLevelMatrixView::new(
slice,
self.glwe_dimension,
self.cm_dimension,
self.fourier.polynomial_size,
DecompositionLevel(decomposition_level_count - i),
)
})
}
}
impl FourierCmGgswCiphertextMutView<'_> {
pub fn fill_with_forward_fourier<Scalar: UnsignedTorus>(
self,
coef_ggsw: CmGgswCiphertextView<'_, Scalar>,
fft: FftView<'_>,
stack: &mut PodStack,
) {
debug_assert_eq!(coef_ggsw.polynomial_size(), self.polynomial_size());
let fourier_poly_size = coef_ggsw.polynomial_size().to_fourier_polynomial_size().0;
for (fourier_poly, coef_poly) in izip!(
self.data().into_chunks(fourier_poly_size),
coef_ggsw.as_polynomial_list().iter()
) {
fft.forward_as_torus(
FourierPolynomialMutView { data: fourier_poly },
coef_poly,
stack,
);
}
}
}
impl FourierCmGgswCiphertext<ABox<[c64]>> {
pub fn new(
glwe_dimension: GlweDimension,
cm_dimension: CmDimension,
polynomial_size: PolynomialSize,
decomposition_base_log: DecompositionBaseLog,
decomposition_level_count: DecompositionLevelCount,
) -> Self {
let boxed = avec![
c64::default();
polynomial_size.to_fourier_polynomial_size().0
* (glwe_dimension.0+cm_dimension.0)
* (glwe_dimension.0+cm_dimension.0)
* decomposition_level_count.0
]
.into_boxed_slice();
FourierCmGgswCiphertext::from_container(
boxed,
glwe_dimension,
cm_dimension,
polynomial_size,
decomposition_base_log,
decomposition_level_count,
)
}
}
#[derive(PartialEq, Eq, Debug, Clone, Copy)]
pub struct FourierCmGgswCiphertextList<C: Container<Element = c64>> {
fourier: FourierPolynomialList<C>,
cm_dimension: CmDimension,
glwe_dimension: GlweDimension,
decomposition_level_count: DecompositionLevelCount,
decomposition_base_log: DecompositionBaseLog,
count: usize,
}
pub type FourierCmGgswCiphertextListView<'a> = FourierCmGgswCiphertextList<&'a [c64]>;
pub type FourierCmGgswCiphertextListMutView<'a> = FourierCmGgswCiphertextList<&'a mut [c64]>;
impl<C: Container<Element = c64>> FourierCmGgswCiphertextList<C> {
pub fn new(
data: C,
count: usize,
glwe_dimension: GlweDimension,
cm_dimension: CmDimension,
polynomial_size: PolynomialSize,
decomposition_base_log: DecompositionBaseLog,
decomposition_level_count: DecompositionLevelCount,
) -> Self {
assert_eq!(
data.container_len(),
count
* decomposition_level_count.0
* (glwe_dimension.0 + cm_dimension.0)
* cm_glwe_ciphertext_fourier_size(
glwe_dimension,
cm_dimension,
polynomial_size.to_fourier_polynomial_size()
)
);
Self {
fourier: FourierPolynomialList {
data,
polynomial_size,
},
count,
glwe_dimension,
cm_dimension,
decomposition_level_count,
decomposition_base_log,
}
}
pub fn data(self) -> C {
self.fourier.data
}
pub fn polynomial_size(&self) -> PolynomialSize {
self.fourier.polynomial_size
}
pub fn count(&self) -> usize {
self.count
}
pub fn glwe_dimension(&self) -> GlweDimension {
self.glwe_dimension
}
pub fn cm_dimension(&self) -> CmDimension {
self.cm_dimension
}
pub fn decomposition_level_count(&self) -> DecompositionLevelCount {
self.decomposition_level_count
}
pub fn decomposition_base_log(&self) -> DecompositionBaseLog {
self.decomposition_base_log
}
pub fn as_view(&self) -> FourierCmGgswCiphertextListView<'_> {
let fourier = FourierPolynomialList {
data: self.fourier.data.as_ref(),
polynomial_size: self.fourier.polynomial_size,
};
FourierCmGgswCiphertextListView {
fourier,
count: self.count,
glwe_dimension: self.glwe_dimension,
cm_dimension: self.cm_dimension,
decomposition_level_count: self.decomposition_level_count,
decomposition_base_log: self.decomposition_base_log,
}
}
pub fn as_mut_view(&mut self) -> FourierCmGgswCiphertextListMutView<'_>
where
C: AsMut<[c64]>,
{
let fourier = FourierPolynomialList {
data: self.fourier.data.as_mut(),
polynomial_size: self.fourier.polynomial_size,
};
FourierCmGgswCiphertextListMutView {
fourier,
count: self.count,
glwe_dimension: self.glwe_dimension,
cm_dimension: self.cm_dimension,
decomposition_level_count: self.decomposition_level_count,
decomposition_base_log: self.decomposition_base_log,
}
}
pub fn into_ggsw_iter(self) -> impl DoubleEndedIterator<Item = FourierCmGgswCiphertext<C>>
where
C: Split,
{
self.fourier.data.split_into(self.count).map(move |slice| {
FourierCmGgswCiphertext::from_container(
slice,
self.glwe_dimension,
self.cm_dimension,
self.fourier.polynomial_size,
self.decomposition_base_log,
self.decomposition_level_count,
)
})
}
pub fn split_at(self, mid: usize) -> (Self, Self)
where
C: Split,
{
let polynomial_size = self.fourier.polynomial_size;
let glwe_dimension = self.glwe_dimension;
let cm_dimension = self.cm_dimension;
let decomposition_level_count = self.decomposition_level_count;
let decomposition_base_log = self.decomposition_base_log;
let (left, right) = self.fourier.data.split_at(
mid * (glwe_dimension.0 + cm_dimension.0)
* decomposition_level_count.0
* cm_glwe_ciphertext_fourier_size(
glwe_dimension,
cm_dimension,
polynomial_size.to_fourier_polynomial_size(),
),
);
(
Self::new(
left,
mid,
glwe_dimension,
cm_dimension,
polynomial_size,
decomposition_base_log,
decomposition_level_count,
),
Self::new(
right,
self.count - mid,
glwe_dimension,
cm_dimension,
polynomial_size,
decomposition_base_log,
decomposition_level_count,
),
)
}
}

View File

@@ -0,0 +1,607 @@
//! Module containing the definition of the CmGgswCiphertext.
use super::cm_glwe_ciphertext::cm_glwe_ciphertext_encryption_mask_sample_count;
use super::cm_glwe_ciphertext_list::{CmGlweCiphertextListMutView, CmGlweCiphertextListView};
use crate::core_crypto::commons::generators::EncryptionRandomGeneratorForkConfig;
use crate::core_crypto::commons::math::random::{Distribution, RandomGenerable};
use crate::core_crypto::commons::parameters::*;
use crate::core_crypto::commons::traits::*;
use crate::core_crypto::experimental::prelude::*;
use crate::core_crypto::prelude::{
glwe_ciphertext_encryption_mask_sample_count, glwe_ciphertext_encryption_noise_sample_count,
PolynomialListMutView, PolynomialListView,
};
#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct CmGgswCiphertext<C: Container>
where
C::Element: UnsignedInteger,
{
data: C,
glwe_dimension: GlweDimension,
cm_dimension: CmDimension,
polynomial_size: PolynomialSize,
decomp_base_log: DecompositionBaseLog,
ciphertext_modulus: CiphertextModulus<C::Element>,
}
impl<T: UnsignedInteger, C: Container<Element = T>> AsRef<[T]> for CmGgswCiphertext<C> {
fn as_ref(&self) -> &[T] {
self.data.as_ref()
}
}
impl<T: UnsignedInteger, C: ContainerMut<Element = T>> AsMut<[T]> for CmGgswCiphertext<C> {
fn as_mut(&mut self) -> &mut [T] {
self.data.as_mut()
}
}
pub fn cm_ggsw_ciphertext_size(
glwe_dimension: GlweDimension,
cm_dimension: CmDimension,
polynomial_size: PolynomialSize,
decomp_level_count: DecompositionLevelCount,
) -> usize {
decomp_level_count.0 * cm_ggsw_level_matrix_size(glwe_dimension, cm_dimension, polynomial_size)
}
pub fn cm_ggsw_level_matrix_size(
glwe_dimension: GlweDimension,
cm_dimension: CmDimension,
polynomial_size: PolynomialSize,
) -> usize {
(glwe_dimension.0 + cm_dimension.0)
* cm_glwe_ciphertext_size(glwe_dimension, cm_dimension, polynomial_size)
}
pub fn fourier_cm_ggsw_ciphertext_size(
glwe_dimension: GlweDimension,
cm_dimension: CmDimension,
fourier_polynomial_size: FourierPolynomialSize,
decomp_level_count: DecompositionLevelCount,
) -> usize {
decomp_level_count.0
* fourier_cm_ggsw_level_matrix_size(glwe_dimension, cm_dimension, fourier_polynomial_size)
}
pub fn fourier_cm_ggsw_level_matrix_size(
glwe_dimension: GlweDimension,
cm_dimension: CmDimension,
fourier_polynomial_size: FourierPolynomialSize,
) -> usize {
(glwe_dimension.0 + cm_dimension.0)
* cm_glwe_ciphertext_fourier_size(glwe_dimension, cm_dimension, fourier_polynomial_size)
}
pub fn cm_ggsw_ciphertext_encryption_mask_sample_count(
glwe_dimension: GlweDimension,
cm_dimension: CmDimension,
polynomial_size: PolynomialSize,
decomp_level_count: DecompositionLevelCount,
) -> EncryptionMaskSampleCount {
decomp_level_count.0
* cm_ggsw_level_matrix_encryption_mask_sample_count(
glwe_dimension,
cm_dimension,
polynomial_size,
)
}
pub fn cm_ggsw_level_matrix_encryption_mask_sample_count(
glwe_dimension: GlweDimension,
cm_dimension: CmDimension,
polynomial_size: PolynomialSize,
) -> EncryptionMaskSampleCount {
(glwe_dimension.0 + cm_dimension.0)
* cm_glwe_ciphertext_encryption_mask_sample_count(glwe_dimension, polynomial_size)
}
pub fn cm_ggsw_ciphertext_encryption_noise_sample_count(
glwe_dimension: GlweDimension,
cm_dimension: CmDimension,
polynomial_size: PolynomialSize,
decomp_level_count: DecompositionLevelCount,
) -> EncryptionNoiseSampleCount {
decomp_level_count.0
* cm_ggsw_level_matrix_encryption_noise_sample_count(
glwe_dimension,
cm_dimension,
polynomial_size,
)
}
pub fn cm_ggsw_level_matrix_encryption_noise_sample_count(
glwe_dimension: GlweDimension,
cm_dimension: CmDimension,
polynomial_size: PolynomialSize,
) -> EncryptionNoiseSampleCount {
cm_dimension.0
* (glwe_dimension.0 + cm_dimension.0)
* glwe_ciphertext_encryption_noise_sample_count(polynomial_size)
}
pub fn cm_ggsw_ciphertext_encryption_fork_config<Scalar, MaskDistribution, NoiseDistribution>(
glwe_dimension: GlweDimension,
cm_dimension: CmDimension,
polynomial_size: PolynomialSize,
decomposition_level_count: DecompositionLevelCount,
mask_distribution: MaskDistribution,
noise_distribution: NoiseDistribution,
ciphertext_modulus: CiphertextModulus<Scalar>,
) -> EncryptionRandomGeneratorForkConfig
where
Scalar: UnsignedInteger
+ RandomGenerable<MaskDistribution, CustomModulus = Scalar>
+ RandomGenerable<NoiseDistribution, CustomModulus = Scalar>,
MaskDistribution: Distribution,
NoiseDistribution: Distribution,
{
let ggsw_level_matrix_mask_sample_count = cm_ggsw_level_matrix_encryption_mask_sample_count(
glwe_dimension,
cm_dimension,
polynomial_size,
);
let ggsw_level_matrix_noise_sample_count = cm_ggsw_level_matrix_encryption_noise_sample_count(
glwe_dimension,
cm_dimension,
polynomial_size,
);
let modulus = ciphertext_modulus.get_custom_modulus_as_optional_scalar();
EncryptionRandomGeneratorForkConfig::new(
decomposition_level_count.0,
ggsw_level_matrix_mask_sample_count,
mask_distribution,
ggsw_level_matrix_noise_sample_count,
noise_distribution,
modulus,
)
}
pub fn cm_ggsw_level_matrix_encryption_fork_config<Scalar, MaskDistribution, NoiseDistribution>(
glwe_dimension: GlweDimension,
cm_dimension: CmDimension,
polynomial_size: PolynomialSize,
mask_distribution: MaskDistribution,
noise_distribution: NoiseDistribution,
ciphertext_modulus: CiphertextModulus<Scalar>,
) -> EncryptionRandomGeneratorForkConfig
where
Scalar: UnsignedInteger
+ RandomGenerable<MaskDistribution, CustomModulus = Scalar>
+ RandomGenerable<NoiseDistribution, CustomModulus = Scalar>,
MaskDistribution: Distribution,
NoiseDistribution: Distribution,
{
let glwe_ciphertext_mask_sample_count =
glwe_ciphertext_encryption_mask_sample_count(glwe_dimension, polynomial_size);
let glwe_ciphertext_noise_sample_count =
glwe_ciphertext_encryption_noise_sample_count(polynomial_size);
let modulus = ciphertext_modulus.get_custom_modulus_as_optional_scalar();
EncryptionRandomGeneratorForkConfig::new(
glwe_dimension.0 + cm_dimension.0,
glwe_ciphertext_mask_sample_count,
mask_distribution,
glwe_ciphertext_noise_sample_count,
noise_distribution,
modulus,
)
}
impl<Scalar: UnsignedInteger, C: Container<Element = Scalar>> CmGgswCiphertext<C> {
pub fn from_container(
container: C,
glwe_dimension: GlweDimension,
cm_dimension: CmDimension,
polynomial_size: PolynomialSize,
decomp_base_log: DecompositionBaseLog,
ciphertext_modulus: CiphertextModulus<C::Element>,
) -> Self {
assert!(
container.container_len() > 0,
"Got an empty container to create a CmGgswCiphertext"
);
let matrix_size = cm_ggsw_level_matrix_size(glwe_dimension, cm_dimension, polynomial_size);
assert!(
container.container_len().is_multiple_of(matrix_size),
"The provided container length is not valid. \
It needs to be dividable by glwe_dimension * glwe_dimension * polynomial_size: {}. \
Got container length: {} and glwe_dimension: {glwe_dimension:?}, \
cm_dimension: {cm_dimension:?}, polynomial_size: {polynomial_size:?}.",
matrix_size,
container.container_len()
);
Self {
data: container,
glwe_dimension,
cm_dimension,
polynomial_size,
decomp_base_log,
ciphertext_modulus,
}
}
pub fn polynomial_size(&self) -> PolynomialSize {
self.polynomial_size
}
pub fn glwe_dimension(&self) -> GlweDimension {
self.glwe_dimension
}
pub fn cm_dimension(&self) -> CmDimension {
self.cm_dimension
}
pub fn decomposition_base_log(&self) -> DecompositionBaseLog {
self.decomp_base_log
}
pub fn decomposition_level_count(&self) -> DecompositionLevelCount {
DecompositionLevelCount(self.data.container_len() / self.ggsw_level_matrix_size())
}
pub fn ciphertext_modulus(&self) -> CiphertextModulus<C::Element> {
self.ciphertext_modulus
}
pub fn ggsw_level_matrix_size(&self) -> usize {
// GlweDimension GlweCiphertext(glwe_dimension,cm_dimension,polynomial_size) per level
cm_ggsw_level_matrix_size(self.glwe_dimension, self.cm_dimension, self.polynomial_size)
}
pub fn as_polynomial_list(&self) -> PolynomialListView<'_, Scalar> {
PolynomialListView::from_container(self.as_ref(), self.polynomial_size)
}
pub fn as_cm_glwe_list(&self) -> CmGlweCiphertextListView<'_, Scalar> {
CmGlweCiphertextListView::from_container(
self.as_ref(),
self.glwe_dimension,
self.cm_dimension,
self.polynomial_size,
self.ciphertext_modulus,
)
}
pub fn as_view(&self) -> CmGgswCiphertextView<'_, Scalar> {
CmGgswCiphertextView::from_container(
self.as_ref(),
self.glwe_dimension(),
self.cm_dimension,
self.polynomial_size(),
self.decomposition_base_log(),
self.ciphertext_modulus(),
)
}
pub fn into_container(self) -> C {
self.data
}
pub fn encryption_fork_config<MaskDistribution, NoiseDistribution>(
&self,
mask_distribution: MaskDistribution,
noise_distribution: NoiseDistribution,
) -> EncryptionRandomGeneratorForkConfig
where
MaskDistribution: Distribution,
NoiseDistribution: Distribution,
Scalar: RandomGenerable<MaskDistribution, CustomModulus = Scalar>
+ RandomGenerable<NoiseDistribution, CustomModulus = Scalar>,
{
cm_ggsw_ciphertext_encryption_fork_config(
self.glwe_dimension(),
self.cm_dimension,
self.polynomial_size(),
self.decomposition_level_count(),
mask_distribution,
noise_distribution,
self.ciphertext_modulus(),
)
}
}
impl<Scalar: UnsignedInteger, C: ContainerMut<Element = Scalar>> CmGgswCiphertext<C> {
pub fn as_mut_polynomial_list(&mut self) -> PolynomialListMutView<'_, Scalar> {
let polynomial_size = self.polynomial_size;
PolynomialListMutView::from_container(self.as_mut(), polynomial_size)
}
pub fn as_mut_cm_glwe_list(&mut self) -> CmGlweCiphertextListMutView<'_, Scalar> {
let polynomial_size = self.polynomial_size;
let glwe_dimension = self.glwe_dimension;
let cm_dimension = self.cm_dimension;
let ciphertext_modulus: CiphertextModulus<Scalar> = self.ciphertext_modulus;
CmGlweCiphertextListMutView::from_container(
self.as_mut(),
glwe_dimension,
cm_dimension,
polynomial_size,
ciphertext_modulus,
)
}
pub fn as_mut_view(&mut self) -> CmGgswCiphertextMutView<'_, Scalar> {
let glwe_dimension = self.glwe_dimension;
let cm_dimension = self.cm_dimension;
let polynomial_size = self.polynomial_size();
let decomp_base_log = self.decomposition_base_log();
let ciphertext_modulus = self.ciphertext_modulus;
CmGgswCiphertextMutView::from_container(
self.as_mut(),
glwe_dimension,
cm_dimension,
polynomial_size,
decomp_base_log,
ciphertext_modulus,
)
}
}
pub type CmGgswCiphertextOwned<Scalar> = CmGgswCiphertext<Vec<Scalar>>;
pub type CmGgswCiphertextView<'data, Scalar> = CmGgswCiphertext<&'data [Scalar]>;
pub type CmGgswCiphertextMutView<'data, Scalar> = CmGgswCiphertext<&'data mut [Scalar]>;
impl<Scalar: UnsignedInteger> CmGgswCiphertextOwned<Scalar> {
pub fn new(
fill_with: Scalar,
glwe_dimension: GlweDimension,
cm_dimension: CmDimension,
polynomial_size: PolynomialSize,
decomp_base_log: DecompositionBaseLog,
decomp_level_count: DecompositionLevelCount,
ciphertext_modulus: CiphertextModulus<Scalar>,
) -> Self {
Self::from_container(
vec![
fill_with;
cm_ggsw_ciphertext_size(
glwe_dimension,
cm_dimension,
polynomial_size,
decomp_level_count
)
],
glwe_dimension,
cm_dimension,
polynomial_size,
decomp_base_log,
ciphertext_modulus,
)
}
}
#[derive(Clone, Copy)]
pub struct CmGgswCiphertextCreationMetadata<Scalar: UnsignedInteger> {
pub glwe_dimension: GlweDimension,
pub cm_dimension: CmDimension,
pub polynomial_size: PolynomialSize,
pub decomp_base_log: DecompositionBaseLog,
pub ciphertext_modulus: CiphertextModulus<Scalar>,
}
impl<Scalar: UnsignedInteger, C: Container<Element = Scalar>> CreateFrom<C>
for CmGgswCiphertext<C>
{
type Metadata = CmGgswCiphertextCreationMetadata<Scalar>;
#[inline]
fn create_from(from: C, meta: Self::Metadata) -> Self {
let CmGgswCiphertextCreationMetadata {
glwe_dimension,
cm_dimension,
polynomial_size,
decomp_base_log,
ciphertext_modulus,
} = meta;
Self::from_container(
from,
glwe_dimension,
cm_dimension,
polynomial_size,
decomp_base_log,
ciphertext_modulus,
)
}
}
pub struct CmGgswLevelMatrix<C: Container>
where
C::Element: UnsignedInteger,
{
data: C,
glwe_dimension: GlweDimension,
cm_dimension: CmDimension,
polynomial_size: PolynomialSize,
ciphertext_modulus: CiphertextModulus<C::Element>,
}
impl<Scalar: UnsignedInteger, C: Container<Element = Scalar>> CmGgswLevelMatrix<C> {
pub fn from_container(
container: C,
glwe_dimension: GlweDimension,
cm_dimension: CmDimension,
polynomial_size: PolynomialSize,
ciphertext_modulus: CiphertextModulus<C::Element>,
) -> Self {
assert!(
container.container_len()
== cm_ggsw_level_matrix_size(glwe_dimension, cm_dimension, polynomial_size),
"The provided container length is not valid. \
Expected length of {} (glwe_dimension * glwe_dimension * polynomial_size), got {}",
cm_ggsw_level_matrix_size(glwe_dimension, cm_dimension, polynomial_size),
container.container_len(),
);
Self {
data: container,
glwe_dimension,
cm_dimension,
polynomial_size,
ciphertext_modulus,
}
}
pub fn glwe_dimension(&self) -> GlweDimension {
self.glwe_dimension
}
pub fn cm_dimension(&self) -> CmDimension {
self.cm_dimension
}
pub fn polynomial_size(&self) -> PolynomialSize {
self.polynomial_size
}
pub fn ciphertext_modulus(&self) -> CiphertextModulus<C::Element> {
self.ciphertext_modulus
}
pub fn as_cm_glwe_list(&self) -> CmGlweCiphertextListView<'_, C::Element> {
CmGlweCiphertextListView::from_container(
self.data.as_ref(),
self.glwe_dimension,
self.cm_dimension,
self.polynomial_size,
self.ciphertext_modulus,
)
}
pub fn encryption_fork_config<MaskDistribution, NoiseDistribution>(
&self,
mask_distribution: MaskDistribution,
noise_distribution: NoiseDistribution,
) -> EncryptionRandomGeneratorForkConfig
where
MaskDistribution: Distribution,
NoiseDistribution: Distribution,
Scalar: RandomGenerable<MaskDistribution, CustomModulus = Scalar>
+ RandomGenerable<NoiseDistribution, CustomModulus = Scalar>,
{
cm_ggsw_level_matrix_encryption_fork_config(
self.glwe_dimension,
self.cm_dimension,
self.polynomial_size(),
mask_distribution,
noise_distribution,
self.ciphertext_modulus(),
)
}
}
impl<Scalar: UnsignedInteger, C: ContainerMut<Element = Scalar>> CmGgswLevelMatrix<C> {
pub fn as_mut_cm_glwe_list(&mut self) -> CmGlweCiphertextListMutView<'_, C::Element> {
CmGlweCiphertextListMutView::from_container(
self.data.as_mut(),
self.glwe_dimension,
self.cm_dimension,
self.polynomial_size,
self.ciphertext_modulus,
)
}
}
pub fn cm_fourier_ggsw_level_matrix_size(
glwe_dimension: GlweDimension,
cm_dimension: CmDimension,
fourier_polynomial_size: FourierPolynomialSize,
) -> usize {
(glwe_dimension.0 + cm_dimension.0)
* cm_glwe_ciphertext_fourier_size(glwe_dimension, cm_dimension, fourier_polynomial_size)
}
#[derive(Clone, Copy)]
pub struct CmGgswLevelMatrixCreationMetadata<Scalar: UnsignedInteger> {
pub glwe_dimension: GlweDimension,
pub cm_dimension: CmDimension,
pub polynomial_size: PolynomialSize,
pub ciphertext_modulus: CiphertextModulus<Scalar>,
}
impl<Scalar: UnsignedInteger, C: Container<Element = Scalar>> CreateFrom<C>
for CmGgswLevelMatrix<C>
{
type Metadata = CmGgswLevelMatrixCreationMetadata<C::Element>;
#[inline]
fn create_from(from: C, meta: Self::Metadata) -> Self {
let CmGgswLevelMatrixCreationMetadata {
glwe_dimension,
cm_dimension,
polynomial_size,
ciphertext_modulus,
} = meta;
Self::from_container(
from,
glwe_dimension,
cm_dimension,
polynomial_size,
ciphertext_modulus,
)
}
}
impl<Scalar: UnsignedInteger, C: Container<Element = Scalar>> ContiguousEntityContainer
for CmGgswCiphertext<C>
{
type Element = C::Element;
type EntityViewMetadata = CmGgswLevelMatrixCreationMetadata<Self::Element>;
type EntityView<'this>
= CmGgswLevelMatrix<&'this [Self::Element]>
where
Self: 'this;
type SelfViewMetadata = ();
type SelfView<'this>
= DummyCreateFrom
where
Self: 'this;
fn get_entity_view_creation_metadata(&self) -> Self::EntityViewMetadata {
CmGgswLevelMatrixCreationMetadata {
glwe_dimension: self.glwe_dimension,
cm_dimension: self.cm_dimension,
polynomial_size: self.polynomial_size,
ciphertext_modulus: self.ciphertext_modulus,
}
}
fn get_entity_view_pod_size(&self) -> usize {
self.ggsw_level_matrix_size()
}
fn get_self_view_creation_metadata(&self) -> Self::SelfViewMetadata {
unimplemented!(
"This function is not supported for CmGgswCiphertext. \
At the moment it does not make sense to return 'sub' CmGgswCiphertext."
)
}
}
impl<Scalar: UnsignedInteger, C: ContainerMut<Element = Scalar>> ContiguousEntityContainerMut
for CmGgswCiphertext<C>
{
type EntityMutView<'this>
= CmGgswLevelMatrix<&'this mut [Self::Element]>
where
Self: 'this;
type SelfMutView<'this>
= DummyCreateFrom
where
Self: 'this;
}

View File

@@ -0,0 +1,351 @@
//! Module containing the definition of the GgswCiphertextList.
use crate::core_crypto::commons::generators::EncryptionRandomGeneratorForkConfig;
use crate::core_crypto::commons::math::random::{Distribution, RandomGenerable};
use crate::core_crypto::commons::parameters::*;
use crate::core_crypto::commons::traits::*;
use crate::core_crypto::entities::*;
use crate::core_crypto::experimental::prelude::*;
use cm_ggsw_ciphertext::{
cm_ggsw_ciphertext_encryption_mask_sample_count,
cm_ggsw_ciphertext_encryption_noise_sample_count, cm_ggsw_ciphertext_size,
CmGgswCiphertextCreationMetadata, CmGgswCiphertextMutView, CmGgswCiphertextView,
};
#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct CmGgswCiphertextList<C: Container>
where
C::Element: UnsignedInteger,
{
data: C,
glwe_dimension: GlweDimension,
cm_dimension: CmDimension,
polynomial_size: PolynomialSize,
decomp_base_log: DecompositionBaseLog,
decomp_level_count: DecompositionLevelCount,
ciphertext_modulus: CiphertextModulus<C::Element>,
}
impl<T: UnsignedInteger, C: Container<Element = T>> AsRef<[T]> for CmGgswCiphertextList<C> {
fn as_ref(&self) -> &[T] {
self.data.as_ref()
}
}
impl<T: UnsignedInteger, C: ContainerMut<Element = T>> AsMut<[T]> for CmGgswCiphertextList<C> {
fn as_mut(&mut self) -> &mut [T] {
self.data.as_mut()
}
}
pub fn cm_ggsw_ciphertext_list_size(
ciphertext_count: GgswCiphertextCount,
glwe_dimension: GlweDimension,
cm_dimension: CmDimension,
polynomial_size: PolynomialSize,
decomp_level_count: DecompositionLevelCount,
) -> usize {
ciphertext_count.0
* cm_ggsw_ciphertext_size(
glwe_dimension,
cm_dimension,
polynomial_size,
decomp_level_count,
)
}
#[allow(clippy::too_many_arguments)]
pub fn cm_ggsw_ciphertext_list_encryption_fork_config<Scalar, MaskDistribution, NoiseDistribution>(
ggsw_ciphertext_count: GgswCiphertextCount,
glwe_dimension: GlweDimension,
cm_dimension: CmDimension,
polynomial_size: PolynomialSize,
decomposition_level_count: DecompositionLevelCount,
mask_distribution: MaskDistribution,
noise_distribution: NoiseDistribution,
ciphertext_modulus: CiphertextModulus<Scalar>,
) -> EncryptionRandomGeneratorForkConfig
where
Scalar: UnsignedInteger
+ RandomGenerable<MaskDistribution, CustomModulus = Scalar>
+ RandomGenerable<NoiseDistribution, CustomModulus = Scalar>,
MaskDistribution: Distribution,
NoiseDistribution: Distribution,
{
let ggsw_mask_sample_count = cm_ggsw_ciphertext_encryption_mask_sample_count(
glwe_dimension,
cm_dimension,
polynomial_size,
decomposition_level_count,
);
let ggsw_noise_sample_count = cm_ggsw_ciphertext_encryption_noise_sample_count(
glwe_dimension,
cm_dimension,
polynomial_size,
decomposition_level_count,
);
let modulus = ciphertext_modulus.get_custom_modulus_as_optional_scalar();
EncryptionRandomGeneratorForkConfig::new(
ggsw_ciphertext_count.0,
ggsw_mask_sample_count,
mask_distribution,
ggsw_noise_sample_count,
noise_distribution,
modulus,
)
}
impl<Scalar: UnsignedInteger, C: Container<Element = Scalar>> CmGgswCiphertextList<C> {
pub fn from_container(
container: C,
glwe_dimension: GlweDimension,
cm_dimension: CmDimension,
polynomial_size: PolynomialSize,
decomp_base_log: DecompositionBaseLog,
decomp_level_count: DecompositionLevelCount,
ciphertext_modulus: CiphertextModulus<C::Element>,
) -> Self {
let ggsw_size = cm_ggsw_ciphertext_size(
glwe_dimension,
cm_dimension,
polynomial_size,
decomp_level_count,
);
assert!(
container.container_len().is_multiple_of(ggsw_size),
"The provided container length is not valid. \
It needs to be dividable by decomp_level_count * glwe_dimension * glwe_dimension * polynomial_size: \
{}.Got container length: {} and decomp_level_count: {decomp_level_count:?}, \
glwe_dimension: {glwe_dimension:?}, cm_dimension: {cm_dimension:?} polynomial_size: {polynomial_size:?}",
ggsw_size,
container.container_len()
);
Self {
data: container,
glwe_dimension,
cm_dimension,
polynomial_size,
decomp_base_log,
decomp_level_count,
ciphertext_modulus,
}
}
pub fn glwe_dimension(&self) -> GlweDimension {
self.glwe_dimension
}
pub fn cm_dimension(&self) -> CmDimension {
self.cm_dimension
}
pub fn polynomial_size(&self) -> PolynomialSize {
self.polynomial_size
}
pub fn decomposition_base_log(&self) -> DecompositionBaseLog {
self.decomp_base_log
}
pub fn decomposition_level_count(&self) -> DecompositionLevelCount {
self.decomp_level_count
}
pub fn cm_ggsw_ciphertext_count(&self) -> GgswCiphertextCount {
GgswCiphertextCount(
self.data.container_len()
/ cm_ggsw_ciphertext_size(
self.glwe_dimension,
self.cm_dimension,
self.polynomial_size,
self.decomp_level_count,
),
)
}
pub fn ciphertext_modulus(&self) -> CiphertextModulus<C::Element> {
self.ciphertext_modulus
}
pub fn into_container(self) -> C {
self.data
}
pub fn as_polynomial_list(&self) -> PolynomialListView<'_, Scalar> {
PolynomialList::from_container(self.as_ref(), self.polynomial_size())
}
pub fn encryption_fork_config<MaskDistribution, NoiseDistribution>(
&self,
mask_distribution: MaskDistribution,
noise_distribution: NoiseDistribution,
) -> EncryptionRandomGeneratorForkConfig
where
MaskDistribution: Distribution,
NoiseDistribution: Distribution,
Scalar: RandomGenerable<MaskDistribution, CustomModulus = Scalar>
+ RandomGenerable<NoiseDistribution, CustomModulus = Scalar>,
{
cm_ggsw_ciphertext_list_encryption_fork_config(
self.cm_ggsw_ciphertext_count(),
self.glwe_dimension(),
self.cm_dimension,
self.polynomial_size(),
self.decomposition_level_count(),
mask_distribution,
noise_distribution,
self.ciphertext_modulus(),
)
}
}
impl<Scalar: UnsignedInteger, C: ContainerMut<Element = Scalar>> CmGgswCiphertextList<C> {
pub fn as_mut_polynomial_list(&mut self) -> PolynomialListMutView<'_, Scalar> {
let polynomial_size = self.polynomial_size();
PolynomialList::from_container(self.as_mut(), polynomial_size)
}
}
pub type CmGgswCiphertextListOwned<Scalar> = CmGgswCiphertextList<Vec<Scalar>>;
pub type CmGgswCiphertextListView<'data, Scalar> = CmGgswCiphertextList<&'data [Scalar]>;
pub type CmGgswCiphertextListMutView<'data, Scalar> = CmGgswCiphertextList<&'data mut [Scalar]>;
impl<Scalar: UnsignedInteger> CmGgswCiphertextListOwned<Scalar> {
#[allow(clippy::too_many_arguments)]
pub fn new(
fill_with: Scalar,
glwe_dimension: GlweDimension,
cm_dimension: CmDimension,
polynomial_size: PolynomialSize,
decomp_base_log: DecompositionBaseLog,
decomp_level_count: DecompositionLevelCount,
ciphertext_count: GgswCiphertextCount,
ciphertext_modulus: CiphertextModulus<Scalar>,
) -> Self {
Self::from_container(
vec![
fill_with;
cm_ggsw_ciphertext_list_size(
ciphertext_count,
glwe_dimension,
cm_dimension,
polynomial_size,
decomp_level_count
)
],
glwe_dimension,
cm_dimension,
polynomial_size,
decomp_base_log,
decomp_level_count,
ciphertext_modulus,
)
}
}
#[derive(Clone, Copy)]
pub struct CmGgswCiphertextListCreationMetadata<Scalar: UnsignedInteger> {
pub glwe_dimension: GlweDimension,
pub cm_dimension: CmDimension,
pub polynomial_size: PolynomialSize,
pub decomp_base_log: DecompositionBaseLog,
pub decomp_level_count: DecompositionLevelCount,
pub ciphertext_modulus: CiphertextModulus<Scalar>,
}
impl<Scalar: UnsignedInteger, C: Container<Element = Scalar>> CreateFrom<C>
for CmGgswCiphertextList<C>
{
type Metadata = CmGgswCiphertextListCreationMetadata<Scalar>;
#[inline]
fn create_from(from: C, meta: Self::Metadata) -> Self {
let CmGgswCiphertextListCreationMetadata {
glwe_dimension,
cm_dimension,
polynomial_size,
decomp_base_log,
decomp_level_count,
ciphertext_modulus,
} = meta;
Self::from_container(
from,
glwe_dimension,
cm_dimension,
polynomial_size,
decomp_base_log,
decomp_level_count,
ciphertext_modulus,
)
}
}
impl<Scalar: UnsignedInteger, C: Container<Element = Scalar>> ContiguousEntityContainer
for CmGgswCiphertextList<C>
{
type Element = C::Element;
type EntityViewMetadata = CmGgswCiphertextCreationMetadata<Self::Element>;
type EntityView<'this>
= CmGgswCiphertextView<'this, Self::Element>
where
Self: 'this;
type SelfViewMetadata = CmGgswCiphertextListCreationMetadata<Self::Element>;
type SelfView<'this>
= CmGgswCiphertextListView<'this, Self::Element>
where
Self: 'this;
fn get_entity_view_creation_metadata(&self) -> Self::EntityViewMetadata {
CmGgswCiphertextCreationMetadata {
glwe_dimension: self.glwe_dimension,
cm_dimension: self.cm_dimension,
polynomial_size: self.polynomial_size,
decomp_base_log: self.decomp_base_log,
ciphertext_modulus: self.ciphertext_modulus,
}
}
fn get_entity_view_pod_size(&self) -> usize {
cm_ggsw_ciphertext_size(
self.glwe_dimension,
self.cm_dimension,
self.polynomial_size,
self.decomp_level_count,
)
}
fn get_self_view_creation_metadata(&self) -> Self::SelfViewMetadata {
CmGgswCiphertextListCreationMetadata {
glwe_dimension: self.glwe_dimension,
cm_dimension: self.cm_dimension,
polynomial_size: self.polynomial_size,
decomp_base_log: self.decomp_base_log,
decomp_level_count: self.decomp_level_count,
ciphertext_modulus: self.ciphertext_modulus,
}
}
}
impl<Scalar: UnsignedInteger, C: ContainerMut<Element = Scalar>> ContiguousEntityContainerMut
for CmGgswCiphertextList<C>
{
type EntityMutView<'this>
= CmGgswCiphertextMutView<'this, Self::Element>
where
Self: 'this;
type SelfMutView<'this>
= CmGgswCiphertextListMutView<'this, Self::Element>
where
Self: 'this;
}

View File

@@ -0,0 +1,273 @@
//! Module containing the definition of the CmGlweCiphertext.
use crate::core_crypto::commons::parameters::*;
use crate::core_crypto::commons::traits::*;
use crate::core_crypto::entities::*;
use crate::core_crypto::experimental::prelude::*;
pub fn cm_glwe_ciphertext_size(
glwe_dimension: GlweDimension,
cm_dimension: CmDimension,
polynomial_size: PolynomialSize,
) -> usize {
(glwe_dimension.0 + cm_dimension.0) * polynomial_size.0
}
pub fn cm_glwe_ciphertext_fourier_size(
glwe_dimension: GlweDimension,
cm_dimension: CmDimension,
fourier_polynomial_size: FourierPolynomialSize,
) -> usize {
(glwe_dimension.0 + cm_dimension.0) * fourier_polynomial_size.0
}
pub fn cm_glwe_ciphertext_mask_size(
glwe_dimension: GlweDimension,
polynomial_size: PolynomialSize,
) -> usize {
glwe_dimension
.to_equivalent_lwe_dimension(polynomial_size)
.0
}
pub fn cm_glwe_ciphertext_encryption_mask_sample_count(
glwe_dimension: GlweDimension,
polynomial_size: PolynomialSize,
) -> EncryptionMaskSampleCount {
EncryptionMaskSampleCount(cm_glwe_ciphertext_mask_size(
glwe_dimension,
polynomial_size,
))
}
pub fn cm_glwe_ciphertext_encryption_noise_sample_count(
cm_dimension: CmDimension,
polynomial_size: PolynomialSize,
) -> EncryptionNoiseSampleCount {
EncryptionNoiseSampleCount(cm_dimension.0 * polynomial_size.0)
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct CmGlweCiphertext<C: Container>
where
C::Element: UnsignedInteger,
{
data: C,
glwe_dimension: GlweDimension,
cm_dimension: CmDimension,
polynomial_size: PolynomialSize,
ciphertext_modulus: CiphertextModulus<C::Element>,
}
impl<T: UnsignedInteger, C: Container<Element = T>> AsRef<[T]> for CmGlweCiphertext<C> {
fn as_ref(&self) -> &[T] {
self.data.as_ref()
}
}
impl<T: UnsignedInteger, C: ContainerMut<Element = T>> AsMut<[T]> for CmGlweCiphertext<C> {
fn as_mut(&mut self) -> &mut [T] {
self.data.as_mut()
}
}
impl<Scalar: UnsignedInteger, C: Container<Element = Scalar>> CmGlweCiphertext<C> {
pub fn from_container(
container: C,
glwe_dimension: GlweDimension,
cm_dimension: CmDimension,
polynomial_size: PolynomialSize,
ciphertext_modulus: CiphertextModulus<C::Element>,
) -> Self {
assert_eq!(
container.container_len(),
cm_glwe_ciphertext_size(glwe_dimension, cm_dimension, polynomial_size)
);
Self {
data: container,
glwe_dimension,
cm_dimension,
polynomial_size,
ciphertext_modulus,
}
}
pub fn glwe_dimension(&self) -> GlweDimension {
self.glwe_dimension
}
pub fn cm_dimension(&self) -> CmDimension {
self.cm_dimension
}
pub fn polynomial_size(&self) -> PolynomialSize {
self.polynomial_size
}
pub fn ciphertext_modulus(&self) -> CiphertextModulus<C::Element> {
self.ciphertext_modulus
}
pub fn get_mask_and_bodies(&self) -> (GlweMask<&[Scalar]>, GlweBodyList<&[Scalar]>) {
let (mask, bodies) = self.data.as_ref().split_at(cm_glwe_ciphertext_mask_size(
self.glwe_dimension(),
self.polynomial_size,
));
(
GlweMask::from_container(mask, self.polynomial_size, self.ciphertext_modulus),
GlweBodyList::from_container(bodies, self.polynomial_size, self.ciphertext_modulus),
)
}
pub fn get_bodies(&self) -> GlweBodyList<&[Scalar]> {
let bodies = &self.data.as_ref()
[cm_glwe_ciphertext_mask_size(self.glwe_dimension(), self.polynomial_size)..];
GlweBodyList::from_container(bodies, self.polynomial_size, self.ciphertext_modulus)
}
pub fn get_mask(&self) -> GlweMask<&[Scalar]> {
GlweMask::from_container(
&self.as_ref()
[0..cm_glwe_ciphertext_mask_size(self.glwe_dimension(), self.polynomial_size)],
self.polynomial_size,
self.ciphertext_modulus,
)
}
pub fn as_polynomial_list(&self) -> PolynomialList<&'_ [Scalar]> {
PolynomialList::from_container(self.as_ref(), self.polynomial_size)
}
pub fn as_view(&self) -> CmGlweCiphertext<&'_ [Scalar]> {
CmGlweCiphertext {
data: self.data.as_ref(),
glwe_dimension: self.glwe_dimension,
polynomial_size: self.polynomial_size,
cm_dimension: self.cm_dimension,
ciphertext_modulus: self.ciphertext_modulus,
}
}
pub fn into_container(self) -> C {
self.data
}
}
impl<Scalar: UnsignedInteger, C: ContainerMut<Element = Scalar>> CmGlweCiphertext<C> {
pub fn get_mut_mask_and_bodies(
&mut self,
) -> (GlweMask<&mut [Scalar]>, GlweBodyList<&mut [Scalar]>) {
let glwe_dimension = self.glwe_dimension();
let polynomial_size = self.polynomial_size();
let ciphertext_modulus = self.ciphertext_modulus();
let (mask, bodies) = self
.data
.as_mut()
.split_at_mut(cm_glwe_ciphertext_mask_size(
glwe_dimension,
polynomial_size,
));
(
GlweMask::from_container(mask, polynomial_size, ciphertext_modulus),
GlweBodyList::from_container(bodies, polynomial_size, ciphertext_modulus),
)
}
pub fn get_mut_bodies(&mut self) -> GlweBodyList<&mut [Scalar]> {
let glwe_dimension = self.glwe_dimension();
let polynomial_size = self.polynomial_size();
let ciphertext_modulus = self.ciphertext_modulus();
let bodies = &mut self.data.as_mut()
[cm_glwe_ciphertext_mask_size(glwe_dimension, polynomial_size)..];
GlweBodyList::from_container(bodies, polynomial_size, ciphertext_modulus)
}
pub fn get_mut_mask(&mut self) -> GlweMask<&mut [Scalar]> {
let polynomial_size = self.polynomial_size();
let glwe_dimension = self.glwe_dimension();
let ciphertext_modulus = self.ciphertext_modulus();
GlweMask::from_container(
&mut self.as_mut()[0..cm_glwe_ciphertext_mask_size(glwe_dimension, polynomial_size)],
polynomial_size,
ciphertext_modulus,
)
}
pub fn as_mut_polynomial_list(&mut self) -> PolynomialList<&'_ mut [Scalar]> {
let polynomial_size = self.polynomial_size;
PolynomialList::from_container(self.as_mut(), polynomial_size)
}
pub fn as_mut_view(&mut self) -> CmGlweCiphertext<&'_ mut [Scalar]> {
CmGlweCiphertext {
data: self.data.as_mut(),
glwe_dimension: self.glwe_dimension,
cm_dimension: self.cm_dimension,
polynomial_size: self.polynomial_size,
ciphertext_modulus: self.ciphertext_modulus,
}
}
}
pub type CmGlweCiphertextOwned<Scalar> = CmGlweCiphertext<Vec<Scalar>>;
pub type CmGlweCiphertextView<'data, Scalar> = CmGlweCiphertext<&'data [Scalar]>;
pub type CmGlweCiphertextMutView<'data, Scalar> = CmGlweCiphertext<&'data mut [Scalar]>;
impl<Scalar: UnsignedInteger> CmGlweCiphertextOwned<Scalar> {
pub fn new(
fill_with: Scalar,
glwe_dimension: GlweDimension,
cm_dimension: CmDimension,
polynomial_size: PolynomialSize,
ciphertext_modulus: CiphertextModulus<Scalar>,
) -> Self {
Self::from_container(
vec![fill_with; cm_glwe_ciphertext_size(glwe_dimension, cm_dimension, polynomial_size)],
glwe_dimension,
cm_dimension,
polynomial_size,
ciphertext_modulus,
)
}
}
#[derive(Clone, Copy)]
pub struct CmGlweCiphertextCreationMetadata<Scalar: UnsignedInteger> {
pub glwe_dimension: GlweDimension,
pub cm_dimension: CmDimension,
pub polynomial_size: PolynomialSize,
pub ciphertext_modulus: CiphertextModulus<Scalar>,
}
impl<Scalar: UnsignedInteger, C: Container<Element = Scalar>> CreateFrom<C>
for CmGlweCiphertext<C>
{
type Metadata = CmGlweCiphertextCreationMetadata<Scalar>;
#[inline]
fn create_from(from: C, meta: Self::Metadata) -> Self {
let CmGlweCiphertextCreationMetadata {
glwe_dimension,
cm_dimension,
polynomial_size,
ciphertext_modulus,
} = meta;
Self::from_container(
from,
glwe_dimension,
cm_dimension,
polynomial_size,
ciphertext_modulus,
)
}
}

View File

@@ -0,0 +1,235 @@
//! Module containing the definition of the CmGlweCiphertextList.
use crate::core_crypto::commons::parameters::*;
use crate::core_crypto::commons::traits::*;
use crate::core_crypto::experimental::prelude::*;
#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct CmGlweCiphertextList<C: Container>
where
C::Element: UnsignedInteger,
{
data: C,
glwe_dimension: GlweDimension,
cm_dimension: CmDimension,
polynomial_size: PolynomialSize,
ciphertext_modulus: CiphertextModulus<C::Element>,
}
impl<T: UnsignedInteger, C: Container<Element = T>> AsRef<[T]> for CmGlweCiphertextList<C> {
fn as_ref(&self) -> &[T] {
self.data.as_ref()
}
}
impl<T: UnsignedInteger, C: ContainerMut<Element = T>> AsMut<[T]> for CmGlweCiphertextList<C> {
fn as_mut(&mut self) -> &mut [T] {
self.data.as_mut()
}
}
impl<Scalar: UnsignedInteger, C: Container<Element = Scalar>> CmGlweCiphertextList<C> {
pub fn from_container(
container: C,
glwe_dimension: GlweDimension,
cm_dimension: CmDimension,
polynomial_size: PolynomialSize,
ciphertext_modulus: CiphertextModulus<C::Element>,
) -> Self {
assert!(
container.container_len().is_multiple_of(cm_glwe_ciphertext_size(
glwe_dimension,
cm_dimension,
polynomial_size,
)),
"The provided container length is not valid. \
It needs to be dividable by (glwe_dimension + cm_dimension) * polynomial_size. \
Got container length: {}, glwe_dimension: {glwe_dimension:?}, cm_dimension: {cm_dimension:?}, polynomial_size: {polynomial_size:?}.",
container.container_len()
);
Self {
data: container,
polynomial_size,
ciphertext_modulus,
glwe_dimension,
cm_dimension,
}
}
pub fn glwe_dimension(&self) -> GlweDimension {
self.glwe_dimension
}
pub fn cm_dimension(&self) -> CmDimension {
self.cm_dimension
}
pub fn polynomial_size(&self) -> PolynomialSize {
self.polynomial_size
}
pub fn glwe_ciphertext_count(&self) -> GlweCiphertextCount {
GlweCiphertextCount(
self.data.container_len()
/ cm_glwe_ciphertext_size(
self.glwe_dimension,
self.cm_dimension,
self.polynomial_size,
),
)
}
pub fn into_container(self) -> C {
self.data
}
pub fn ciphertext_modulus(&self) -> CiphertextModulus<C::Element> {
self.ciphertext_modulus
}
}
pub type CmGlweCiphertextListOwned<Scalar> = CmGlweCiphertextList<Vec<Scalar>>;
pub type CmGlweCiphertextListView<'data, Scalar> = CmGlweCiphertextList<&'data [Scalar]>;
pub type CmGlweCiphertextListMutView<'data, Scalar> = CmGlweCiphertextList<&'data mut [Scalar]>;
impl<Scalar: UnsignedInteger> CmGlweCiphertextListOwned<Scalar> {
pub fn new(
fill_with: Scalar,
glwe_dimension: GlweDimension,
cm_dimension: CmDimension,
polynomial_size: PolynomialSize,
ciphertext_count: GlweCiphertextCount,
ciphertext_modulus: CiphertextModulus<Scalar>,
) -> Self {
Self::from_container(
vec![
fill_with;
cm_glwe_ciphertext_size(glwe_dimension, cm_dimension, polynomial_size)
* ciphertext_count.0
],
glwe_dimension,
cm_dimension,
polynomial_size,
ciphertext_modulus,
)
}
pub fn from_fn<F>(
glwe_dimension: GlweDimension,
cm_dimension: CmDimension,
polynomial_size: PolynomialSize,
ciphertext_count: GlweCiphertextCount,
ciphertext_modulus: CiphertextModulus<Scalar>,
mut fill_with: F,
) -> Self
where
F: FnMut(usize, usize) -> Scalar,
{
let ciphertext_size =
cm_glwe_ciphertext_size(glwe_dimension, cm_dimension, polynomial_size);
let container: Vec<_> = (0..ciphertext_count.0)
.flat_map(move |i| (0..ciphertext_size).map(move |j| (i, j)))
.map(|(i, j)| fill_with(i, j))
.collect();
Self::from_container(
container,
glwe_dimension,
cm_dimension,
polynomial_size,
ciphertext_modulus,
)
}
}
#[derive(Clone, Copy)]
pub struct CmGlweCiphertextListCreationMetadata<Scalar: UnsignedInteger> {
pub glwe_dimension: GlweDimension,
pub cm_dimension: CmDimension,
pub polynomial_size: PolynomialSize,
pub ciphertext_modulus: CiphertextModulus<Scalar>,
}
impl<Scalar: UnsignedInteger, C: Container<Element = Scalar>> CreateFrom<C>
for CmGlweCiphertextList<C>
{
type Metadata = CmGlweCiphertextListCreationMetadata<Scalar>;
#[inline]
fn create_from(from: C, meta: Self::Metadata) -> Self {
let CmGlweCiphertextListCreationMetadata {
glwe_dimension,
cm_dimension,
polynomial_size,
ciphertext_modulus,
} = meta;
Self::from_container(
from,
glwe_dimension,
cm_dimension,
polynomial_size,
ciphertext_modulus,
)
}
}
impl<Scalar: UnsignedInteger, C: Container<Element = Scalar>> ContiguousEntityContainer
for CmGlweCiphertextList<C>
{
type Element = C::Element;
type EntityViewMetadata = CmGlweCiphertextCreationMetadata<Self::Element>;
type EntityView<'this>
= CmGlweCiphertextView<'this, Self::Element>
where
Self: 'this;
type SelfViewMetadata = CmGlweCiphertextListCreationMetadata<Self::Element>;
type SelfView<'this>
= CmGlweCiphertextListView<'this, Self::Element>
where
Self: 'this;
fn get_entity_view_creation_metadata(&self) -> Self::EntityViewMetadata {
CmGlweCiphertextCreationMetadata {
glwe_dimension: self.glwe_dimension,
cm_dimension: self.cm_dimension,
polynomial_size: self.polynomial_size(),
ciphertext_modulus: self.ciphertext_modulus(),
}
}
fn get_entity_view_pod_size(&self) -> usize {
cm_glwe_ciphertext_size(
self.glwe_dimension,
self.cm_dimension,
self.polynomial_size(),
)
}
fn get_self_view_creation_metadata(&self) -> Self::SelfViewMetadata {
CmGlweCiphertextListCreationMetadata {
glwe_dimension: self.glwe_dimension,
cm_dimension: self.cm_dimension,
polynomial_size: self.polynomial_size(),
ciphertext_modulus: self.ciphertext_modulus(),
}
}
}
impl<Scalar: UnsignedInteger, C: ContainerMut<Element = Scalar>> ContiguousEntityContainerMut
for CmGlweCiphertextList<C>
{
type EntityMutView<'this>
= CmGlweCiphertextMutView<'this, Self::Element>
where
Self: 'this;
type SelfMutView<'this>
= CmGlweCiphertextListMutView<'this, Self::Element>
where
Self: 'this;
}

View File

@@ -0,0 +1,149 @@
//! Module containing the definition of the CmLweBootstrapKey.
use crate::core_crypto::commons::parameters::*;
use crate::core_crypto::commons::traits::*;
use crate::core_crypto::experimental::entities::*;
use crate::core_crypto::experimental::prelude::*;
#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct CmLweBootstrapKey<C: Container>
where
C::Element: UnsignedInteger,
{
// An CmLweBootstrapKey is literally a CmGgswCiphertextList, so we wrap a
// CmGgswCiphertextList and use Deref to have access to all the primitives of the
// CmGgswCiphertextList easily
ggsw_list: CmGgswCiphertextList<C>,
}
impl<Scalar: UnsignedInteger, C: Container<Element = Scalar>> std::ops::Deref
for CmLweBootstrapKey<C>
{
type Target = CmGgswCiphertextList<C>;
fn deref(&self) -> &CmGgswCiphertextList<C> {
&self.ggsw_list
}
}
impl<Scalar: UnsignedInteger, C: ContainerMut<Element = Scalar>> std::ops::DerefMut
for CmLweBootstrapKey<C>
{
fn deref_mut(&mut self) -> &mut CmGgswCiphertextList<C> {
&mut self.ggsw_list
}
}
pub fn cm_lwe_bootstrap_key_size(
input_lwe_dimension: LweDimension,
glwe_dimension: GlweDimension,
cm_dimension: CmDimension,
polynomial_size: PolynomialSize,
decomp_level_count: DecompositionLevelCount,
) -> usize {
cm_ggsw_ciphertext_list_size(
GgswCiphertextCount(input_lwe_dimension.0),
glwe_dimension,
cm_dimension,
polynomial_size,
decomp_level_count,
)
}
impl<Scalar: UnsignedInteger, C: Container<Element = Scalar>> CmLweBootstrapKey<C> {
pub fn from_container(
container: C,
glwe_dimension: GlweDimension,
cm_dimension: CmDimension,
polynomial_size: PolynomialSize,
decomp_base_log: DecompositionBaseLog,
decomp_level_count: DecompositionLevelCount,
ciphertext_modulus: CiphertextModulus<C::Element>,
) -> Self {
Self {
ggsw_list: CmGgswCiphertextList::from_container(
container,
glwe_dimension,
cm_dimension,
polynomial_size,
decomp_base_log,
decomp_level_count,
ciphertext_modulus,
),
}
}
pub fn input_lwe_dimension(&self) -> LweDimension {
LweDimension(self.cm_ggsw_ciphertext_count().0)
}
pub fn output_lwe_dimension(&self) -> LweDimension {
self.glwe_dimension()
.to_equivalent_lwe_dimension(self.polynomial_size())
}
pub fn into_container(self) -> C {
self.ggsw_list.into_container()
}
pub fn as_view(&self) -> CmLweBootstrapKey<&'_ [Scalar]> {
CmLweBootstrapKey::from_container(
self.as_ref(),
self.glwe_dimension(),
self.cm_dimension(),
self.polynomial_size(),
self.decomposition_base_log(),
self.decomposition_level_count(),
self.ciphertext_modulus(),
)
}
}
impl<Scalar: UnsignedInteger, C: ContainerMut<Element = Scalar>> CmLweBootstrapKey<C> {
pub fn as_mut_view(&mut self) -> CmLweBootstrapKey<&'_ mut [Scalar]> {
let glwe_dimension = self.glwe_dimension();
let cm_dimension = self.cm_dimension();
let polynomial_size = self.polynomial_size();
let decomp_base_log = self.decomposition_base_log();
let decomp_level_count = self.decomposition_level_count();
let ciphertext_modulus = self.ciphertext_modulus();
CmLweBootstrapKey::from_container(
self.as_mut(),
glwe_dimension,
cm_dimension,
polynomial_size,
decomp_base_log,
decomp_level_count,
ciphertext_modulus,
)
}
}
pub type CmLweBootstrapKeyOwned<Scalar> = CmLweBootstrapKey<Vec<Scalar>>;
impl<Scalar: UnsignedInteger> CmLweBootstrapKeyOwned<Scalar> {
#[allow(clippy::too_many_arguments)]
pub fn new(
fill_with: Scalar,
glwe_dimension: GlweDimension,
cm_dimension: CmDimension,
polynomial_size: PolynomialSize,
decomp_base_log: DecompositionBaseLog,
decomp_level_count: DecompositionLevelCount,
input_lwe_dimension: LweDimension,
ciphertext_modulus: CiphertextModulus<Scalar>,
) -> Self {
Self {
ggsw_list: CmGgswCiphertextList::new(
fill_with,
glwe_dimension,
cm_dimension,
polynomial_size,
decomp_base_log,
decomp_level_count,
GgswCiphertextCount(input_lwe_dimension.0),
ciphertext_modulus,
),
}
}
}

View File

@@ -0,0 +1,164 @@
//! Module containing the definition of the [`CmLweCiphertext`].
use crate::core_crypto::commons::traits::*;
use crate::core_crypto::experimental::prelude::*;
use crate::core_crypto::prelude::*;
#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct CmLweCiphertext<C: Container>
where
C::Element: UnsignedInteger,
{
data: C,
lwe_dimension: LweDimension,
ciphertext_modulus: CiphertextModulus<C::Element>,
}
impl<T: UnsignedInteger, C: Container<Element = T>> AsRef<[T]> for CmLweCiphertext<C> {
fn as_ref(&self) -> &[T] {
self.data.as_ref()
}
}
impl<T: UnsignedInteger, C: ContainerMut<Element = T>> AsMut<[T]> for CmLweCiphertext<C> {
fn as_mut(&mut self) -> &mut [T] {
self.data.as_mut()
}
}
impl<Scalar: UnsignedInteger, C: Container<Element = Scalar>> CmLweCiphertext<C> {
pub fn from_container(
container: C,
lwe_dimension: LweDimension,
ciphertext_modulus: CiphertextModulus<C::Element>,
) -> Self {
assert!(
container.container_len() > 0,
"Got an empty container to create an CmLweCiphertext"
);
Self {
data: container,
ciphertext_modulus,
lwe_dimension,
}
}
pub fn lwe_dimension(&self) -> LweDimension {
self.lwe_dimension
}
pub fn cm_dimension(&self) -> CmDimension {
CmDimension(self.data.container_len() - self.lwe_dimension.0)
}
pub fn get_mask_and_bodies(&self) -> (LweMask<&[Scalar]>, LweBodyList<&[Scalar]>) {
let (mask, bodies) = self.data.as_ref().split_at(self.lwe_dimension.0);
let ciphertext_modulus = self.ciphertext_modulus();
(
LweMask::from_container(mask, ciphertext_modulus),
LweBodyList::from_container(bodies, ciphertext_modulus),
)
}
pub fn get_bodies(&self) -> LweBodyList<&[Scalar]> {
self.get_mask_and_bodies().1
}
pub fn get_mask(&self) -> LweMask<&[Scalar]> {
self.get_mask_and_bodies().0
}
pub fn as_view(&self) -> CmLweCiphertextView<'_, Scalar> {
CmLweCiphertextView::from_container(
self.as_ref(),
self.lwe_dimension,
self.ciphertext_modulus(),
)
}
pub fn into_container(self) -> C {
self.data
}
pub fn ciphertext_modulus(&self) -> CiphertextModulus<C::Element> {
self.ciphertext_modulus
}
pub fn extract_lwe_ciphertext(&self, index: usize) -> LweCiphertextOwned<Scalar> {
let mut extracted_lwe = self.get_mask().into_container().to_vec();
extracted_lwe.push(self.get_bodies().into_container()[index]);
LweCiphertext::from_container(extracted_lwe, self.ciphertext_modulus)
}
}
impl<Scalar: UnsignedInteger, C: ContainerMut<Element = Scalar>> CmLweCiphertext<C> {
pub fn get_mut_mask_and_bodies(
&mut self,
) -> (LweMask<&mut [Scalar]>, LweBodyList<&mut [Scalar]>) {
let ciphertext_modulus = self.ciphertext_modulus();
let (mask, bodies) = self.data.as_mut().split_at(self.lwe_dimension.0);
(
LweMask::from_container(mask, ciphertext_modulus),
LweBodyList::from_container(bodies, ciphertext_modulus),
)
}
pub fn get_mut_bodies(&mut self) -> LweBodyList<&mut [Scalar]> {
self.get_mut_mask_and_bodies().1
}
pub fn get_mut_mask(&mut self) -> LweMask<&mut [Scalar]> {
self.get_mut_mask_and_bodies().0
}
pub fn as_mut_view(&mut self) -> CmLweCiphertextMutView<'_, Scalar> {
let ciphertext_modulus = self.ciphertext_modulus();
let lwe_dimension = self.lwe_dimension;
CmLweCiphertextMutView::from_container(self.as_mut(), lwe_dimension, ciphertext_modulus)
}
}
pub type CmLweCiphertextOwned<Scalar> = CmLweCiphertext<Vec<Scalar>>;
pub type CmLweCiphertextView<'data, Scalar> = CmLweCiphertext<&'data [Scalar]>;
pub type CmLweCiphertextMutView<'data, Scalar> = CmLweCiphertext<&'data mut [Scalar]>;
impl<Scalar: UnsignedInteger> CmLweCiphertextOwned<Scalar> {
pub fn new(
fill_with: Scalar,
lwe_dimension: LweDimension,
cm_dimension: CmDimension,
ciphertext_modulus: CiphertextModulus<Scalar>,
) -> Self {
Self::from_container(
vec![fill_with; lwe_dimension.0 + cm_dimension.0],
lwe_dimension,
ciphertext_modulus,
)
}
}
#[derive(Clone, Copy)]
pub struct CmLweCiphertextCreationMetadata<Scalar: UnsignedInteger> {
pub lwe_dimension: LweDimension,
pub ciphertext_modulus: CiphertextModulus<Scalar>,
}
impl<Scalar: UnsignedInteger, C: Container<Element = Scalar>> CreateFrom<C> for CmLweCiphertext<C> {
type Metadata = CmLweCiphertextCreationMetadata<C::Element>;
#[inline]
fn create_from(from: C, meta: Self::Metadata) -> Self {
let CmLweCiphertextCreationMetadata {
lwe_dimension,
ciphertext_modulus,
} = meta;
Self::from_container(from, lwe_dimension, ciphertext_modulus)
}
}

View File

@@ -0,0 +1,202 @@
//! Module containing the definition of the [`CmLweCiphertextList`].
use super::cm_lwe_ciphertext::{
CmLweCiphertextCreationMetadata, CmLweCiphertextMutView, CmLweCiphertextView,
};
use crate::core_crypto::commons::parameters::*;
use crate::core_crypto::commons::traits::*;
use crate::core_crypto::experimental::prelude::*;
#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct CmLweCiphertextList<C: Container>
where
C::Element: UnsignedInteger,
{
data: C,
lwe_dimension: LweDimension,
cm_dimension: CmDimension,
ciphertext_modulus: CiphertextModulus<C::Element>,
}
impl<T: UnsignedInteger, C: Container<Element = T>> AsRef<[T]> for CmLweCiphertextList<C> {
fn as_ref(&self) -> &[T] {
self.data.as_ref()
}
}
impl<T: UnsignedInteger, C: ContainerMut<Element = T>> AsMut<[T]> for CmLweCiphertextList<C> {
fn as_mut(&mut self) -> &mut [T] {
self.data.as_mut()
}
}
impl<Scalar: UnsignedInteger, C: Container<Element = Scalar>> CmLweCiphertextList<C> {
pub fn from_container(
container: C,
lwe_dimension: LweDimension,
cm_dimension: CmDimension,
ciphertext_modulus: CiphertextModulus<C::Element>,
) -> Self {
assert!(
container
.container_len()
.is_multiple_of(lwe_dimension.0 + cm_dimension.0),
"The provided container length is not valid. \
It needs to be dividable by lwe_dimension + cm_dimension. \
Got container length: {} and lwe_dimension + cm_dimension: {}.",
container.container_len(),
lwe_dimension.0 + cm_dimension.0
);
Self {
data: container,
lwe_dimension,
cm_dimension,
ciphertext_modulus,
}
}
pub fn lwe_dimension(&self) -> LweDimension {
self.lwe_dimension
}
pub fn cm_dimension(&self) -> CmDimension {
self.cm_dimension
}
pub fn cm_lwe_ciphertext_count(&self) -> CmLweCiphertextCount {
CmLweCiphertextCount(
self.data.container_len() / (self.lwe_dimension.0 + self.cm_dimension.0),
)
}
pub fn as_view(&self) -> CmLweCiphertextListView<'_, Scalar> {
CmLweCiphertextListView::from_container(
self.as_ref(),
self.lwe_dimension,
self.cm_dimension,
self.ciphertext_modulus,
)
}
pub fn into_container(self) -> C {
self.data
}
pub fn ciphertext_modulus(&self) -> CiphertextModulus<C::Element> {
self.ciphertext_modulus
}
}
impl<Scalar: UnsignedInteger, C: ContainerMut<Element = Scalar>> CmLweCiphertextList<C> {
pub fn as_mut_view(&mut self) -> CmLweCiphertextListMutView<'_, Scalar> {
let lwe_dimension = self.lwe_dimension();
let ciphertext_modulus = self.ciphertext_modulus();
let cm_dimension = self.cm_dimension;
CmLweCiphertextListMutView::from_container(
self.as_mut(),
lwe_dimension,
cm_dimension,
ciphertext_modulus,
)
}
}
pub type CmLweCiphertextListOwned<Scalar> = CmLweCiphertextList<Vec<Scalar>>;
pub type CmLweCiphertextListView<'data, Scalar> = CmLweCiphertextList<&'data [Scalar]>;
pub type CmLweCiphertextListMutView<'data, Scalar> = CmLweCiphertextList<&'data mut [Scalar]>;
impl<Scalar: UnsignedInteger> CmLweCiphertextListOwned<Scalar> {
pub fn new(
fill_with: Scalar,
lwe_dimension: LweDimension,
cm_dimension: CmDimension,
ciphertext_count: CmLweCiphertextCount,
ciphertext_modulus: CiphertextModulus<Scalar>,
) -> Self {
Self::from_container(
vec![fill_with; (lwe_dimension.0 + cm_dimension.0) * ciphertext_count.0],
lwe_dimension,
cm_dimension,
ciphertext_modulus,
)
}
}
#[derive(Clone, Copy)]
pub struct CmLweCiphertextListCreationMetadata<Scalar: UnsignedInteger> {
pub lwe_dimension: LweDimension,
pub cm_dimension: CmDimension,
pub ciphertext_modulus: CiphertextModulus<Scalar>,
}
impl<Scalar: UnsignedInteger, C: Container<Element = Scalar>> CreateFrom<C>
for CmLweCiphertextList<C>
{
type Metadata = CmLweCiphertextListCreationMetadata<Scalar>;
#[inline]
fn create_from(from: C, meta: Self::Metadata) -> Self {
let CmLweCiphertextListCreationMetadata {
lwe_dimension,
cm_dimension,
ciphertext_modulus,
} = meta;
Self::from_container(from, lwe_dimension, cm_dimension, ciphertext_modulus)
}
}
impl<Scalar: UnsignedInteger, C: Container<Element = Scalar>> ContiguousEntityContainer
for CmLweCiphertextList<C>
{
type Element = C::Element;
type EntityViewMetadata = CmLweCiphertextCreationMetadata<Self::Element>;
type EntityView<'this>
= CmLweCiphertextView<'this, Self::Element>
where
Self: 'this;
type SelfViewMetadata = CmLweCiphertextListCreationMetadata<Self::Element>;
type SelfView<'this>
= CmLweCiphertextListView<'this, Self::Element>
where
Self: 'this;
fn get_entity_view_creation_metadata(&self) -> Self::EntityViewMetadata {
CmLweCiphertextCreationMetadata {
ciphertext_modulus: self.ciphertext_modulus,
lwe_dimension: self.lwe_dimension,
}
}
fn get_entity_view_pod_size(&self) -> usize {
self.lwe_dimension().0 + self.cm_dimension().0
}
fn get_self_view_creation_metadata(&self) -> Self::SelfViewMetadata {
CmLweCiphertextListCreationMetadata {
lwe_dimension: self.lwe_dimension,
cm_dimension: self.cm_dimension,
ciphertext_modulus: self.ciphertext_modulus,
}
}
}
impl<Scalar: UnsignedInteger, C: ContainerMut<Element = Scalar>> ContiguousEntityContainerMut
for CmLweCiphertextList<C>
{
type EntityMutView<'this>
= CmLweCiphertextMutView<'this, Self::Element>
where
Self: 'this;
type SelfMutView<'this>
= CmLweCiphertextListMutView<'this, Self::Element>
where
Self: 'this;
}

View File

@@ -0,0 +1,300 @@
//! Module containing the definition of the [`CmLweKeyswitchKey`].
use crate::core_crypto::commons::parameters::*;
use crate::core_crypto::commons::traits::*;
use crate::core_crypto::experimental::prelude::*;
#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct CmLweKeyswitchKey<C: Container>
where
C::Element: UnsignedInteger,
{
data: C,
decomp_base_log: DecompositionBaseLog,
decomp_level_count: DecompositionLevelCount,
input_lwe_dimension: LweDimension,
output_lwe_dimension: LweDimension,
cm_dimension: CmDimension,
ciphertext_modulus: CiphertextModulus<C::Element>,
}
impl<T: UnsignedInteger, C: Container<Element = T>> AsRef<[T]> for CmLweKeyswitchKey<C> {
fn as_ref(&self) -> &[T] {
self.data.as_ref()
}
}
impl<T: UnsignedInteger, C: ContainerMut<Element = T>> AsMut<[T]> for CmLweKeyswitchKey<C> {
fn as_mut(&mut self) -> &mut [T] {
self.data.as_mut()
}
}
pub fn cm_lwe_keyswitch_key_input_key_element_encrypted_size(
decomp_level_count: DecompositionLevelCount,
output_lwe_dimension: LweDimension,
cm_dimension: CmDimension,
) -> usize {
// One ciphertext per level encrypted under the output key
decomp_level_count.0 * (output_lwe_dimension.0 + cm_dimension.0)
}
impl<Scalar: UnsignedInteger, C: Container<Element = Scalar>> CmLweKeyswitchKey<C> {
pub fn from_container(
container: C,
decomp_base_log: DecompositionBaseLog,
decomp_level_count: DecompositionLevelCount,
input_lwe_dimension: LweDimension,
output_lwe_dimension: LweDimension,
cm_dimension: CmDimension,
ciphertext_modulus: CiphertextModulus<C::Element>,
) -> Self {
assert!(
container.container_len() > 0,
"Got an empty container to create an CmLweKeyswitchKey"
);
let input_key_element_size = cm_lwe_keyswitch_key_input_key_element_encrypted_size(
decomp_level_count,
output_lwe_dimension,
cm_dimension,
);
assert!(
container.container_len().is_multiple_of(input_key_element_size),
"The provided container length is not valid. \
It needs to be dividable by decomp_level_count * (output_lwe_dimension + cm_dimension): {}. \
Got container length: {} and decomp_level_count: {decomp_level_count:?}, \
output_lwe_dimension: {output_lwe_dimension:?}.",
input_key_element_size,
container.container_len()
);
Self {
data: container,
decomp_base_log,
decomp_level_count,
input_lwe_dimension,
output_lwe_dimension,
cm_dimension,
ciphertext_modulus,
}
}
pub fn decomposition_base_log(&self) -> DecompositionBaseLog {
self.decomp_base_log
}
pub fn decomposition_level_count(&self) -> DecompositionLevelCount {
self.decomp_level_count
}
pub fn input_lwe_dimension(&self) -> LweDimension {
self.input_lwe_dimension
}
pub fn output_lwe_dimension(&self) -> LweDimension {
self.output_lwe_dimension
}
pub fn cm_dimension(&self) -> CmDimension {
self.cm_dimension
}
pub fn input_key_element_encrypted_size(&self) -> usize {
cm_lwe_keyswitch_key_input_key_element_encrypted_size(
self.decomp_level_count,
self.output_lwe_dimension,
self.cm_dimension,
)
}
pub fn as_view(&self) -> CmLweKeyswitchKeyView<'_, Scalar> {
CmLweKeyswitchKey::from_container(
self.as_ref(),
self.decomp_base_log,
self.decomp_level_count,
self.input_lwe_dimension,
self.output_lwe_dimension,
self.cm_dimension,
self.ciphertext_modulus,
)
}
pub fn into_container(self) -> C {
self.data
}
pub fn as_cm_lwe_ciphertext_list(&self) -> CmLweCiphertextListView<'_, Scalar> {
CmLweCiphertextListView::from_container(
self.as_ref(),
self.output_lwe_dimension,
self.cm_dimension,
self.ciphertext_modulus(),
)
}
pub fn ciphertext_modulus(&self) -> CiphertextModulus<C::Element> {
self.ciphertext_modulus
}
}
impl<Scalar: UnsignedInteger, C: ContainerMut<Element = Scalar>> CmLweKeyswitchKey<C> {
pub fn as_mut_view(&mut self) -> CmLweKeyswitchKeyMutView<'_, Scalar> {
let decomp_base_log = self.decomp_base_log;
let decomp_level_count = self.decomp_level_count;
let input_lwe_dimension = self.input_lwe_dimension;
let output_lwe_dimension = self.output_lwe_dimension;
let cm_dimension = self.cm_dimension;
let ciphertext_modulus = self.ciphertext_modulus;
CmLweKeyswitchKey::from_container(
self.as_mut(),
decomp_base_log,
decomp_level_count,
input_lwe_dimension,
output_lwe_dimension,
cm_dimension,
ciphertext_modulus,
)
}
pub fn as_mut_cm_lwe_ciphertext_list(&mut self) -> CmLweCiphertextListMutView<'_, Scalar> {
let output_lwe_dimension = self.output_lwe_dimension();
let cm_dimension = self.cm_dimension;
let ciphertext_modulus = self.ciphertext_modulus();
CmLweCiphertextListMutView::from_container(
self.as_mut(),
output_lwe_dimension,
cm_dimension,
ciphertext_modulus,
)
}
}
pub type CmLweKeyswitchKeyOwned<Scalar> = CmLweKeyswitchKey<Vec<Scalar>>;
pub type CmLweKeyswitchKeyView<'data, Scalar> = CmLweKeyswitchKey<&'data [Scalar]>;
pub type CmLweKeyswitchKeyMutView<'data, Scalar> = CmLweKeyswitchKey<&'data mut [Scalar]>;
impl<Scalar: UnsignedInteger> CmLweKeyswitchKeyOwned<Scalar> {
pub fn new(
fill_with: Scalar,
decomp_base_log: DecompositionBaseLog,
decomp_level_count: DecompositionLevelCount,
input_key_lwe_dimension: LweDimension,
output_key_lwe_dimension: LweDimension,
cm_dimension: CmDimension,
ciphertext_modulus: CiphertextModulus<Scalar>,
) -> Self {
Self::from_container(
vec![
fill_with;
input_key_lwe_dimension.0
* cm_lwe_keyswitch_key_input_key_element_encrypted_size(
decomp_level_count,
output_key_lwe_dimension,
cm_dimension,
)
],
decomp_base_log,
decomp_level_count,
input_key_lwe_dimension,
output_key_lwe_dimension,
cm_dimension,
ciphertext_modulus,
)
}
}
#[derive(Clone, Copy)]
pub struct CmLweKeyswitchKeyCreationMetadata<Scalar: UnsignedInteger> {
pub decomp_base_log: DecompositionBaseLog,
pub decomp_level_count: DecompositionLevelCount,
pub input_lwe_dimension: LweDimension,
pub output_lwe_dimension: LweDimension,
pub cm_dimension: CmDimension,
pub ciphertext_modulus: CiphertextModulus<Scalar>,
}
impl<Scalar: UnsignedInteger, C: Container<Element = Scalar>> CreateFrom<C>
for CmLweKeyswitchKey<C>
{
type Metadata = CmLweKeyswitchKeyCreationMetadata<Scalar>;
#[inline]
fn create_from(from: C, meta: Self::Metadata) -> Self {
let CmLweKeyswitchKeyCreationMetadata {
decomp_base_log,
decomp_level_count,
output_lwe_dimension,
ciphertext_modulus,
input_lwe_dimension,
cm_dimension,
} = meta;
Self::from_container(
from,
decomp_base_log,
decomp_level_count,
input_lwe_dimension,
output_lwe_dimension,
cm_dimension,
ciphertext_modulus,
)
}
}
impl<Scalar: UnsignedInteger, C: Container<Element = Scalar>> ContiguousEntityContainer
for CmLweKeyswitchKey<C>
{
type Element = C::Element;
type EntityViewMetadata = CmLweCiphertextListCreationMetadata<Self::Element>;
type EntityView<'this>
= CmLweCiphertextListView<'this, Self::Element>
where
Self: 'this;
type SelfViewMetadata = CmLweKeyswitchKeyCreationMetadata<Self::Element>;
type SelfView<'this>
= CmLweKeyswitchKeyView<'this, Self::Element>
where
Self: 'this;
fn get_entity_view_creation_metadata(&self) -> Self::EntityViewMetadata {
CmLweCiphertextListCreationMetadata {
ciphertext_modulus: self.ciphertext_modulus(),
lwe_dimension: self.output_lwe_dimension,
cm_dimension: self.cm_dimension,
}
}
fn get_entity_view_pod_size(&self) -> usize {
self.input_key_element_encrypted_size()
}
fn get_self_view_creation_metadata(&self) -> Self::SelfViewMetadata {
CmLweKeyswitchKeyCreationMetadata {
decomp_base_log: self.decomposition_base_log(),
decomp_level_count: self.decomposition_level_count(),
output_lwe_dimension: self.output_lwe_dimension,
input_lwe_dimension: self.input_lwe_dimension,
cm_dimension: self.cm_dimension,
ciphertext_modulus: self.ciphertext_modulus(),
}
}
}
impl<Scalar: UnsignedInteger, C: ContainerMut<Element = Scalar>> ContiguousEntityContainerMut
for CmLweKeyswitchKey<C>
{
type EntityMutView<'this>
= CmLweCiphertextListMutView<'this, Self::Element>
where
Self: 'this;
type SelfMutView<'this>
= CmLweKeyswitchKeyMutView<'this, Self::Element>
where
Self: 'this;
}

View File

@@ -0,0 +1,361 @@
//! Module containing the definition of the [`CmLwePackingKey`].
use super::cm_lwe_packing_key_part::{
cm_lwe_packing_key_part_size, CmLwePackingKeyPartCreationMetadata, CmLwePackingKeyPartMutView,
CmLwePackingKeyPartView,
};
use crate::core_crypto::commons::parameters::*;
use crate::core_crypto::commons::traits::*;
use crate::core_crypto::experimental::entities::*;
use crate::core_crypto::experimental::prelude::*;
/// A packing key that converts `d = output_cm_dimension` independent LWE ciphertexts into a
/// single CM-LWE ciphertext.
///
/// For each input index `i`, the i-th [`CmLwePackingKeyPart`] key-switches
/// `lwe(m_i)` into `cm_lwe(0, ..., 0, m_i, 0, ..., 0)` where `m_i` is placed at position `i`.
/// Summing all `d` results yields `cm_lwe(m_0, m_1, ..., m_{d-1})`.
///
/// Memory layout (`d` = `output_cm_dimension`, `n` = `input_lwe_dimension`,
/// `L` = `decomp_level_count`, `k` = `output_lwe_dimension`):
/// `key_part[0] | key_part[1] | ... | key_part[d-1]`, each of size `n * L * (k + d)`.
/// Total size: `d * n * L * (k + d)`.
#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct CmLwePackingKey<C: Container>
where
C::Element: UnsignedInteger,
{
data: C,
decomp_base_log: DecompositionBaseLog,
decomp_level_count: DecompositionLevelCount,
input_lwe_dimension: LweDimension,
output_lwe_dimension: LweDimension,
output_cm_dimension: CmDimension,
ciphertext_modulus: CiphertextModulus<C::Element>,
}
impl<T: UnsignedInteger, C: Container<Element = T>> AsRef<[T]> for CmLwePackingKey<C> {
fn as_ref(&self) -> &[T] {
self.data.as_ref()
}
}
impl<T: UnsignedInteger, C: ContainerMut<Element = T>> AsMut<[T]> for CmLwePackingKey<C> {
fn as_mut(&mut self) -> &mut [T] {
self.data.as_mut()
}
}
pub fn cm_lwe_packing_key_size(
decomp_level_count: DecompositionLevelCount,
input_lwe_dimension: LweDimension,
output_lwe_dimension: LweDimension,
output_cm_dimension: CmDimension,
) -> usize {
output_cm_dimension.0
* cm_lwe_packing_key_part_size(
decomp_level_count,
input_lwe_dimension,
output_lwe_dimension,
output_cm_dimension,
)
}
impl<Scalar: UnsignedInteger, C: Container<Element = Scalar>> CmLwePackingKey<C> {
pub fn from_container(
container: C,
decomp_base_log: DecompositionBaseLog,
decomp_level_count: DecompositionLevelCount,
input_lwe_dimension: LweDimension,
output_lwe_dimension: LweDimension,
output_cm_dimension: CmDimension,
ciphertext_modulus: CiphertextModulus<C::Element>,
) -> Self {
let expected_len = cm_lwe_packing_key_size(
decomp_level_count,
input_lwe_dimension,
output_lwe_dimension,
output_cm_dimension,
);
assert_eq!(
container.container_len(),
expected_len,
"The provided container length is not valid. \
Expected decomp_level_count * (output_lwe_dimension + output_cm_dimension) * output_cm_dimension * input_lwe_dimension: {}. \
Got container length: {} and decomp_level_count: {decomp_level_count:?}, \
output_lwe_dimension + output_cm_dimension: {}, input_lwe_dimension: {input_lwe_dimension:?}.",
expected_len,
container.container_len(),
output_lwe_dimension.0 + output_cm_dimension.0,
);
Self {
data: container,
decomp_base_log,
decomp_level_count,
input_lwe_dimension,
output_lwe_dimension,
output_cm_dimension,
ciphertext_modulus,
}
}
pub fn decomposition_base_log(&self) -> DecompositionBaseLog {
self.decomp_base_log
}
pub fn decomposition_level_count(&self) -> DecompositionLevelCount {
self.decomp_level_count
}
pub fn input_key_lwe_dimension(&self) -> LweDimension {
self.input_lwe_dimension
}
pub fn output_lwe_dimension(&self) -> LweDimension {
self.output_lwe_dimension
}
pub fn output_cm_dimension(&self) -> CmDimension {
self.output_cm_dimension
}
pub fn key_part_size(&self) -> usize {
let Self {
decomp_level_count,
input_lwe_dimension,
output_lwe_dimension,
output_cm_dimension,
..
} = *self;
cm_lwe_packing_key_part_size(
decomp_level_count,
input_lwe_dimension,
output_lwe_dimension,
output_cm_dimension,
)
}
pub fn as_view(&self) -> CmLwePackingKeyView<'_, Scalar> {
let Self {
data: _,
decomp_base_log,
decomp_level_count,
input_lwe_dimension,
output_lwe_dimension,
output_cm_dimension,
ciphertext_modulus,
} = *self;
CmLwePackingKey::from_container(
self.as_ref(),
decomp_base_log,
decomp_level_count,
input_lwe_dimension,
output_lwe_dimension,
output_cm_dimension,
ciphertext_modulus,
)
}
pub fn into_container(self) -> C {
self.data
}
pub fn as_cm_lwe_ciphertext_list(&self) -> CmLweCiphertextListView<'_, Scalar> {
CmLweCiphertextListView::from_container(
self.as_ref(),
self.output_lwe_dimension(),
self.output_cm_dimension,
self.ciphertext_modulus(),
)
}
pub fn ciphertext_modulus(&self) -> CiphertextModulus<C::Element> {
self.ciphertext_modulus
}
}
impl<Scalar: UnsignedInteger, C: ContainerMut<Element = Scalar>> CmLwePackingKey<C> {
pub fn as_mut_view(&mut self) -> CmLwePackingKeyMutView<'_, Scalar> {
let Self {
data: _,
decomp_base_log,
decomp_level_count,
input_lwe_dimension,
output_lwe_dimension,
output_cm_dimension,
ciphertext_modulus,
} = *self;
CmLwePackingKey::from_container(
self.as_mut(),
decomp_base_log,
decomp_level_count,
input_lwe_dimension,
output_lwe_dimension,
output_cm_dimension,
ciphertext_modulus,
)
}
pub fn as_mut_cm_lwe_ciphertext_list(&mut self) -> CmLweCiphertextListMutView<'_, Scalar> {
let Self {
output_lwe_dimension,
output_cm_dimension,
ciphertext_modulus,
..
} = *self;
CmLweCiphertextListMutView::from_container(
self.as_mut(),
output_lwe_dimension,
output_cm_dimension,
ciphertext_modulus,
)
}
}
pub type CmLwePackingKeyOwned<Scalar> = CmLwePackingKey<Vec<Scalar>>;
pub type CmLwePackingKeyView<'data, Scalar> = CmLwePackingKey<&'data [Scalar]>;
pub type CmLwePackingKeyMutView<'data, Scalar> = CmLwePackingKey<&'data mut [Scalar]>;
impl<Scalar: UnsignedInteger> CmLwePackingKeyOwned<Scalar> {
pub fn new(
fill_with: Scalar,
decomp_base_log: DecompositionBaseLog,
decomp_level_count: DecompositionLevelCount,
input_key_lwe_dimension: LweDimension,
output_lwe_dimension: LweDimension,
output_cm_dimension: CmDimension,
ciphertext_modulus: CiphertextModulus<Scalar>,
) -> Self {
let expected_len = cm_lwe_packing_key_size(
decomp_level_count,
input_key_lwe_dimension,
output_lwe_dimension,
output_cm_dimension,
);
Self::from_container(
vec![fill_with; expected_len],
decomp_base_log,
decomp_level_count,
input_key_lwe_dimension,
output_lwe_dimension,
output_cm_dimension,
ciphertext_modulus,
)
}
}
#[derive(Clone, Copy)]
pub struct CmLwePackingKeyCreationMetadata<Scalar: UnsignedInteger> {
pub decomp_base_log: DecompositionBaseLog,
pub decomp_level_count: DecompositionLevelCount,
pub input_lwe_dimension: LweDimension,
pub output_lwe_dimension: LweDimension,
pub output_cm_dimension: CmDimension,
pub ciphertext_modulus: CiphertextModulus<Scalar>,
}
impl<Scalar: UnsignedInteger, C: Container<Element = Scalar>> CreateFrom<C> for CmLwePackingKey<C> {
type Metadata = CmLwePackingKeyCreationMetadata<Scalar>;
#[inline]
fn create_from(from: C, meta: Self::Metadata) -> Self {
let CmLwePackingKeyCreationMetadata {
decomp_base_log,
decomp_level_count,
input_lwe_dimension,
output_lwe_dimension,
output_cm_dimension,
ciphertext_modulus,
} = meta;
Self::from_container(
from,
decomp_base_log,
decomp_level_count,
input_lwe_dimension,
output_lwe_dimension,
output_cm_dimension,
ciphertext_modulus,
)
}
}
impl<Scalar: UnsignedInteger, C: Container<Element = Scalar>> ContiguousEntityContainer
for CmLwePackingKey<C>
{
type Element = C::Element;
type EntityViewMetadata = CmLwePackingKeyPartCreationMetadata<Self::Element>;
type EntityView<'this>
= CmLwePackingKeyPartView<'this, Self::Element>
where
Self: 'this;
type SelfViewMetadata = CmLwePackingKeyCreationMetadata<Self::Element>;
type SelfView<'this>
= CmLwePackingKeyView<'this, Self::Element>
where
Self: 'this;
fn get_entity_view_creation_metadata(&self) -> Self::EntityViewMetadata {
let Self {
data: _,
decomp_base_log,
decomp_level_count,
input_lwe_dimension,
output_lwe_dimension,
output_cm_dimension,
ciphertext_modulus,
} = *self;
CmLwePackingKeyPartCreationMetadata {
decomp_base_log,
decomp_level_count,
input_lwe_dimension,
output_lwe_dimension,
output_cm_dimension,
ciphertext_modulus,
}
}
fn get_entity_view_pod_size(&self) -> usize {
self.key_part_size()
}
fn get_self_view_creation_metadata(&self) -> Self::SelfViewMetadata {
let Self {
data: _,
decomp_base_log,
decomp_level_count,
input_lwe_dimension,
output_lwe_dimension,
output_cm_dimension,
ciphertext_modulus,
} = *self;
CmLwePackingKeyCreationMetadata {
decomp_base_log,
decomp_level_count,
input_lwe_dimension,
output_lwe_dimension,
output_cm_dimension,
ciphertext_modulus,
}
}
}
impl<Scalar: UnsignedInteger, C: ContainerMut<Element = Scalar>> ContiguousEntityContainerMut
for CmLwePackingKey<C>
{
type EntityMutView<'this>
= CmLwePackingKeyPartMutView<'this, Self::Element>
where
Self: 'this;
type SelfMutView<'this>
= CmLwePackingKeyMutView<'this, Self::Element>
where
Self: 'this;
}

View File

@@ -0,0 +1,343 @@
//! Module containing the definition of the [`CmLwePackingKeyPart`].
use super::cm_lwe_ciphertext_list::{
CmLweCiphertextListCreationMetadata, CmLweCiphertextListMutView, CmLweCiphertextListView,
};
use crate::core_crypto::commons::parameters::*;
use crate::core_crypto::commons::traits::*;
use crate::core_crypto::experimental::prelude::CmDimension;
/// The i-th part of a [`CmLwePackingKey`](super::cm_lwe_packing_key::CmLwePackingKey): a keyswitch
/// key that converts `lwe(m_i)` into `cm_lwe(0, ..., 0, m_i, 0, ..., 0)` where `m_i` is at position
/// `i`.
///
/// Concretely it key-switches each of the `n = input_lwe_dimension` mask elements of the input
/// ciphertext into a CM-LWE ciphertext. Each mask element contributes `L` CM-LWE ciphertexts
/// (one per decomposition level), each of size `k + d`.
///
/// Memory layout (`n` = `input_lwe_dimension`, `L` = `decomp_level_count`,
/// `k` = `output_lwe_dimension`, `d` = `output_cm_dimension`):
/// `block[0] | block[1] | ... | block[n-1]`, each of size `L * (k + d)`.
/// Total size: `n * L * (k + d)`.
#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct CmLwePackingKeyPart<C: Container>
where
C::Element: UnsignedInteger,
{
data: C,
decomp_base_log: DecompositionBaseLog,
decomp_level_count: DecompositionLevelCount,
input_lwe_dimension: LweDimension,
output_lwe_dimension: LweDimension,
output_cm_dimension: CmDimension,
ciphertext_modulus: CiphertextModulus<C::Element>,
}
impl<T: UnsignedInteger, C: Container<Element = T>> AsRef<[T]> for CmLwePackingKeyPart<C> {
fn as_ref(&self) -> &[T] {
self.data.as_ref()
}
}
impl<T: UnsignedInteger, C: ContainerMut<Element = T>> AsMut<[T]> for CmLwePackingKeyPart<C> {
fn as_mut(&mut self) -> &mut [T] {
self.data.as_mut()
}
}
pub fn cm_lwe_packing_key_part_size(
decomp_level_count: DecompositionLevelCount,
input_lwe_dimension: LweDimension,
output_lwe_dimension: LweDimension,
output_cm_dimension: CmDimension,
) -> usize {
input_lwe_dimension.0
* cm_lwe_packing_key_part_input_key_element_encrypted_size(
decomp_level_count,
output_lwe_dimension,
output_cm_dimension,
)
}
pub fn cm_lwe_packing_key_part_input_key_element_encrypted_size(
decomp_level_count: DecompositionLevelCount,
output_lwe_dimension: LweDimension,
output_cm_dimension: CmDimension,
) -> usize {
// One ciphertext per level encrypted under the output key
decomp_level_count.0 * (output_lwe_dimension.0 + output_cm_dimension.0)
}
impl<Scalar: UnsignedInteger, C: Container<Element = Scalar>> CmLwePackingKeyPart<C> {
pub fn from_container(
container: C,
decomp_base_log: DecompositionBaseLog,
decomp_level_count: DecompositionLevelCount,
input_lwe_dimension: LweDimension,
output_lwe_dimension: LweDimension,
output_cm_dimension: CmDimension,
ciphertext_modulus: CiphertextModulus<C::Element>,
) -> Self {
let expected_len = cm_lwe_packing_key_part_size(
decomp_level_count,
input_lwe_dimension,
output_lwe_dimension,
output_cm_dimension,
);
assert_eq!(
container.container_len(),
expected_len,
"The provided container length is not valid. \
Expected decomp_level_count * (output_lwe_dimension + output_cm_dimension) * input_lwe_dimension: {}. \
Got container length: {} and decomp_level_count: {decomp_level_count:?}, \
output_lwe_dimension + output_cm_dimension: {}, input_lwe_dimension: {input_lwe_dimension:?}.",
expected_len,
container.container_len(),
output_lwe_dimension.0 + output_cm_dimension.0,
);
Self {
data: container,
decomp_base_log,
decomp_level_count,
input_lwe_dimension,
output_lwe_dimension,
output_cm_dimension,
ciphertext_modulus,
}
}
pub fn decomposition_base_log(&self) -> DecompositionBaseLog {
self.decomp_base_log
}
pub fn decomposition_level_count(&self) -> DecompositionLevelCount {
self.decomp_level_count
}
pub fn input_key_lwe_dimension(&self) -> LweDimension {
self.input_lwe_dimension
}
pub fn output_lwe_dimension(&self) -> LweDimension {
self.output_lwe_dimension
}
pub fn output_cm_dimension(&self) -> CmDimension {
self.output_cm_dimension
}
pub fn input_key_element_encrypted_size(&self) -> usize {
cm_lwe_packing_key_part_input_key_element_encrypted_size(
self.decomp_level_count,
self.output_lwe_dimension,
self.output_cm_dimension,
)
}
pub fn as_view(&self) -> CmLwePackingKeyPartView<'_, Scalar> {
CmLwePackingKeyPart::from_container(
self.as_ref(),
self.decomp_base_log,
self.decomp_level_count,
self.input_lwe_dimension,
self.output_lwe_dimension,
self.output_cm_dimension,
self.ciphertext_modulus,
)
}
pub fn into_container(self) -> C {
self.data
}
pub fn as_cm_lwe_ciphertext_list(&self) -> CmLweCiphertextListView<'_, Scalar> {
CmLweCiphertextListView::from_container(
self.as_ref(),
self.output_lwe_dimension(),
self.output_cm_dimension,
self.ciphertext_modulus(),
)
}
pub fn ciphertext_modulus(&self) -> CiphertextModulus<C::Element> {
self.ciphertext_modulus
}
}
impl<Scalar: UnsignedInteger, C: ContainerMut<Element = Scalar>> CmLwePackingKeyPart<C> {
pub fn as_mut_view(&mut self) -> CmLwePackingKeyPartMutView<'_, Scalar> {
let Self {
data: _,
decomp_base_log,
decomp_level_count,
input_lwe_dimension,
output_lwe_dimension,
output_cm_dimension,
ciphertext_modulus,
} = *self;
CmLwePackingKeyPart::from_container(
self.as_mut(),
decomp_base_log,
decomp_level_count,
input_lwe_dimension,
output_lwe_dimension,
output_cm_dimension,
ciphertext_modulus,
)
}
pub fn as_mut_cm_lwe_ciphertext_list(&mut self) -> CmLweCiphertextListMutView<'_, Scalar> {
let Self {
output_lwe_dimension,
output_cm_dimension,
ciphertext_modulus,
..
} = *self;
CmLweCiphertextListMutView::from_container(
self.as_mut(),
output_lwe_dimension,
output_cm_dimension,
ciphertext_modulus,
)
}
}
pub type CmLwePackingKeyPartOwned<Scalar> = CmLwePackingKeyPart<Vec<Scalar>>;
pub type CmLwePackingKeyPartView<'data, Scalar> = CmLwePackingKeyPart<&'data [Scalar]>;
pub type CmLwePackingKeyPartMutView<'data, Scalar> = CmLwePackingKeyPart<&'data mut [Scalar]>;
impl<Scalar: UnsignedInteger> CmLwePackingKeyPartOwned<Scalar> {
pub fn new(
fill_with: Scalar,
decomp_base_log: DecompositionBaseLog,
decomp_level_count: DecompositionLevelCount,
input_key_lwe_dimension: LweDimension,
output_lwe_dimension: LweDimension,
output_cm_dimension: CmDimension,
ciphertext_modulus: CiphertextModulus<Scalar>,
) -> Self {
let expected_len = cm_lwe_packing_key_part_size(
decomp_level_count,
input_key_lwe_dimension,
output_lwe_dimension,
output_cm_dimension,
);
Self::from_container(
vec![fill_with; expected_len],
decomp_base_log,
decomp_level_count,
input_key_lwe_dimension,
output_lwe_dimension,
output_cm_dimension,
ciphertext_modulus,
)
}
}
#[derive(Clone, Copy)]
pub struct CmLwePackingKeyPartCreationMetadata<Scalar: UnsignedInteger> {
pub decomp_base_log: DecompositionBaseLog,
pub decomp_level_count: DecompositionLevelCount,
pub input_lwe_dimension: LweDimension,
pub output_lwe_dimension: LweDimension,
pub output_cm_dimension: CmDimension,
pub ciphertext_modulus: CiphertextModulus<Scalar>,
}
impl<Scalar: UnsignedInteger, C: Container<Element = Scalar>> CreateFrom<C>
for CmLwePackingKeyPart<C>
{
type Metadata = CmLwePackingKeyPartCreationMetadata<Scalar>;
#[inline]
fn create_from(from: C, meta: Self::Metadata) -> Self {
let CmLwePackingKeyPartCreationMetadata {
decomp_base_log,
decomp_level_count,
input_lwe_dimension,
output_lwe_dimension,
output_cm_dimension,
ciphertext_modulus,
} = meta;
Self::from_container(
from,
decomp_base_log,
decomp_level_count,
input_lwe_dimension,
output_lwe_dimension,
output_cm_dimension,
ciphertext_modulus,
)
}
}
impl<Scalar: UnsignedInteger, C: Container<Element = Scalar>> ContiguousEntityContainer
for CmLwePackingKeyPart<C>
{
type Element = C::Element;
type EntityViewMetadata = CmLweCiphertextListCreationMetadata<Self::Element>;
type EntityView<'this>
= CmLweCiphertextListView<'this, Self::Element>
where
Self: 'this;
type SelfViewMetadata = CmLwePackingKeyPartCreationMetadata<Self::Element>;
type SelfView<'this>
= CmLwePackingKeyPartView<'this, Self::Element>
where
Self: 'this;
fn get_entity_view_creation_metadata(&self) -> Self::EntityViewMetadata {
CmLweCiphertextListCreationMetadata {
lwe_dimension: self.output_lwe_dimension(),
cm_dimension: self.output_cm_dimension,
ciphertext_modulus: self.ciphertext_modulus(),
}
}
fn get_entity_view_pod_size(&self) -> usize {
self.input_key_element_encrypted_size()
}
fn get_self_view_creation_metadata(&self) -> Self::SelfViewMetadata {
let Self {
decomp_base_log,
decomp_level_count,
input_lwe_dimension,
output_lwe_dimension,
output_cm_dimension,
ciphertext_modulus,
..
} = *self;
CmLwePackingKeyPartCreationMetadata {
decomp_base_log,
decomp_level_count,
input_lwe_dimension,
output_lwe_dimension,
output_cm_dimension,
ciphertext_modulus,
}
}
}
impl<Scalar: UnsignedInteger, C: ContainerMut<Element = Scalar>> ContiguousEntityContainerMut
for CmLwePackingKeyPart<C>
{
type EntityMutView<'this>
= CmLweCiphertextListMutView<'this, Self::Element>
where
Self: 'this;
type SelfMutView<'this>
= CmLwePackingKeyPartMutView<'this, Self::Element>
where
Self: 'this;
}

View File

@@ -0,0 +1,25 @@
pub mod cm_bootstrap_key;
pub mod cm_fourier_ggsw_ciphertext;
pub mod cm_ggsw_ciphertext;
pub mod cm_ggsw_ciphertext_list;
pub mod cm_glwe_ciphertext;
pub mod cm_glwe_ciphertext_list;
pub mod cm_lwe_bootstrap_key;
pub mod cm_lwe_ciphertext;
pub mod cm_lwe_ciphertext_list;
pub mod cm_lwe_keyswitch_key;
pub mod cm_lwe_packing_key;
pub mod cm_lwe_packing_key_part;
pub use cm_bootstrap_key::*;
pub use cm_fourier_ggsw_ciphertext::*;
pub use cm_ggsw_ciphertext::*;
pub use cm_ggsw_ciphertext_list::*;
pub use cm_glwe_ciphertext::*;
pub use cm_glwe_ciphertext_list::*;
pub use cm_lwe_bootstrap_key::*;
pub use cm_lwe_ciphertext::*;
pub use cm_lwe_ciphertext_list::*;
pub use cm_lwe_keyswitch_key::*;
pub use cm_lwe_packing_key::*;
pub use cm_lwe_packing_key_part::*;

View File

@@ -11,7 +11,7 @@ use crate::core_crypto::fft_impl::fft64::math::decomposition::DecompositionLevel
use crate::core_crypto::fft_impl::fft64::math::fft::{FftView, FourierPolynomialList};
use crate::core_crypto::fft_impl::fft64::math::polynomial::FourierPolynomialMutView;
use aligned_vec::{avec, ABox};
use dyn_stack::{PodStack, StackReq};
use dyn_stack::PodStack;
use tfhe_fft::c64;
/// A pseudo GGSW ciphertext in the Fourier domain.
@@ -260,12 +260,6 @@ impl<'a> PseudoFourierGgswCiphertextView<'a> {
}
}
/// Return the required memory for
/// [`PseudoFourierGgswCiphertextMutView::fill_with_forward_fourier`].
pub fn fill_with_forward_fourier_scratch(fft: FftView<'_>) -> StackReq {
fft.forward_scratch()
}
impl PseudoFourierGgswCiphertextMutView<'_> {
/// Fill a GGSW ciphertext with the Fourier transform of a GGSW ciphertext in the standard
/// domain.

View File

@@ -1,7 +1,9 @@
pub mod common_mask_entities;
pub mod fourier_pseudo_ggsw_ciphertext;
pub mod lwe_shrinking_keyswitch_key;
pub mod pseudo_ggsw_ciphertext;
pub use common_mask_entities::*;
pub use fourier_pseudo_ggsw_ciphertext::*;
pub use lwe_shrinking_keyswitch_key::*;
pub use pseudo_ggsw_ciphertext::*;

View File

@@ -108,14 +108,14 @@ fn plans() -> &'static PlanMap {
}
pub fn setup_custom_fft_plan(plan: Plan) {
let fft_size = FourierPolynomialSize(plan.fft_size());
let std_poly_size = fft_size.to_standard_polynomial_size();
let base_n = FourierPolynomialSize(plan.fft_size());
let n = base_n.to_standard_polynomial_size();
let plan = Arc::new((Twisties::new(fft_size.0), plan));
let plan = Arc::new((Twisties::new(base_n.0), plan));
let global_plans = plans();
global_plans.set(std_poly_size, plan);
global_plans.set(n, plan);
}
/// Return the input slice, cast to the same type.

View File

@@ -56,7 +56,7 @@ impl<Scalar: UnsignedInteger + CastFrom<u64>> ShortintEncoding<Scalar> {
}
impl<Scalar: UnsignedInteger + CastFrom<u64>> ShortintEncoding<Scalar> {
/// Return the cleartext space including the space for the [`Self::padding_bit`] if it is set to
/// Return the cleatext space including the space for the [`Self::padding_bit`] if it is set to
/// [`PaddingBit::Yes`].
pub(crate) fn full_cleartext_space(&self) -> Scalar {
let cleartext_modulus = self.cleartext_space_without_padding();
@@ -69,7 +69,7 @@ impl<Scalar: UnsignedInteger + CastFrom<u64>> ShortintEncoding<Scalar> {
}
}
/// Return the cleartext space defined by the [`Self::message_modulus`] and
/// Return the cleatext space defined by the [`Self::message_modulus`] and
/// [`Self::carry_modulus`], not taking the value of the [`Self::padding_bit`] into account.
pub(crate) fn cleartext_space_without_padding(&self) -> Scalar {
(self.message_modulus.0 * self.carry_modulus.0).cast_into()

View File

@@ -1130,7 +1130,7 @@ pub mod test_utils {
/// to 0 to keep the carry free.
/// output_modulus: the output cleartext space, continuing the above example, it must contain
/// the padding bit, so for 4 bits of cleartext this is actually 2^(1 + 4)==32
pub fn cleartext_prf(
pub fn cleatext_prf(
input_cleartext: u64,
random_bits_count: u64,
output_modulus: u64,
@@ -1163,7 +1163,7 @@ pub mod test_utils {
#[cfg(test)]
pub(crate) mod test {
use super::test_utils::cleartext_prf;
use super::test_utils::cleatext_prf;
use super::*;
use crate::core_crypto::commons::math::random::Seed;
use crate::core_crypto::prelude::{decrypt_lwe_ciphertext, CastInto, LweSecretKeyView};
@@ -1244,7 +1244,7 @@ pub(crate) mod test {
// includes padding bit
let output_modulus = 2 * params.message_modulus().0 * params.carry_modulus().0;
let expected_output = cleartext_prf(
let expected_output = cleatext_prf(
plain_prf_input,
random_bits_count,
output_modulus,
@@ -1477,67 +1477,4 @@ pub(crate) mod test {
}
}
}
#[test]
fn oprf_test_uniformity_bits_ci_run_filter() {
let sample_count: usize = 100_000;
let p_value_limit: f64 = 0.000_01;
use crate::shortint::gen_keys;
use crate::shortint::parameters::test_params::{
TEST_PARAM_MESSAGE_2_CARRY_2_KS32_PBS_TUNIFORM_2M128,
TEST_PARAM_MULTI_BIT_GROUP_3_MESSAGE_2_CARRY_2_KS_PBS_GAUSSIAN_2M128,
};
use crate::shortint::parameters::PARAM_MESSAGE_2_CARRY_2_KS_PBS;
for params in [
ShortintParameterSet::from(
TEST_PARAM_MULTI_BIT_GROUP_3_MESSAGE_2_CARRY_2_KS_PBS_GAUSSIAN_2M128,
),
ShortintParameterSet::from(PARAM_MESSAGE_2_CARRY_2_KS_PBS),
ShortintParameterSet::from(TEST_PARAM_MESSAGE_2_CARRY_2_KS32_PBS_TUNIFORM_2M128),
] {
let (ck, sk) = gen_keys(params);
let oprf_ck = OprfPrivateKey::new(&ck);
let oprf_sk = OprfServerKey::new(&oprf_ck, &ck).unwrap();
let random_bits_per_block = sk.message_modulus.0.ilog2() as u64;
for random_bits_count in [3u64, 4] {
let expected_num_blocks =
random_bits_count.div_ceil(random_bits_per_block) as usize;
test_uniformity(
sample_count,
p_value_limit,
1 << random_bits_count,
|seed| {
let seed = (seed as u128).to_le_bytes();
let blocks = oprf_sk.generate_oblivious_pseudo_random_bits(
seed.as_slice(),
random_bits_count,
&sk,
);
let mut combined: u64 = 0;
let mut shift = 0u64;
for (i, block) in blocks.iter().enumerate() {
let decrypted = ck.decrypt_message_and_carry(block);
let block_bits = bits_in_block(
i,
expected_num_blocks,
random_bits_count,
random_bits_per_block,
);
combined |= decrypted << shift;
shift += block_bits;
}
combined
},
);
}
}
}
}