From 71db96a912dbf752b399bb7b2f0f443ca813b70e Mon Sep 17 00:00:00 2001 From: Carl-Zama Date: Thu, 13 Feb 2025 16:18:52 +0100 Subject: [PATCH] feat(core): add glwe keyswitch --- .../core_crypto/algorithms/glwe_keyswitch.rs | 301 +++++++++++ .../glwe_keyswitch_key_generation.rs | 351 ++++++++++++ tfhe/src/core_crypto/algorithms/mod.rs | 4 + .../algorithms/polynomial_algorithms.rs | 61 +++ .../commons/math/decomposition/decomposer.rs | 26 + .../entities/glwe_keyswitch_key.rs | 505 ++++++++++++++++++ tfhe/src/core_crypto/entities/mod.rs | 2 + 7 files changed, 1250 insertions(+) create mode 100644 tfhe/src/core_crypto/algorithms/glwe_keyswitch.rs create mode 100644 tfhe/src/core_crypto/algorithms/glwe_keyswitch_key_generation.rs create mode 100644 tfhe/src/core_crypto/entities/glwe_keyswitch_key.rs diff --git a/tfhe/src/core_crypto/algorithms/glwe_keyswitch.rs b/tfhe/src/core_crypto/algorithms/glwe_keyswitch.rs new file mode 100644 index 0000000000..5da18fc6cf --- /dev/null +++ b/tfhe/src/core_crypto/algorithms/glwe_keyswitch.rs @@ -0,0 +1,301 @@ +//! Module containing primitives pertaining to [`GLWE ciphertext +//! keyswitch`](`GlweKeyswitchKey#glwe-keyswitch`). + +use crate::core_crypto::algorithms::polynomial_algorithms::*; +use crate::core_crypto::commons::math::decomposition::{ + SignedDecomposer, SignedDecomposerNonNative, +}; +use crate::core_crypto::commons::numeric::UnsignedInteger; +use crate::core_crypto::commons::traits::*; +use crate::core_crypto::entities::*; + +/// Keyswitch a [`GLWE ciphertext`](`GlweCiphertext`) encrypted under a +/// [`GLWE secret key`](`GlweSecretKey`) to another [`GLWE secret key`](`GlweSecretKey`). +/// +/// # Formal Definition +/// +/// See [`GLWE keyswitch key`](`GlweKeyswitchKey#glwe-keyswitch`). +/// +/// # Example +/// +/// ``` +/// use tfhe::core_crypto::prelude::*; +/// +/// // DISCLAIMER: these toy example parameters are not guaranteed to be secure or yield correct +/// // computations +/// // Define parameters for GlweKeyswitchKey creation +/// let input_glwe_dimension = GlweDimension(4); +/// let poly_size = PolynomialSize(512); +/// let glwe_noise_distribution = Gaussian::from_dispersion_parameter( +/// StandardDev(0.00000000000000000000007069849454709433), +/// 0.0, +/// ); +/// let output_glwe_dimension = GlweDimension(3); +/// let decomp_base_log = DecompositionBaseLog(21); +/// let decomp_level_count = DecompositionLevelCount(2); +/// let ciphertext_modulus = CiphertextModulus::new_native(); +/// let delta = 1 << 59; +/// +/// // Create the PRNG +/// let mut seeder = new_seeder(); +/// let seeder = seeder.as_mut(); +/// let mut encryption_generator = +/// EncryptionRandomGenerator::::new(seeder.seed(), seeder); +/// let mut secret_generator = SecretRandomGenerator::::new(seeder.seed()); +/// +/// // Create the LweSecretKey +/// let input_glwe_secret_key = allocate_and_generate_new_binary_glwe_secret_key( +/// input_glwe_dimension, +/// poly_size, +/// &mut secret_generator, +/// ); +/// let output_glwe_secret_key = allocate_and_generate_new_binary_glwe_secret_key( +/// output_glwe_dimension, +/// poly_size, +/// &mut secret_generator, +/// ); +/// +/// let ksk = allocate_and_generate_new_glwe_keyswitch_key( +/// &input_glwe_secret_key, +/// &output_glwe_secret_key, +/// decomp_base_log, +/// decomp_level_count, +/// glwe_noise_distribution, +/// ciphertext_modulus, +/// &mut encryption_generator, +/// ); +/// +/// // Create the plaintext +/// let msg = 3u64; +/// let plaintext_list = PlaintextList::new(msg * delta, PlaintextCount(poly_size.0)); +/// +/// // Create a new GlweCiphertext +/// let mut input_glwe = GlweCiphertext::new( +/// 0u64, +/// input_glwe_dimension.to_glwe_size(), +/// poly_size, +/// ciphertext_modulus, +/// ); +/// +/// encrypt_glwe_ciphertext( +/// &input_glwe_secret_key, +/// &mut input_glwe, +/// &plaintext_list, +/// glwe_noise_distribution, +/// &mut encryption_generator, +/// ); +/// +/// let mut output_glwe = GlweCiphertext::new( +/// 0u64, +/// output_glwe_secret_key.glwe_dimension().to_glwe_size(), +/// output_glwe_secret_key.polynomial_size(), +/// ciphertext_modulus, +/// ); +/// +/// keyswitch_glwe_ciphertext(&ksk, &input_glwe, &mut output_glwe); +/// +/// // Round and remove encoding +/// // First create a decomposer working on the high 5 bits corresponding to our encoding. +/// let decomposer = SignedDecomposer::new(DecompositionBaseLog(5), DecompositionLevelCount(1)); +/// +/// let mut output_plaintext_list = PlaintextList::new(0u64, plaintext_list.plaintext_count()); +/// +/// decrypt_glwe_ciphertext( +/// &output_glwe_secret_key, +/// &output_glwe, +/// &mut output_plaintext_list, +/// ); +/// +/// // Get the raw vector +/// let cleartext_list: Vec<_> = output_plaintext_list +/// .as_ref() +/// .iter() +/// .map(|elt| decomposer.decode_plaintext(*elt)) +/// .collect(); +/// +/// // Check we recovered the original message for each plaintext we encrypted +/// cleartext_list.iter().for_each(|&elt| assert_eq!(elt, msg)); +/// ``` +pub fn keyswitch_glwe_ciphertext( + glwe_keyswitch_key: &GlweKeyswitchKey, + input_glwe_ciphertext: &GlweCiphertext, + output_glwe_ciphertext: &mut GlweCiphertext, +) where + Scalar: UnsignedInteger, + KSKCont: Container, + InputCont: Container, + OutputCont: ContainerMut, +{ + if glwe_keyswitch_key + .ciphertext_modulus() + .is_compatible_with_native_modulus() + { + keyswitch_glwe_ciphertext_native_mod_compatible( + glwe_keyswitch_key, + input_glwe_ciphertext, + output_glwe_ciphertext, + ) + } else { + keyswitch_glwe_ciphertext_other_mod( + glwe_keyswitch_key, + input_glwe_ciphertext, + output_glwe_ciphertext, + ) + } +} + +pub fn keyswitch_glwe_ciphertext_native_mod_compatible( + glwe_keyswitch_key: &GlweKeyswitchKey, + input_glwe_ciphertext: &GlweCiphertext, + output_glwe_ciphertext: &mut GlweCiphertext, +) where + Scalar: UnsignedInteger, + KSKCont: Container, + InputCont: Container, + OutputCont: ContainerMut, +{ + assert!( + glwe_keyswitch_key.input_key_glwe_dimension() + == input_glwe_ciphertext.glwe_size().to_glwe_dimension(), + "Mismatched input GlweDimension. \ + GlweKeyswitchKey input GlweDimension: {:?}, input GlweCiphertext GlweDimension {:?}.", + glwe_keyswitch_key.input_key_glwe_dimension(), + input_glwe_ciphertext.glwe_size().to_glwe_dimension(), + ); + assert!( + glwe_keyswitch_key.output_key_glwe_dimension() + == output_glwe_ciphertext.glwe_size().to_glwe_dimension(), + "Mismatched output GlweDimension. \ + GlweKeyswitchKey output GlweDimension: {:?}, output GlweCiphertext GlweDimension {:?}.", + glwe_keyswitch_key.output_key_glwe_dimension(), + output_glwe_ciphertext.glwe_size().to_glwe_dimension(), + ); + assert!( + glwe_keyswitch_key.polynomial_size() == input_glwe_ciphertext.polynomial_size(), + "Mismatched input PolynomialSize. \ + GlweKeyswithcKey input PolynomialSize: {:?}, input GlweCiphertext PolynomialSize {:?}.", + glwe_keyswitch_key.polynomial_size(), + input_glwe_ciphertext.polynomial_size(), + ); + assert!( + glwe_keyswitch_key.polynomial_size() == output_glwe_ciphertext.polynomial_size(), + "Mismatched output PolynomialSize. \ + GlweKeyswitchKey output PolynomialSize: {:?}, output GlweCiphertext PolynomialSize {:?}.", + glwe_keyswitch_key.polynomial_size(), + output_glwe_ciphertext.polynomial_size(), + ); + assert!(glwe_keyswitch_key + .ciphertext_modulus() + .is_compatible_with_native_modulus()); + + // Clear the output ciphertext, as it will get updated gradually + output_glwe_ciphertext.as_mut().fill(Scalar::ZERO); + + // Copy the input body to the output ciphertext + output_glwe_ciphertext + .get_mut_body() + .as_mut() + .copy_from_slice(input_glwe_ciphertext.get_body().as_ref()); + + // We instantiate a decomposer + let decomposer = SignedDecomposer::new( + glwe_keyswitch_key.decomposition_base_log(), + glwe_keyswitch_key.decomposition_level_count(), + ); + + for (keyswitch_key_block, input_mask_element) in glwe_keyswitch_key + .iter() + .zip(input_glwe_ciphertext.get_mask().as_polynomial_list().iter()) + { + let mut decomposition_iter = decomposer.decompose_slice(input_mask_element.as_ref()); + // loop over the number of levels + for level_key_ciphertext in keyswitch_key_block.iter() { + let decomposed = decomposition_iter.next_term().unwrap(); + polynomial_list_wrapping_sub_scalar_mul_assign( + &mut output_glwe_ciphertext.as_mut_polynomial_list(), + &level_key_ciphertext.as_polynomial_list(), + &Polynomial::from_container(decomposed.as_slice()), + ); + } + } +} + +pub fn keyswitch_glwe_ciphertext_other_mod( + glwe_keyswitch_key: &GlweKeyswitchKey, + input_glwe_ciphertext: &GlweCiphertext, + output_glwe_ciphertext: &mut GlweCiphertext, +) where + Scalar: UnsignedInteger, + KSKCont: Container, + InputCont: Container, + OutputCont: ContainerMut, +{ + assert!( + glwe_keyswitch_key.input_key_glwe_dimension() + == input_glwe_ciphertext.glwe_size().to_glwe_dimension(), + "Mismatched input GlweDimension. \ + GlweKeyswitchKey input GlweDimension: {:?}, input GlweCiphertext GlweDimension {:?}.", + glwe_keyswitch_key.input_key_glwe_dimension(), + input_glwe_ciphertext.glwe_size().to_glwe_dimension(), + ); + assert!( + glwe_keyswitch_key.output_key_glwe_dimension() + == output_glwe_ciphertext.glwe_size().to_glwe_dimension(), + "Mismatched output GlweDimension. \ + GlweKeyswitchKey output GlweDimension: {:?}, output GlweCiphertext GlweDimension {:?}.", + glwe_keyswitch_key.output_key_glwe_dimension(), + output_glwe_ciphertext.glwe_size().to_glwe_dimension(), + ); + assert!( + glwe_keyswitch_key.polynomial_size() == input_glwe_ciphertext.polynomial_size(), + "Mismatched input PolynomialSize. \ + GlweKeyswithcKey input PolynomialSize: {:?}, input GlweCiphertext PolynomialSize {:?}.", + glwe_keyswitch_key.polynomial_size(), + input_glwe_ciphertext.polynomial_size(), + ); + assert!( + glwe_keyswitch_key.polynomial_size() == output_glwe_ciphertext.polynomial_size(), + "Mismatched output PolynomialSize. \ + GlweKeyswitchKey output PolynomialSize: {:?}, output GlweCiphertext PolynomialSize {:?}.", + glwe_keyswitch_key.polynomial_size(), + output_glwe_ciphertext.polynomial_size(), + ); + let ciphertext_modulus = glwe_keyswitch_key.ciphertext_modulus(); + assert!(!ciphertext_modulus.is_compatible_with_native_modulus()); + + // Clear the output ciphertext, as it will get updated gradually + output_glwe_ciphertext.as_mut().fill(Scalar::ZERO); + + // Copy the input body to the output ciphertext (no need to use non native addition here) + polynomial_wrapping_add_assign( + &mut output_glwe_ciphertext.get_mut_body().as_mut_polynomial(), + &input_glwe_ciphertext.get_body().as_polynomial(), + ); + + // We instantiate a decomposer + let decomposer = SignedDecomposerNonNative::new( + glwe_keyswitch_key.decomposition_base_log(), + glwe_keyswitch_key.decomposition_level_count(), + ciphertext_modulus, + ); + + let mut scalar_poly = Polynomial::new(Scalar::ZERO, input_glwe_ciphertext.polynomial_size()); + + for (keyswitch_key_block, input_mask_element) in glwe_keyswitch_key + .iter() + .zip(input_glwe_ciphertext.get_mask().as_polynomial_list().iter()) + { + let mut decomposition_iter = decomposer.decompose_slice(input_mask_element.as_ref()); + // loop over the number of levels + for level_key_ciphertext in keyswitch_key_block.iter() { + let decomposed = decomposition_iter.next_term().unwrap(); + decomposed.modular_value(scalar_poly.as_mut()); + polynomial_list_wrapping_sub_scalar_mul_assign_custom_mod( + &mut output_glwe_ciphertext.as_mut_polynomial_list(), + &level_key_ciphertext.as_polynomial_list(), + &scalar_poly, + ciphertext_modulus.get_custom_modulus().cast_into(), + ); + } + } +} diff --git a/tfhe/src/core_crypto/algorithms/glwe_keyswitch_key_generation.rs b/tfhe/src/core_crypto/algorithms/glwe_keyswitch_key_generation.rs new file mode 100644 index 0000000000..53000fd025 --- /dev/null +++ b/tfhe/src/core_crypto/algorithms/glwe_keyswitch_key_generation.rs @@ -0,0 +1,351 @@ +//! Module containing primitives pertaining to [`GLWE keyswitch key generation`](`GlweKeyswitchKey`) + +use crate::core_crypto::algorithms::slice_algorithms::slice_wrapping_scalar_div_assign; +use crate::core_crypto::algorithms::*; +use crate::core_crypto::commons::generators::EncryptionRandomGenerator; +use crate::core_crypto::commons::math::decomposition::{ + DecompositionLevel, DecompositionTermSlice, DecompositionTermSliceNonNative, +}; +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::*; + +/// Fill a [`GLWE keyswitch key`](`GlweKeyswitchKey`) with an actual keyswitching key constructed +/// from an input and an output key [`GLWE secret key`](`GlweSecretKey`). +/// +/// ``` +/// use tfhe::core_crypto::prelude::*; +/// +/// // DISCLAIMER: these toy example parameters are not guaranteed to be secure or yield correct +/// // computations +/// // Define parameters for GlweKeyswitchKey creation +/// let input_glwe_dimension = GlweDimension(2); +/// let polynomial_size = PolynomialSize(1024); +/// let glwe_noise_distribution = +/// Gaussian::from_dispersion_parameter(StandardDev(0.000007069849454709433), 0.0); +/// let output_glwe_dimension = GlweDimension(1); +/// let decomp_base_log = DecompositionBaseLog(3); +/// let decomp_level_count = DecompositionLevelCount(5); +/// let ciphertext_modulus = CiphertextModulus::new_native(); +/// +/// // Create the PRNG +/// let mut seeder = new_seeder(); +/// let seeder = seeder.as_mut(); +/// let mut encryption_generator = +/// EncryptionRandomGenerator::::new(seeder.seed(), seeder); +/// let mut secret_generator = SecretRandomGenerator::::new(seeder.seed()); +/// +/// // Create the GlweSecretKey +/// let input_glwe_secret_key = allocate_and_generate_new_binary_glwe_secret_key( +/// input_glwe_dimension, +/// polynomial_size, +/// &mut secret_generator, +/// ); +/// let output_glwe_secret_key = allocate_and_generate_new_binary_glwe_secret_key( +/// output_glwe_dimension, +/// polynomial_size, +/// &mut secret_generator, +/// ); +/// +/// let mut ksk = GlweKeyswitchKey::new( +/// 0u64, +/// decomp_base_log, +/// decomp_level_count, +/// input_glwe_dimension, +/// output_glwe_dimension, +/// polynomial_size, +/// ciphertext_modulus, +/// ); +/// +/// generate_glwe_keyswitch_key( +/// &input_glwe_secret_key, +/// &output_glwe_secret_key, +/// &mut ksk, +/// glwe_noise_distribution, +/// &mut encryption_generator, +/// ); +/// +/// assert!(!ksk.as_ref().iter().all(|&x| x == 0)); +/// ``` +pub fn generate_glwe_keyswitch_key< + Scalar, + NoiseDistribution, + InputKeyCont, + OutputKeyCont, + KSKeyCont, + Gen, +>( + input_glwe_sk: &GlweSecretKey, + output_glwe_sk: &GlweSecretKey, + glwe_keyswitch_key: &mut GlweKeyswitchKey, + noise_distribution: NoiseDistribution, + generator: &mut EncryptionRandomGenerator, +) where + Scalar: Encryptable, + NoiseDistribution: Distribution, + InputKeyCont: Container, + OutputKeyCont: Container, + KSKeyCont: ContainerMut, + Gen: ByteRandomGenerator, +{ + let ciphertext_modulus = glwe_keyswitch_key.ciphertext_modulus(); + + if ciphertext_modulus.is_compatible_with_native_modulus() { + generate_glwe_keyswitch_key_native_mod_compatible( + input_glwe_sk, + output_glwe_sk, + glwe_keyswitch_key, + noise_distribution, + generator, + ) + } else { + generate_glwe_keyswitch_key_other_mod( + input_glwe_sk, + output_glwe_sk, + glwe_keyswitch_key, + noise_distribution, + generator, + ) + } +} + +pub fn generate_glwe_keyswitch_key_native_mod_compatible< + Scalar, + NoiseDistribution, + InputKeyCont, + OutputKeyCont, + KSKeyCont, + Gen, +>( + input_glwe_sk: &GlweSecretKey, + output_glwe_sk: &GlweSecretKey, + glwe_keyswitch_key: &mut GlweKeyswitchKey, + noise_distribution: NoiseDistribution, + generator: &mut EncryptionRandomGenerator, +) where + Scalar: Encryptable, + NoiseDistribution: Distribution, + InputKeyCont: Container, + OutputKeyCont: Container, + KSKeyCont: ContainerMut, + Gen: ByteRandomGenerator, +{ + assert!( + glwe_keyswitch_key.input_key_glwe_dimension() == input_glwe_sk.glwe_dimension(), + "The destination GlweKeyswitchKey input GlweDimension is not equal \ + to the input GlweSecretKey GlweDimension. Destination: {:?}, input: {:?}", + glwe_keyswitch_key.input_key_glwe_dimension(), + input_glwe_sk.glwe_dimension() + ); + assert!( + glwe_keyswitch_key.output_key_glwe_dimension() == output_glwe_sk.glwe_dimension(), + "The destination GlweKeyswitchKey output GlweDimension is not equal \ + to the output GlweSecretKey GlweDimension. Destination: {:?}, output: {:?}", + glwe_keyswitch_key.output_key_glwe_dimension(), + input_glwe_sk.glwe_dimension() + ); + assert!( + glwe_keyswitch_key.polynomial_size() == input_glwe_sk.polynomial_size(), + "The destination GlweKeyswitchKey input PolynomialSize is not equal \ + to the input GlweSecretKey PolynomialSize. Destination: {:?}, input: {:?}", + glwe_keyswitch_key.polynomial_size(), + input_glwe_sk.polynomial_size(), + ); + assert!( + glwe_keyswitch_key.polynomial_size() == output_glwe_sk.polynomial_size(), + "The destination GlweKeyswitchKey output PolynomialSize is not equal \ + to the output GlweSecretKey PolynomialSize. Destination: {:?}, output: {:?}", + glwe_keyswitch_key.polynomial_size(), + output_glwe_sk.polynomial_size(), + ); + + let decomp_base_log = glwe_keyswitch_key.decomposition_base_log(); + let decomp_level_count = glwe_keyswitch_key.decomposition_level_count(); + let ciphertext_modulus = glwe_keyswitch_key.ciphertext_modulus(); + assert!(ciphertext_modulus.is_compatible_with_native_modulus()); + + // Iterate over the input key elements and the destination glwe_keyswitch_key memory + for (input_key_polynomial, mut keyswitch_key_block) in input_glwe_sk + .as_polynomial_list() + .iter() + .zip(glwe_keyswitch_key.iter_mut()) + { + // The plaintexts used to encrypt a key element will be stored in this buffer + let mut decomposition_polynomials_buffer = PolynomialList::new( + Scalar::ZERO, + input_glwe_sk.polynomial_size(), + PolynomialCount(decomp_level_count.0), + ); + + // We fill the buffer with the powers of the key elmements + for (level, mut message_polynomial) in (1..=decomp_level_count.0) + .rev() + .map(DecompositionLevel) + .zip(decomposition_polynomials_buffer.as_mut_view().iter_mut()) + { + DecompositionTermSlice::new(level, decomp_base_log, input_key_polynomial.as_ref()) + .fill_slice_with_recomposition_summand(message_polynomial.as_mut()); + + slice_wrapping_scalar_div_assign( + message_polynomial.as_mut(), + ciphertext_modulus.get_power_of_two_scaling_to_native_torus(), + ); + } + + let decomposition_plaintexts_buffer = + PlaintextList::from_container(decomposition_polynomials_buffer.into_container()); + + encrypt_glwe_ciphertext_list( + output_glwe_sk, + &mut keyswitch_key_block, + &decomposition_plaintexts_buffer, + noise_distribution, + generator, + ); + } +} + +pub fn generate_glwe_keyswitch_key_other_mod< + Scalar, + NoiseDistribution, + InputKeyCont, + OutputKeyCont, + KSKeyCont, + Gen, +>( + input_glwe_sk: &GlweSecretKey, + output_glwe_sk: &GlweSecretKey, + glwe_keyswitch_key: &mut GlweKeyswitchKey, + noise_distribution: NoiseDistribution, + generator: &mut EncryptionRandomGenerator, +) where + Scalar: Encryptable, + NoiseDistribution: Distribution, + InputKeyCont: Container, + OutputKeyCont: Container, + KSKeyCont: ContainerMut, + Gen: ByteRandomGenerator, +{ + assert!( + glwe_keyswitch_key.input_key_glwe_dimension() == input_glwe_sk.glwe_dimension(), + "The destination GlweKeyswitchKey input GlweDimension is not equal \ + to the input GlweSecretKey GlweDimension. Destination: {:?}, input: {:?}", + glwe_keyswitch_key.input_key_glwe_dimension(), + input_glwe_sk.glwe_dimension() + ); + assert!( + glwe_keyswitch_key.output_key_glwe_dimension() == output_glwe_sk.glwe_dimension(), + "The destination GlweKeyswitchKey output GlweDimension is not equal \ + to the output GlweSecretKey GlweDimension. Destination: {:?}, output: {:?}", + glwe_keyswitch_key.output_key_glwe_dimension(), + input_glwe_sk.glwe_dimension() + ); + assert!( + glwe_keyswitch_key.polynomial_size() == input_glwe_sk.polynomial_size(), + "The destination GlweKeyswitchKey input PolynomialSize is not equal \ + to the input GlweSecretKey PolynomialSize. Destination: {:?}, input: {:?}", + glwe_keyswitch_key.polynomial_size(), + input_glwe_sk.polynomial_size(), + ); + assert!( + glwe_keyswitch_key.polynomial_size() == output_glwe_sk.polynomial_size(), + "The destination GlweKeyswitchKey output PolynomialSize is not equal \ + to the output GlweSecretKey PolynomialSize. Destination: {:?}, output: {:?}", + glwe_keyswitch_key.polynomial_size(), + output_glwe_sk.polynomial_size(), + ); + + let decomp_base_log = glwe_keyswitch_key.decomposition_base_log(); + let decomp_level_count = glwe_keyswitch_key.decomposition_level_count(); + let ciphertext_modulus = glwe_keyswitch_key.ciphertext_modulus(); + assert!(!ciphertext_modulus.is_compatible_with_native_modulus()); + + // Iterate over the input key elements and the destination glwe_keyswitch_key memory + for (input_key_polynomial, mut keyswitch_key_block) in input_glwe_sk + .as_polynomial_list() + .iter() + .zip(glwe_keyswitch_key.iter_mut()) + { + // The plaintexts used to encrypt a key element will be stored in this buffer + let mut decomposition_polynomials_buffer = PolynomialList::new( + Scalar::ZERO, + input_glwe_sk.polynomial_size(), + PolynomialCount(decomp_level_count.0), + ); + + // We fill the buffer with the powers of the key elmements + for (level, mut message_polynomial) in (1..=decomp_level_count.0) + .rev() + .map(DecompositionLevel) + .zip(decomposition_polynomials_buffer.as_mut_view().iter_mut()) + { + let term = DecompositionTermSliceNonNative::new( + level, + decomp_base_log, + input_key_polynomial.as_ref(), + ciphertext_modulus, + ); + term.to_approximate_recomposition_summand(message_polynomial.as_mut()); + } + + let decomposition_plaintexts_buffer = + PlaintextList::from_container(decomposition_polynomials_buffer.into_container()); + + encrypt_glwe_ciphertext_list( + output_glwe_sk, + &mut keyswitch_key_block, + &decomposition_plaintexts_buffer, + noise_distribution, + generator, + ); + } +} + +/// Allocate a new [`GLWE keyswitch key`](`GlweKeyswitchKey`) and fill it with an actual +/// keyswitching key constructed from an input and an output +/// [`GLWE secret key`](`GlweSecretKey`). +/// +/// See [`keyswitch_glwe_ciphertext`] for usage. +pub fn allocate_and_generate_new_glwe_keyswitch_key< + Scalar, + NoiseDistribution, + InputKeyCont, + OutputKeyCont, + Gen, +>( + input_glwe_sk: &GlweSecretKey, + output_glwe_sk: &GlweSecretKey, + decomp_base_log: DecompositionBaseLog, + decomp_level_count: DecompositionLevelCount, + noise_distribution: NoiseDistribution, + ciphertext_modulus: CiphertextModulus, + generator: &mut EncryptionRandomGenerator, +) -> GlweKeyswitchKeyOwned +where + Scalar: Encryptable, + NoiseDistribution: Distribution, + InputKeyCont: Container, + OutputKeyCont: Container, + Gen: ByteRandomGenerator, +{ + let mut new_glwe_keyswitch_key = GlweKeyswitchKeyOwned::new( + Scalar::ZERO, + decomp_base_log, + decomp_level_count, + input_glwe_sk.glwe_dimension(), + output_glwe_sk.glwe_dimension(), + output_glwe_sk.polynomial_size(), + ciphertext_modulus, + ); + + generate_glwe_keyswitch_key( + input_glwe_sk, + output_glwe_sk, + &mut new_glwe_keyswitch_key, + noise_distribution, + generator, + ); + + new_glwe_keyswitch_key +} diff --git a/tfhe/src/core_crypto/algorithms/mod.rs b/tfhe/src/core_crypto/algorithms/mod.rs index ce14acbcc5..79adba680c 100644 --- a/tfhe/src/core_crypto/algorithms/mod.rs +++ b/tfhe/src/core_crypto/algorithms/mod.rs @@ -5,6 +5,8 @@ pub mod ggsw_conversion; pub mod ggsw_encryption; pub mod glwe_encryption; +pub mod glwe_keyswitch; +pub mod glwe_keyswitch_key_generation; pub mod glwe_linear_algebra; pub mod glwe_sample_extraction; pub mod glwe_secret_key_generation; @@ -54,6 +56,8 @@ pub(crate) mod test; pub use ggsw_conversion::*; pub use ggsw_encryption::*; pub use glwe_encryption::*; +pub use glwe_keyswitch::*; +pub use glwe_keyswitch_key_generation::*; pub use glwe_linear_algebra::*; pub use glwe_sample_extraction::*; pub use glwe_secret_key_generation::*; diff --git a/tfhe/src/core_crypto/algorithms/polynomial_algorithms.rs b/tfhe/src/core_crypto/algorithms/polynomial_algorithms.rs index 43e4c39304..b56ae7cae5 100644 --- a/tfhe/src/core_crypto/algorithms/polynomial_algorithms.rs +++ b/tfhe/src/core_crypto/algorithms/polynomial_algorithms.rs @@ -1,5 +1,7 @@ //! Module providing algorithms to perform computations on polynomials modulo $X^{N} + 1$. +use itertools::Itertools; + use crate::core_crypto::algorithms::slice_algorithms::*; use crate::core_crypto::commons::parameters::MonomialDegree; use crate::core_crypto::commons::traits::*; @@ -919,6 +921,65 @@ pub fn polynomial_wrapping_sub_mul_assign_custom_mod( + output_poly_list: &mut PolynomialList, + input_poly_list: &PolynomialList, + scalar_poly: &Polynomial, +) where + Scalar: UnsignedInteger, + OutputCont: ContainerMut, + InputCont: Container, + PolyCont: Container, +{ + assert_eq!( + output_poly_list.polynomial_size(), + input_poly_list.polynomial_size() + ); + assert_eq!( + output_poly_list.polynomial_count(), + input_poly_list.polynomial_count() + ); + for (mut output_poly, input_poly) in output_poly_list.iter_mut().zip_eq(input_poly_list.iter()) + { + polynomial_wrapping_sub_mul_assign(&mut output_poly, &input_poly, scalar_poly) + } +} + +pub fn polynomial_list_wrapping_sub_scalar_mul_assign_custom_mod< + Scalar, + InputCont, + OutputCont, + PolyCont, +>( + output_poly_list: &mut PolynomialList, + input_poly_list: &PolynomialList, + scalar_poly: &Polynomial, + custom_modulus: Scalar, +) where + Scalar: UnsignedInteger, + OutputCont: ContainerMut, + InputCont: Container, + PolyCont: Container, +{ + assert_eq!( + output_poly_list.polynomial_size(), + input_poly_list.polynomial_size() + ); + assert_eq!( + output_poly_list.polynomial_count(), + input_poly_list.polynomial_count() + ); + for (mut output_poly, input_poly) in output_poly_list.iter_mut().zip_eq(input_poly_list.iter()) + { + polynomial_wrapping_sub_mul_assign_custom_mod( + &mut output_poly, + &input_poly, + scalar_poly, + custom_modulus, + ) + } +} + /// Fill the output polynomial, with the result of the product of two polynomials, reduced modulo /// $(X^{N} + 1)$ with the schoolbook algorithm Complexity: $O(N^{2})$ /// diff --git a/tfhe/src/core_crypto/commons/math/decomposition/decomposer.rs b/tfhe/src/core_crypto/commons/math/decomposition/decomposer.rs index 3643370d81..1a64eb153b 100644 --- a/tfhe/src/core_crypto/commons/math/decomposition/decomposer.rs +++ b/tfhe/src/core_crypto/commons/math/decomposition/decomposer.rs @@ -145,6 +145,12 @@ where native_closest_representable(input, self.level_count, self.base_log) } + /// Decode a plaintext value using the decoder to compute the closest representable. + pub fn decode_plaintext(&self, input: Scalar) -> Scalar { + let shift = Scalar::BITS - self.level_count * self.base_log; + self.closest_representable(input) >> shift + } + #[inline(always)] pub fn init_decomposer_state(&self, input: Scalar) -> Scalar { // The closest number representable by the decomposition can be computed by performing @@ -486,6 +492,26 @@ where } } + /// Decode a plaintext value using the decoder modulo a custom modulus. + pub fn decode_plaintext(&self, input: Scalar) -> Scalar { + let ciphertext_modulus_as_scalar: Scalar = + self.ciphertext_modulus.get_custom_modulus().cast_into(); + let mut negate_input = false; + let mut ptxt = input; + if input > ciphertext_modulus_as_scalar >> 1 { + negate_input = true; + ptxt = ptxt.wrapping_neg_custom_mod(ciphertext_modulus_as_scalar); + } + let number_of_message_bits = self.base_log().0 * self.level_count().0; + let delta = ciphertext_modulus_as_scalar >> number_of_message_bits; + let half_delta = delta >> 1; + let mut decoded = (ptxt + half_delta) / delta; + if negate_input { + decoded = decoded.wrapping_neg_custom_mod(ciphertext_modulus_as_scalar); + } + decoded + } + #[inline(always)] pub fn init_decomposer_state(&self, input: Scalar) -> (Scalar, ValueSign) { let ciphertext_modulus_as_scalar: Scalar = diff --git a/tfhe/src/core_crypto/entities/glwe_keyswitch_key.rs b/tfhe/src/core_crypto/entities/glwe_keyswitch_key.rs new file mode 100644 index 0000000000..cc80d9e9a6 --- /dev/null +++ b/tfhe/src/core_crypto/entities/glwe_keyswitch_key.rs @@ -0,0 +1,505 @@ +//! Module containing the definition of the [`GlweKeyswitchKey`]. + +use crate::conformance::ParameterSetConformant; +use crate::core_crypto::commons::parameters::*; +use crate::core_crypto::commons::traits::*; +use crate::core_crypto::entities::*; + +/// A [`GLWE keyswitch key`](`GlweKeyswitchKey`). +/// +/// # Formal Definition +/// +/// ## Key Switching Key +/// +/// A key switching key is a vector of GLev ciphertexts (described on the bottom of +/// [`this page`](`crate::core_crypto::entities::GgswCiphertext#Glev-ciphertext`)). +/// It encrypts the coefficient of +/// the [`GLWE secret key`](`crate::core_crypto::entities::GlweSecretKey`) +/// $\vec{S}\_{\mathsf{in}}$ under the +/// [`GLWE secret key`](`crate::core_crypto::entities::GlweSecretKey`) +/// $\vec{S}\_{\mathsf{out}}$. +/// +/// $$\mathsf{KSK}\_{\vec{S}\_{\mathsf{in}}\rightarrow \vec{S}\_{\mathsf{out}}} = \left( +/// \overline{\mathsf{CT}\_0}, \cdots , \overline{\mathsf{CT}\_{k\_{\mathsf{in}}-1}}\right) +/// \subseteq R\_q^{(k\_{\mathsf{out}}+1)\cdot k\_{\mathsf{in}}\cdot \ell}$$ +/// +/// where $\vec{S}\_{\mathsf{in}} = \left( S\_0 , \cdots , S\_{\mathsf{in}-1} \right)$ and for all +/// $0\le i +where + C::Element: UnsignedInteger, +{ + data: C, + decomp_base_log: DecompositionBaseLog, + decomp_level_count: DecompositionLevelCount, + output_glwe_size: GlweSize, + poly_size: PolynomialSize, + ciphertext_modulus: CiphertextModulus, +} + +impl> AsRef<[T]> for GlweKeyswitchKey { + fn as_ref(&self) -> &[T] { + self.data.as_ref() + } +} + +impl> AsMut<[T]> for GlweKeyswitchKey { + fn as_mut(&mut self) -> &mut [T] { + self.data.as_mut() + } +} + +/// Return the number of elements in an encryption of an input [`GlweSecretKey`] element for a +/// [`GlweKeyswitchKey`] given a [`DecompositionLevelCount`] and output [`GlweSize`] and +/// [`PolynomialSize`]. +pub fn glwe_keyswitch_key_input_key_element_encrypted_size( + decomp_level_count: DecompositionLevelCount, + output_glwe_size: GlweSize, + poly_size: PolynomialSize, +) -> usize { + // One ciphertext per level encrypted under the output key + decomp_level_count.0 * output_glwe_size.0 * poly_size.0 +} + +impl> GlweKeyswitchKey { + /// Create an [`GlweKeyswitchKey`] from an existing container. + /// + /// # Note + /// + /// This function only wraps a container in the appropriate type. If you want to generate an + /// [`GlweKeyswitchKey`] you need to call + /// [`crate::core_crypto::algorithms::generate_glwe_keyswitch_key`] using this key as output. + /// + /// This docstring exhibits [`GlweKeyswitchKey`] primitives usage. + /// + /// ``` + /// use tfhe::core_crypto::prelude::*; + /// + /// // DISCLAIMER: these toy example parameters are not guaranteed to be secure or yield correct + /// // computations + /// // Define parameters for LweKeyswitchKey creation + /// let input_glwe_dimension = GlweDimension(1); + /// let output_glwe_dimension = GlweDimension(2); + /// let poly_size = PolynomialSize(1024); + /// let decomp_base_log = DecompositionBaseLog(4); + /// let decomp_level_count = DecompositionLevelCount(5); + /// let ciphertext_modulus = CiphertextModulus::new_native(); + /// + /// // Create a new LweKeyswitchKey + /// let glwe_ksk = GlweKeyswitchKey::new( + /// 0u64, + /// decomp_base_log, + /// decomp_level_count, + /// input_glwe_dimension, + /// output_glwe_dimension, + /// poly_size, + /// ciphertext_modulus, + /// ); + /// + /// assert_eq!(glwe_ksk.decomposition_base_log(), decomp_base_log); + /// assert_eq!(glwe_ksk.decomposition_level_count(), decomp_level_count); + /// assert_eq!(glwe_ksk.input_key_glwe_dimension(), input_glwe_dimension); + /// assert_eq!(glwe_ksk.output_key_glwe_dimension(), output_glwe_dimension); + /// assert_eq!(glwe_ksk.polynomial_size(), poly_size); + /// assert_eq!( + /// glwe_ksk.output_glwe_size(), + /// output_glwe_dimension.to_glwe_size() + /// ); + /// assert_eq!(glwe_ksk.ciphertext_modulus(), ciphertext_modulus); + /// + /// // Demonstrate how to recover the allocated container + /// let underlying_container: Vec = glwe_ksk.into_container(); + /// + /// // Recreate a keyswitch key using from_container + /// let glwe_ksk = GlweKeyswitchKey::from_container( + /// underlying_container, + /// decomp_base_log, + /// decomp_level_count, + /// output_glwe_dimension.to_glwe_size(), + /// poly_size, + /// ciphertext_modulus, + /// ); + /// + /// assert_eq!(glwe_ksk.decomposition_base_log(), decomp_base_log); + /// assert_eq!(glwe_ksk.decomposition_level_count(), decomp_level_count); + /// assert_eq!(glwe_ksk.input_key_glwe_dimension(), input_glwe_dimension); + /// assert_eq!(glwe_ksk.output_key_glwe_dimension(), output_glwe_dimension); + /// assert_eq!( + /// glwe_ksk.output_glwe_size(), + /// output_glwe_dimension.to_glwe_size() + /// ); + /// assert_eq!(glwe_ksk.ciphertext_modulus(), ciphertext_modulus); + /// ``` + pub fn from_container( + container: C, + decomp_base_log: DecompositionBaseLog, + decomp_level_count: DecompositionLevelCount, + output_glwe_size: GlweSize, + poly_size: PolynomialSize, + ciphertext_modulus: CiphertextModulus, + ) -> Self { + assert!( + container.container_len() > 0, + "Got an empty container to create a GlweKeyswitchKey" + ); + assert!( + container.container_len() % (decomp_level_count.0 * output_glwe_size.0 * poly_size.0) + == 0, + "The provided container length is not valid. \ + It needs to be dividable by decomp_level_count * output_glwe_size * output_poly_size: {}. \ + Got container length: {} and decomp_level_count: {decomp_level_count:?}, \ + output_glwe_size: {output_glwe_size:?}, poly_size: {poly_size:?}.", + decomp_level_count.0 * output_glwe_size.0 * poly_size.0, + container.container_len() + ); + + Self { + data: container, + decomp_base_log, + decomp_level_count, + output_glwe_size, + poly_size, + ciphertext_modulus, + } + } + + /// Return the [`DecompositionBaseLog`] of the [`LweKeyswitchKey`]. + /// + /// See [`LweKeyswitchKey::from_container`] for usage. + pub fn decomposition_base_log(&self) -> DecompositionBaseLog { + self.decomp_base_log + } + + /// Return the [`DecompositionLevelCount`] of the [`LweKeyswitchKey`]. + /// + /// See [`LweKeyswitchKey::from_container`] for usage. + pub fn decomposition_level_count(&self) -> DecompositionLevelCount { + self.decomp_level_count + } + + /// Return the input [`GlweDimension`] of the [`GlweKeyswitchKey`]. + /// + /// See [`GlweKeyswitchKey::from_container`] for usage. + pub fn input_key_glwe_dimension(&self) -> GlweDimension { + GlweDimension(self.data.container_len() / self.input_key_element_encrypted_size()) + } + + /// Return the input [`PolynomialSize`] of the [`GlweKeyswitchKey`]. + /// + /// See [`GlweKeyswitchKey::from_container`] for usage. + pub fn polynomial_size(&self) -> PolynomialSize { + self.poly_size + } + + /// Return the output [`GlweDimension`] of the [`GlweKeyswitchKey`]. + /// + /// See [`GlweKeyswitchKey::from_container`] for usage. + pub fn output_key_glwe_dimension(&self) -> GlweDimension { + self.output_glwe_size.to_glwe_dimension() + } + + /// Return the output [`GlweSize`] of the [`GlweKeyswitchKey`]. + /// + /// See [`GlweKeyswitchKey::from_container`] for usage. + pub fn output_glwe_size(&self) -> GlweSize { + self.output_glwe_size + } + + /// Return the number of elements in an encryption of an input [`GlweSecretKey`] element of the + /// current [`GlweKeyswitchKey`]. + pub fn input_key_element_encrypted_size(&self) -> usize { + glwe_keyswitch_key_input_key_element_encrypted_size( + self.decomp_level_count, + self.output_glwe_size, + self.poly_size, + ) + } + + /// Return a view of the [`GlweKeyswitchKey`]. This is useful if an algorithm takes a view by + /// value. + pub fn as_view(&self) -> GlweKeyswitchKey<&'_ [Scalar]> { + GlweKeyswitchKey::from_container( + self.as_ref(), + self.decomp_base_log, + self.decomp_level_count, + self.output_glwe_size, + self.poly_size, + self.ciphertext_modulus, + ) + } + + /// Consume the entity and return its underlying container. + /// + /// See [`GlweKeyswitchKey::from_container`] for usage. + pub fn into_container(self) -> C { + self.data + } + + pub fn as_glwe_ciphertext_list(&self) -> GlweCiphertextListView<'_, Scalar> { + GlweCiphertextListView::from_container( + self.as_ref(), + self.output_glwe_size(), + self.polynomial_size(), + self.ciphertext_modulus(), + ) + } + + /// Return the [`CiphertextModulus`] of the [`GlweKeyswitchKey`]. + /// + /// See [`GlweKeyswitchKey::from_container`] for usage. + pub fn ciphertext_modulus(&self) -> CiphertextModulus { + self.ciphertext_modulus + } +} + +impl> GlweKeyswitchKey { + /// Mutable variant of [`GlweKeyswitchKey::as_view`]. + pub fn as_mut_view(&mut self) -> GlweKeyswitchKey<&'_ mut [Scalar]> { + let decomp_base_log = self.decomp_base_log; + let decomp_level_count = self.decomp_level_count; + let output_glwe_size = self.output_glwe_size; + let poly_size = self.poly_size; + let ciphertext_modulus = self.ciphertext_modulus; + GlweKeyswitchKey::from_container( + self.as_mut(), + decomp_base_log, + decomp_level_count, + output_glwe_size, + poly_size, + ciphertext_modulus, + ) + } + + pub fn as_mut_glwe_ciphertext_list(&mut self) -> GlweCiphertextListMutView<'_, Scalar> { + let output_glwe_size = self.output_glwe_size(); + let poly_size = self.polynomial_size(); + let ciphertext_modulus = self.ciphertext_modulus(); + GlweCiphertextListMutView::from_container( + self.as_mut(), + output_glwe_size, + poly_size, + ciphertext_modulus, + ) + } +} + +/// A [`GlweKeyswitchKey`] owning the memory for its own storage. +pub type GlweKeyswitchKeyOwned = GlweKeyswitchKey>; +/// A [`GlweKeyswitchKey`] immutably borrowing memory for its own storage. +pub type GlweKeyswitchKeyView<'data, Scalar> = GlweKeyswitchKey<&'data [Scalar]>; +/// A [`GlweKeyswitchKey`] mutably borrowing memory for its own storage. +pub type GlweKeyswitchKeyMutView<'data, Scalar> = GlweKeyswitchKey<&'data mut [Scalar]>; + +impl GlweKeyswitchKeyOwned { + /// Allocate memory and create a new owned [`GlweKeyswitchKey`]. + /// + /// # Note + /// + /// This function allocates a vector of the appropriate size and wraps it in the appropriate + /// type. If you want to generate an [`GlweKeyswitchKey`] you need to call + /// [`crate::core_crypto::algorithms::generate_glwe_keyswitch_key`] using this key as output. + /// + /// See [`GlweKeyswitchKey::from_container`] for usage. + pub fn new( + fill_with: Scalar, + decomp_base_log: DecompositionBaseLog, + decomp_level_count: DecompositionLevelCount, + input_key_glwe_dimension: GlweDimension, + output_key_glwe_dimension: GlweDimension, + poly_size: PolynomialSize, + ciphertext_modulus: CiphertextModulus, + ) -> Self { + Self::from_container( + vec![ + fill_with; + input_key_glwe_dimension.0 + * glwe_keyswitch_key_input_key_element_encrypted_size( + decomp_level_count, + output_key_glwe_dimension.to_glwe_size(), + poly_size, + ) + ], + decomp_base_log, + decomp_level_count, + output_key_glwe_dimension.to_glwe_size(), + poly_size, + ciphertext_modulus, + ) + } +} + +#[derive(Clone, Copy)] +pub struct GlweKeyswitchKeyCreationMetadata { + pub decomp_base_log: DecompositionBaseLog, + pub decomp_level_count: DecompositionLevelCount, + pub output_glwe_size: GlweSize, + pub polynomial_size: PolynomialSize, + pub ciphertext_modulus: CiphertextModulus, +} + +impl> CreateFrom + for GlweKeyswitchKey +{ + type Metadata = GlweKeyswitchKeyCreationMetadata; + + #[inline] + fn create_from(from: C, meta: Self::Metadata) -> Self { + let GlweKeyswitchKeyCreationMetadata { + decomp_base_log, + decomp_level_count, + output_glwe_size, + polynomial_size, + ciphertext_modulus, + } = meta; + Self::from_container( + from, + decomp_base_log, + decomp_level_count, + output_glwe_size, + polynomial_size, + ciphertext_modulus, + ) + } +} + +impl> ContiguousEntityContainer + for GlweKeyswitchKey +{ + type Element = C::Element; + + type EntityViewMetadata = GlweCiphertextListCreationMetadata; + + type EntityView<'this> + = GlweCiphertextListView<'this, Self::Element> + where + Self: 'this; + + type SelfViewMetadata = GlweKeyswitchKeyCreationMetadata; + + type SelfView<'this> + = GlweKeyswitchKeyView<'this, Self::Element> + where + Self: 'this; + + fn get_entity_view_creation_metadata(&self) -> Self::EntityViewMetadata { + GlweCiphertextListCreationMetadata { + glwe_size: self.output_glwe_size(), + polynomial_size: self.polynomial_size(), + 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 { + GlweKeyswitchKeyCreationMetadata { + decomp_base_log: self.decomposition_base_log(), + decomp_level_count: self.decomposition_level_count(), + output_glwe_size: self.output_glwe_size(), + polynomial_size: self.polynomial_size(), + ciphertext_modulus: self.ciphertext_modulus(), + } + } +} + +impl> ContiguousEntityContainerMut + for GlweKeyswitchKey +{ + type EntityMutView<'this> + = GlweCiphertextListMutView<'this, Self::Element> + where + Self: 'this; + + type SelfMutView<'this> + = GlweKeyswitchKeyMutView<'this, Self::Element> + where + Self: 'this; +} + +pub struct GlweKeyswitchKeyConformanceParams { + pub decomp_base_log: DecompositionBaseLog, + pub decomp_level_count: DecompositionLevelCount, + pub output_glwe_size: GlweSize, + pub input_glwe_dimension: GlweDimension, + pub polynomial_size: PolynomialSize, + pub ciphertext_modulus: CiphertextModulus, +} + +impl> ParameterSetConformant for GlweKeyswitchKey { + type ParameterSet = GlweKeyswitchKeyConformanceParams; + + fn is_conformant(&self, parameter_set: &Self::ParameterSet) -> bool { + let Self { + data, + decomp_base_log, + decomp_level_count, + output_glwe_size, + poly_size, + ciphertext_modulus, + } = self; + + *ciphertext_modulus == parameter_set.ciphertext_modulus + && data.container_len() + == parameter_set.input_glwe_dimension.0 + * glwe_keyswitch_key_input_key_element_encrypted_size( + parameter_set.decomp_level_count, + parameter_set.output_glwe_size, + parameter_set.polynomial_size, + ) + && *decomp_base_log == parameter_set.decomp_base_log + && *decomp_level_count == parameter_set.decomp_level_count + && *output_glwe_size == parameter_set.output_glwe_size + && *poly_size == parameter_set.polynomial_size + } +} diff --git a/tfhe/src/core_crypto/entities/mod.rs b/tfhe/src/core_crypto/entities/mod.rs index f951c7b1c9..a0a5ec329a 100644 --- a/tfhe/src/core_crypto/entities/mod.rs +++ b/tfhe/src/core_crypto/entities/mod.rs @@ -11,6 +11,7 @@ pub mod ggsw_ciphertext; pub mod ggsw_ciphertext_list; pub mod glwe_ciphertext; pub mod glwe_ciphertext_list; +pub mod glwe_keyswitch_key; pub mod glwe_secret_key; pub mod gsw_ciphertext; pub mod lwe_bootstrap_key; @@ -68,6 +69,7 @@ pub use ggsw_ciphertext::*; pub use ggsw_ciphertext_list::*; pub use glwe_ciphertext::*; pub use glwe_ciphertext_list::*; +pub use glwe_keyswitch_key::*; pub use glwe_secret_key::*; pub use gsw_ciphertext::*; pub use lwe_bootstrap_key::*;