diff --git a/.gitignore b/.gitignore index 34faa79ce4..f9d9b37386 100644 --- a/.gitignore +++ b/.gitignore @@ -34,6 +34,7 @@ package-lock.json # Python .env .env +__pycache__ # Dir used for backward compatibility test data tests/tfhe-backward-compat-data/ diff --git a/tfhe/src/core_crypto/algorithms/lwe_bootstrap_key_generation.rs b/tfhe/src/core_crypto/algorithms/lwe_bootstrap_key_generation.rs index 61afc37b79..617d08fd65 100644 --- a/tfhe/src/core_crypto/algorithms/lwe_bootstrap_key_generation.rs +++ b/tfhe/src/core_crypto/algorithms/lwe_bootstrap_key_generation.rs @@ -594,3 +594,413 @@ where bsk } + +/// A generator for producing chunks of an LWE bootstrap key. +/// +/// This struct allows for the generation of LWE bootstrap key chunks, which can be used to +/// construct a full LWE bootstrap key. The generator ensures that the final key would be equivalent +/// to the non-chunked generation. +/// +/// # Example +/// +/// ```rust +/// use tfhe::core_crypto::prelude::*; +/// +/// // DISCLAIMER: these toy example parameters are not guaranteed to be secure or yield correct +/// // computations +/// let input_lwe_dimension = LweDimension(742); +/// let chunk_size = ChunkSize(100); +/// let decomp_base_log = DecompositionBaseLog(3); +/// let decomp_level_count = DecompositionLevelCount(5); +/// let glwe_dimension = GlweDimension(1); +/// let polynomial_size = PolynomialSize(1024); +/// let glwe_noise_distribution = +/// Gaussian::from_dispersion_parameter(StandardDev(0.00000000000000029403601535432533), 0.0); +/// let ciphertext_modulus: CiphertextModulus = CiphertextModulus::new_native(); +/// 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()); +/// let input_lwe_secret_key = +/// allocate_and_generate_new_binary_lwe_secret_key(input_lwe_dimension, &mut secret_generator); +/// let output_glwe_secret_key = allocate_and_generate_new_binary_glwe_secret_key( +/// glwe_dimension, +/// polynomial_size, +/// &mut secret_generator, +/// ); +/// let chunk_generator = LweBootstrapKeyChunkGenerator::new( +/// &mut encryption_generator, +/// chunk_size, +/// input_lwe_dimension, +/// glwe_dimension.to_glwe_size(), +/// polynomial_size, +/// decomp_base_log, +/// decomp_level_count, +/// ciphertext_modulus, +/// &input_lwe_secret_key, +/// &output_glwe_secret_key, +/// glwe_noise_distribution, +/// false, +/// ); +/// let chunks = chunk_generator.collect::>(); +/// let assembled_bsk = allocate_and_assemble_lwe_bootstrap_key_from_chunks(chunks.as_slice()); +/// +/// for (ggsw, &input_key_bit) in assembled_bsk.iter().zip(input_lwe_secret_key.as_ref()) { +/// let decrypted_ggsw = decrypt_constant_ggsw_ciphertext(&output_glwe_secret_key, &ggsw); +/// assert_eq!(decrypted_ggsw.0, input_key_bit) +/// } +/// ``` +pub struct LweBootstrapKeyChunkGenerator<'a, Gen, Cont, Scalar, NoiseDistribution> +where + Gen: ByteRandomGenerator, + NoiseDistribution: Distribution, + Scalar: Encryptable, + Cont: Container, +{ + enc_generator: &'a mut EncryptionRandomGenerator, + chunk_size: ChunkSize, + lwe_dim: LweDimension, + glwe_size: GlweSize, + polynomial_size: PolynomialSize, + decomposition_base_log: DecompositionBaseLog, + decomposition_level_count: DecompositionLevelCount, + ciphertext_modulus: CiphertextModulus, + input_lwe_secret_key: &'a LweSecretKey, + output_glwe_secret_key: &'a GlweSecretKey, + noise_distribution: NoiseDistribution, + position: usize, + parallel: bool, +} + +impl<'a, Gen, Cont, Scalar, NoiseDistribution> + LweBootstrapKeyChunkGenerator<'a, Gen, Cont, Scalar, NoiseDistribution> +where + Gen: ByteRandomGenerator, + NoiseDistribution: Distribution, + Scalar: Encryptable, + Cont: Container, +{ + #[allow(clippy::too_many_arguments)] + #[allow(clippy::use_self)] + pub fn new( + enc_generator: &'a mut EncryptionRandomGenerator, + chunk_size: ChunkSize, + lwe_dim: LweDimension, + glwe_size: GlweSize, + polynomial_size: PolynomialSize, + decomposition_base_log: DecompositionBaseLog, + decomposition_level_count: DecompositionLevelCount, + ciphertext_modulus: CiphertextModulus, + input_lwe_secret_key: &'a LweSecretKey, + output_glwe_secret_key: &'a GlweSecretKey, + noise_distribution: NoiseDistribution, + parallel: bool, + ) -> LweBootstrapKeyChunkGenerator<'a, Gen, Cont, Scalar, NoiseDistribution> { + assert!(chunk_size.0 <= lwe_dim.0); + Self { + enc_generator, + chunk_size, + lwe_dim, + glwe_size, + polynomial_size, + decomposition_base_log, + decomposition_level_count, + ciphertext_modulus, + input_lwe_secret_key, + output_glwe_secret_key, + noise_distribution, + position: 0, + parallel, + } + } +} + +impl Iterator + for LweBootstrapKeyChunkGenerator<'_, Gen, Cont, Scalar, NoiseDistribution> +where + Gen: ParallelByteRandomGenerator, + NoiseDistribution: Distribution + Sync, + Scalar: Encryptable, + Cont: Container + Sync, +{ + type Item = LweBootstrapKeyChunkOwned; + + fn next(&mut self) -> Option { + if self.chunk_size.0 == 0 || self.position >= self.lwe_dim.0 { + return None; + } + + let left = self.lwe_dim.0 - self.position; + let chunk_size = if left < self.chunk_size.0 { + ChunkSize(left) + } else { + self.chunk_size + }; + + let mut chunk = LweBootstrapKeyChunkOwned::new( + Scalar::ZERO, + self.glwe_size, + self.polynomial_size, + self.decomposition_base_log, + self.decomposition_level_count, + chunk_size, + self.ciphertext_modulus, + ); + + if self.parallel { + par_generate_chunked_lwe_bootstrap_key( + self.input_lwe_secret_key, + self.output_glwe_secret_key, + &mut chunk, + self.noise_distribution, + self.enc_generator, + self.position, + ) + } else { + generate_chunked_lwe_bootstrap_key( + self.input_lwe_secret_key, + self.output_glwe_secret_key, + &mut chunk, + self.noise_distribution, + self.enc_generator, + self.position, + ) + } + + self.position += chunk_size.0; + + Some(chunk) + } +} + +/// Fill an [`LWE bootstrap key chunk`](`LweBootstrapKeyChunk`) with a part of a bootstrapping key. +/// It is constructed from a target chunk of an input key [`LWE secret key`](`LweSecretKey`) +/// and an output key [`GLWE secret key`](`GlweSecretKey`). +/// +/// The chunk is defined by `chunk_start`, and the chunk size of the output. +/// +/// Chunks can be assembled into a full [`LweBootstrapKey`] using +/// [`assemble_lwe_bootstrap_key_from_chunks`]. +/// +/// Consider using the [`ChunkGenerator`](`LweBootstrapKeyChunkGenerator`) to make sure you have +/// an equivalent key to the non-chunked version. +/// +/// Consider using [`par_generate_chunked_lwe_bootstrap_key`] for better key generation times. +/// +/// WARNING: this assumes the caller manages the random generator and the order of generation to +/// make sure the key is equivalent to the non-chunked version. +pub fn generate_chunked_lwe_bootstrap_key< + Scalar, + NoiseDistribution, + InputKeyCont, + OutputKeyCont, + OutputCont, + Gen, +>( + input_lwe_secret_key: &LweSecretKey, + output_glwe_secret_key: &GlweSecretKey, + output: &mut LweBootstrapKeyChunk, + noise_distribution: NoiseDistribution, + generator: &mut EncryptionRandomGenerator, + chunk_start: usize, +) where + Scalar: Encryptable, + NoiseDistribution: Distribution, + InputKeyCont: Container, + OutputKeyCont: Container, + OutputCont: ContainerMut, + Gen: ByteRandomGenerator, +{ + let chunk_end = chunk_start + output.chunk_size().0; + assert!( + chunk_end <= input_lwe_secret_key.lwe_dimension().0, + "Expected chunk out of bound of the input LWE secret key \ + Chunk ending at: {:?}, Input LWE secret key LweDimension {:?}.", + chunk_end, + input_lwe_secret_key.lwe_dimension() + ); + + assert!( + output.chunk_size().0 <= input_lwe_secret_key.lwe_dimension().0, + "Chunk size is larger than the input LWE secret key LweDimension. \ + LWE bootstrap key ChunkSize: {:?}, Input LWE secret key LweDimension {:?}.", + output.chunk_size(), + input_lwe_secret_key.lwe_dimension() + ); + + assert!( + output.glwe_size() == output_glwe_secret_key.glwe_dimension().to_glwe_size(), + "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().to_glwe_size(), + output.glwe_size() + ); + + 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 + .try_fork_from_config(output.encryption_fork_config(Uniform, noise_distribution)) + .unwrap(); + + for ((mut ggsw, &input_key_element), mut generator) in output + .iter_mut() + .zip(input_lwe_secret_key.as_ref()[chunk_start..chunk_end].iter()) + .zip(gen_iter) + { + encrypt_constant_ggsw_ciphertext( + output_glwe_secret_key, + &mut ggsw, + Cleartext(input_key_element), + noise_distribution, + &mut generator, + ); + } +} + +/// Parallel variant of [`generate_chunked_lwe_bootstrap_key`] for better key generation times. +pub fn par_generate_chunked_lwe_bootstrap_key< + Scalar, + NoiseDistribution, + InputKeyCont, + OutputKeyCont, + OutputCont, + Gen, +>( + input_lwe_secret_key: &LweSecretKey, + output_glwe_secret_key: &GlweSecretKey, + output: &mut LweBootstrapKeyChunk, + noise_distribution: NoiseDistribution, + generator: &mut EncryptionRandomGenerator, + chunk_start: usize, +) where + Scalar: Encryptable + Sync + Send, + NoiseDistribution: Distribution + Sync, + InputKeyCont: Container, + OutputKeyCont: Container + Sync, + OutputCont: ContainerMut, + Gen: ParallelByteRandomGenerator, +{ + let chunk_end = chunk_start + output.chunk_size().0; + assert!( + chunk_end <= input_lwe_secret_key.lwe_dimension().0, + "Expected chunk out of bound of the input LWE secret key \ + Chunk ending at: {:?}, Input LWE secret key LweDimension {:?}.", + chunk_end, + input_lwe_secret_key.lwe_dimension() + ); + + assert!( + output.chunk_size().0 <= input_lwe_secret_key.lwe_dimension().0, + "Chunk size is larger than the input LWE secret key LweDimension. \ + LWE bootstrap key ChunkSize: {:?}, Input LWE secret key LweDimension {:?}.", + output.chunk_size(), + input_lwe_secret_key.lwe_dimension() + ); + + assert!( + output.glwe_size() == output_glwe_secret_key.glwe_dimension().to_glwe_size(), + "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().to_glwe_size(), + output.glwe_size() + ); + + 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(); + + output + .par_iter_mut() + .zip(input_lwe_secret_key.as_ref()[chunk_start..chunk_end].par_iter()) + .zip(gen_iter) + .for_each(|((mut ggsw, &input_key_element), mut generator)| { + par_encrypt_constant_ggsw_ciphertext( + output_glwe_secret_key, + &mut ggsw, + Cleartext(input_key_element), + noise_distribution, + &mut generator, + ); + }); +} + +/// Assemble a vector of [`LweBootstrapKeyChunk`] into an [`LweBootstrapKey`]. +/// +/// This function takes a vector of `LweBootstrapKeyChunk` and assemble them into a single +/// `LweBootstrapKey`. It considers that chunks are in the correct order, and that they would fill +/// the `LweBootstrapKey`. +pub fn assemble_lwe_bootstrap_key_from_chunks( + output: &mut LweBootstrapKey, + chunks: &[LweBootstrapKeyChunk], +) where + Scalar: UnsignedInteger, + Cont: Container, + ContMut: ContainerMut, +{ + let total_chunk_size: usize = chunks.iter().map(|c| c.chunk_size().0).sum(); + let chunks_lwe_dimension = LweDimension(total_chunk_size); + assert!(chunks_lwe_dimension == output.input_lwe_dimension()); + + let mut start: usize = 0; + for chunk in chunks { + assert!(output.glwe_size() == chunk.glwe_size()); + assert!(output.polynomial_size() == chunk.polynomial_size()); + assert!(output.decomposition_base_log() == chunk.decomposition_base_log()); + assert!(output.decomposition_level_count() == chunk.decomposition_level_count()); + assert!(output.ciphertext_modulus() == chunk.ciphertext_modulus()); + + let end = start + chunk.as_ref().len(); + output.as_mut()[start..end].copy_from_slice(chunk.as_ref()); + start = end; + } +} + +/// Allocate a new [`LweBootstrapKey`] and assemble it from a vector of [`LweBootstrapKeyChunk`]. +/// +/// This function takes multiple `LweBootstrapKeyChunk` and assemble them into a single +/// `LweBootstrapKey`. It considers that chunks are in the correct order. +pub fn allocate_and_assemble_lwe_bootstrap_key_from_chunks( + chunks: &[LweBootstrapKeyChunk], +) -> LweBootstrapKeyOwned +where + Scalar: UnsignedInteger, + Cont: ContainerMut, +{ + assert!(!chunks.is_empty()); + let glwe_size = chunks[0].glwe_size(); + let polynomial_size = chunks[0].polynomial_size(); + let decomp_base_log = chunks[0].decomposition_base_log(); + let decomp_level_count = chunks[0].decomposition_level_count(); + let total_chunk_size: usize = chunks.iter().map(|c| c.chunk_size().0).sum(); + let input_lwe_dimension = LweDimension(total_chunk_size); + let ciphertext_modulus = chunks[0].ciphertext_modulus(); + + let mut assembled_bsk = LweBootstrapKey::new( + Scalar::ZERO, + glwe_size, + polynomial_size, + decomp_base_log, + decomp_level_count, + input_lwe_dimension, + ciphertext_modulus, + ); + + assemble_lwe_bootstrap_key_from_chunks(&mut assembled_bsk, chunks); + + assembled_bsk +} diff --git a/tfhe/src/core_crypto/algorithms/test/lwe_bootstrap_key_generation.rs b/tfhe/src/core_crypto/algorithms/test/lwe_bootstrap_key_generation.rs index ea4b04fe91..040d0a6aeb 100644 --- a/tfhe/src/core_crypto/algorithms/test/lwe_bootstrap_key_generation.rs +++ b/tfhe/src/core_crypto/algorithms/test/lwe_bootstrap_key_generation.rs @@ -6,8 +6,8 @@ use crate::core_crypto::commons::math::random::{ }; use crate::core_crypto::commons::math::torus::UnsignedTorus; use crate::core_crypto::commons::parameters::{ - CiphertextModulus, DecompositionBaseLog, DecompositionLevelCount, GlweDimension, LweDimension, - PolynomialSize, + ChunkSize, CiphertextModulus, DecompositionBaseLog, DecompositionLevelCount, GlweDimension, + LweDimension, PolynomialSize, }; use crate::core_crypto::commons::test_tools::new_secret_random_generator; use crate::core_crypto::entities::*; @@ -17,7 +17,7 @@ const NB_TESTS: usize = 10; #[cfg(tarpaulin)] const NB_TESTS: usize = 1; -fn test_parallel_and_seeded_bsk_gen_equivalence( +fn test_parallel_and_seeded_and_chunked_bsk_gen_equivalence( ciphertext_modulus: CiphertextModulus, ) { for _ in 0..NB_TESTS { @@ -144,29 +144,76 @@ fn test_parallel_and_seeded_bsk_gen_equivalence( let par_decompressed_bsk = parallel_seeded_bsk.par_decompress_into_lwe_bootstrap_key(); assert_eq!(ser_decompressed_bsk, par_decompressed_bsk); + + let mut encryption_generator = EncryptionRandomGenerator::::new( + mask_seed, + &mut DeterministicSeeder::::new(deterministic_seeder_seed), + ); + + let chunk_generator = LweBootstrapKeyChunkGenerator::new( + &mut encryption_generator, + ChunkSize(crate::core_crypto::commons::test_tools::random_usize_between(1..5)), + lwe_dim, + glwe_dim.to_glwe_size(), + poly_size, + base_log, + level, + ciphertext_modulus, + &lwe_sk, + &glwe_sk, + noise_distribution, + false, + ); + + let chunks = chunk_generator.collect::>(); + let assembled_bsk = allocate_and_assemble_lwe_bootstrap_key_from_chunks(chunks.as_slice()); + assert_eq!(assembled_bsk, sequential_bsk); + + let mut encryption_generator = EncryptionRandomGenerator::::new( + mask_seed, + &mut DeterministicSeeder::::new(deterministic_seeder_seed), + ); + + let par_chunk_generator = LweBootstrapKeyChunkGenerator::new( + &mut encryption_generator, + ChunkSize(crate::core_crypto::commons::test_tools::random_usize_between(1..5)), + lwe_dim, + glwe_dim.to_glwe_size(), + poly_size, + base_log, + level, + ciphertext_modulus, + &lwe_sk, + &glwe_sk, + noise_distribution, + true, + ); + let chunks = par_chunk_generator.collect::>(); + let assembled_bsk = allocate_and_assemble_lwe_bootstrap_key_from_chunks(chunks.as_slice()); + assert_eq!(assembled_bsk, sequential_bsk); } } #[test] -fn test_parallel_and_seeded_bsk_gen_equivalence_u32_native_mod() { - test_parallel_and_seeded_bsk_gen_equivalence::(CiphertextModulus::new_native()); +fn test_parallel_and_seeded_and_chunked_bsk_gen_equivalence_u32_native_mod() { + test_parallel_and_seeded_and_chunked_bsk_gen_equivalence::(CiphertextModulus::new_native()); } #[test] -fn test_parallel_and_seeded_bsk_gen_equivalence_u32_custom_mod() { - test_parallel_and_seeded_bsk_gen_equivalence::( +fn test_parallel_and_seeded_and_chunked_bsk_gen_equivalence_u32_custom_mod() { + test_parallel_and_seeded_and_chunked_bsk_gen_equivalence::( CiphertextModulus::try_new_power_of_2(31).unwrap(), ); } #[test] -fn test_parallel_and_seeded_bsk_gen_equivalence_u64_native_mod() { - test_parallel_and_seeded_bsk_gen_equivalence::(CiphertextModulus::new_native()); +fn test_parallel_and_seeded_and_chunked_bsk_gen_equivalence_u64_native_mod() { + test_parallel_and_seeded_and_chunked_bsk_gen_equivalence::(CiphertextModulus::new_native()); } #[test] -fn test_parallel_and_seeded_bsk_gen_equivalence_u64_custom_mod() { - test_parallel_and_seeded_bsk_gen_equivalence::( +fn test_parallel_and_seeded_and_chunked_bsk_gen_equivalence_u64_custom_mod() { + test_parallel_and_seeded_and_chunked_bsk_gen_equivalence::( CiphertextModulus::try_new_power_of_2(63).unwrap(), ); } diff --git a/tfhe/src/core_crypto/backward_compatibility/commons/parameters.rs b/tfhe/src/core_crypto/backward_compatibility/commons/parameters.rs index 6ecd83254b..1dc7ba4c1e 100644 --- a/tfhe/src/core_crypto/backward_compatibility/commons/parameters.rs +++ b/tfhe/src/core_crypto/backward_compatibility/commons/parameters.rs @@ -182,3 +182,8 @@ pub enum RSigmaFactorVersions { pub enum NoiseEstimationMeasureBoundVersions { V0(NoiseEstimationMeasureBound), } + +#[derive(VersionsDispatch)] +pub enum ChunkSizeVersions { + V0(ChunkSize), +} diff --git a/tfhe/src/core_crypto/backward_compatibility/entities/lwe_bootstrap_key_chunk.rs b/tfhe/src/core_crypto/backward_compatibility/entities/lwe_bootstrap_key_chunk.rs new file mode 100644 index 0000000000..d380296cbe --- /dev/null +++ b/tfhe/src/core_crypto/backward_compatibility/entities/lwe_bootstrap_key_chunk.rs @@ -0,0 +1,11 @@ +use tfhe_versionable::VersionsDispatch; + +use crate::core_crypto::prelude::{Container, LweBootstrapKeyChunk, UnsignedInteger}; + +#[derive(VersionsDispatch)] +pub enum LweBootstrapKeyChunkVersions +where + C::Element: UnsignedInteger, +{ + V0(LweBootstrapKeyChunk), +} diff --git a/tfhe/src/core_crypto/backward_compatibility/entities/mod.rs b/tfhe/src/core_crypto/backward_compatibility/entities/mod.rs index 25ceb37029..a07e3f6c50 100644 --- a/tfhe/src/core_crypto/backward_compatibility/entities/mod.rs +++ b/tfhe/src/core_crypto/backward_compatibility/entities/mod.rs @@ -9,6 +9,7 @@ pub mod glwe_ciphertext_list; pub mod glwe_secret_key; pub mod gsw_ciphertext; pub mod lwe_bootstrap_key; +pub mod lwe_bootstrap_key_chunk; pub mod lwe_ciphertext; pub mod lwe_ciphertext_list; pub mod lwe_compact_ciphertext_list; diff --git a/tfhe/src/core_crypto/commons/parameters.rs b/tfhe/src/core_crypto/commons/parameters.rs index 14dcb8beae..8c1109f3b6 100644 --- a/tfhe/src/core_crypto/commons/parameters.rs +++ b/tfhe/src/core_crypto/commons/parameters.rs @@ -388,3 +388,8 @@ pub struct RSigmaFactor(pub f64); #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize, Versionize)] #[versionize(NoiseEstimationMeasureBoundVersions)] pub struct NoiseEstimationMeasureBound(pub f64); + +/// The size of a chunk in a chunked key. +#[derive(Copy, Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Versionize)] +#[versionize(ChunkSizeVersions)] +pub struct ChunkSize(pub usize); diff --git a/tfhe/src/core_crypto/entities/lwe_bootstrap_key_chunk.rs b/tfhe/src/core_crypto/entities/lwe_bootstrap_key_chunk.rs new file mode 100644 index 0000000000..605801dc46 --- /dev/null +++ b/tfhe/src/core_crypto/entities/lwe_bootstrap_key_chunk.rs @@ -0,0 +1,252 @@ +//! Module containing the definition of the LweBootstrapKeyChunk. + +use tfhe_versionable::Versionize; + +use crate::core_crypto::backward_compatibility::entities::lwe_bootstrap_key_chunk::LweBootstrapKeyChunkVersions; +use crate::core_crypto::commons::parameters::*; +use crate::core_crypto::commons::traits::*; +use crate::core_crypto::entities::*; + +/// An [`LWE bootstrap key chunk`](`LweBootstrapKeyChunk`). +/// +/// It is a chunked version of [`LWE bootstrap key`](`LweBootstrapKey`). +/// +/// This is a wrapper type of [`GgswCiphertextList`], [`std::ops::Deref`] and [`std::ops::DerefMut`] +/// are implemented to dereference to the underlying [`GgswCiphertextList`] for ease of use. See +/// [`GgswCiphertextList`] for additional methods. +#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize, Versionize)] +#[versionize(LweBootstrapKeyChunkVersions)] +pub struct LweBootstrapKeyChunk +where + C::Element: UnsignedInteger, +{ + // An LweBootstrapKeyChunk is literally a GgswCiphertextList, so we wrap a GgswCiphertextList + // and use Deref to have access to all the primitives of the GgswCiphertextList easily + ggsw_list: GgswCiphertextList, +} + +impl> std::ops::Deref + for LweBootstrapKeyChunk +{ + type Target = GgswCiphertextList; + + fn deref(&self) -> &GgswCiphertextList { + &self.ggsw_list + } +} + +impl> std::ops::DerefMut + for LweBootstrapKeyChunk +{ + fn deref_mut(&mut self) -> &mut GgswCiphertextList { + &mut self.ggsw_list + } +} + +pub fn lwe_bootstrap_key_chunk_size( + chunk_size: ChunkSize, + glwe_size: GlweSize, + polynomial_size: PolynomialSize, + decomp_level_count: DecompositionLevelCount, +) -> usize { + ggsw_ciphertext_list_size( + GgswCiphertextCount(chunk_size.0), + glwe_size, + polynomial_size, + decomp_level_count, + ) +} + +impl> LweBootstrapKeyChunk { + /// Create an [`LweBootstrapKeyChunk`] from an existing container. + /// + /// # Note + /// + /// This function only wraps a container in the appropriate type. If you want to generate an LWE + /// bootstrap key chunk you need to use + /// [`crate::core_crypto::algorithms::generate_chunked_lwe_bootstrap_key`] or its parallel + /// equivalent [`crate::core_crypto::algorithms::par_generate_chunked_lwe_bootstrap_key`] + /// using this key as output. + /// + /// This docstring exhibits [`LweBootstrapKeyChunk`] primitives usage. + /// + /// ```rust + /// use tfhe::core_crypto::prelude::*; + /// + /// // DISCLAIMER: these toy example parameters are not guaranteed to be secure or yield correct + /// // computations + /// // Define parameters for LweBootstrapKeyChunk creation + /// let glwe_size = GlweSize(2); + /// let polynomial_size = PolynomialSize(1024); + /// let decomp_base_log = DecompositionBaseLog(8); + /// let decomp_level_count = DecompositionLevelCount(3); + /// let chunk_size = ChunkSize(8); + /// let ciphertext_modulus = CiphertextModulus::new_native(); + /// + /// // Create a new LweBootstrapKeyChunk + /// let bsk_chunk = LweBootstrapKeyChunk::new( + /// 0u64, + /// glwe_size, + /// polynomial_size, + /// decomp_base_log, + /// decomp_level_count, + /// chunk_size, + /// ciphertext_modulus, + /// ); + /// + /// // These methods are "inherited" from GgswCiphertextList and are accessed through the Deref + /// // trait + /// assert_eq!(bsk_chunk.glwe_size(), glwe_size); + /// assert_eq!(bsk_chunk.polynomial_size(), polynomial_size); + /// assert_eq!(bsk_chunk.decomposition_base_log(), decomp_base_log); + /// assert_eq!(bsk_chunk.decomposition_level_count(), decomp_level_count); + /// assert_eq!(bsk_chunk.chunk_size(), chunk_size); + /// assert_eq!(bsk_chunk.ciphertext_modulus(), ciphertext_modulus); + /// + /// // These methods are specific to the LweBootstrapKeyChunk + /// assert_eq!( + /// bsk_chunk.output_lwe_dimension(), + /// glwe_size + /// .to_glwe_dimension() + /// .to_equivalent_lwe_dimension(polynomial_size) + /// ); + /// + /// // Demonstrate how to recover the allocated container + /// let underlying_container: Vec = bsk_chunk.into_container(); + /// + /// // Recreate a key using from_container + /// let bsk_chunk = LweBootstrapKeyChunk::from_container( + /// underlying_container, + /// glwe_size, + /// polynomial_size, + /// decomp_base_log, + /// decomp_level_count, + /// ciphertext_modulus, + /// ); + /// + /// assert_eq!(bsk_chunk.glwe_size(), glwe_size); + /// assert_eq!(bsk_chunk.polynomial_size(), polynomial_size); + /// assert_eq!(bsk_chunk.decomposition_base_log(), decomp_base_log); + /// assert_eq!(bsk_chunk.decomposition_level_count(), decomp_level_count); + /// assert_eq!(bsk_chunk.chunk_size(), chunk_size); + /// assert_eq!(bsk_chunk.ciphertext_modulus(), ciphertext_modulus); + /// assert_eq!( + /// bsk_chunk.output_lwe_dimension(), + /// glwe_size + /// .to_glwe_dimension() + /// .to_equivalent_lwe_dimension(polynomial_size) + /// ); + /// ``` + pub fn from_container( + container: C, + glwe_size: GlweSize, + polynomial_size: PolynomialSize, + decomp_base_log: DecompositionBaseLog, + decomp_level_count: DecompositionLevelCount, + ciphertext_modulus: CiphertextModulus, + ) -> Self { + Self { + ggsw_list: GgswCiphertextList::from_container( + container, + glwe_size, + polynomial_size, + decomp_base_log, + decomp_level_count, + ciphertext_modulus, + ), + } + } + + /// Return the [`LweDimension`] of the input [`LweSecretKey`]. + /// + /// See [`LweBootstrapKeyChunk::from_container`] for usage. + pub fn chunk_size(&self) -> ChunkSize { + ChunkSize(self.ggsw_ciphertext_count().0) + } + + /// Return the [`LweDimension`] of the equivalent output [`LweSecretKey`]. + /// + /// See [`LweBootstrapKeyChunk::from_container`] for usage. + pub fn output_lwe_dimension(&self) -> LweDimension { + self.glwe_size() + .to_glwe_dimension() + .to_equivalent_lwe_dimension(self.polynomial_size()) + } + + /// Consume the entity and return its underlying container. + /// + /// See [`LweBootstrapKeyChunk::from_container`] for usage. + pub fn into_container(self) -> C { + self.ggsw_list.into_container() + } + + /// Return a view of the [`LweBootstrapKeyChunk`]. This is useful if an algorithm takes a view + /// by value. + pub fn as_view(&self) -> LweBootstrapKeyChunk<&'_ [Scalar]> { + LweBootstrapKeyChunk::from_container( + self.as_ref(), + self.glwe_size(), + self.polynomial_size(), + self.decomposition_base_log(), + self.decomposition_level_count(), + self.ciphertext_modulus(), + ) + } +} + +impl> LweBootstrapKeyChunk { + /// Mutable variant of [`LweBootstrapKeyChunk::as_view`]. + pub fn as_mut_view(&mut self) -> LweBootstrapKeyChunk<&'_ mut [Scalar]> { + let glwe_size = self.glwe_size(); + 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(); + LweBootstrapKeyChunk::from_container( + self.as_mut(), + glwe_size, + polynomial_size, + decomp_base_log, + decomp_level_count, + ciphertext_modulus, + ) + } +} + +/// An [`LweBootstrapKeyChunk`] owning the memory for its own storage. +pub type LweBootstrapKeyChunkOwned = LweBootstrapKeyChunk>; + +impl LweBootstrapKeyChunkOwned { + /// Allocate memory and create a new owned [`LweBootstrapKeyChunk`]. + /// + /// # Note + /// + /// This function allocates a vector of the appropriate size and wraps it in the appropriate + /// type. If you want to generate an LWE bootstrap key chunk you need to use + /// [`crate::core_crypto::algorithms::generate_chunked_lwe_bootstrap_key`] or its parallel + /// equivalent [`crate::core_crypto::algorithms::par_generate_chunked_lwe_bootstrap_key`] using + /// this key as output. + /// + /// See [`LweBootstrapKeyChunk::from_container`] for usage. + pub fn new( + fill_with: Scalar, + glwe_size: GlweSize, + polynomial_size: PolynomialSize, + decomp_base_log: DecompositionBaseLog, + decomp_level_count: DecompositionLevelCount, + chunk_size: ChunkSize, + ciphertext_modulus: CiphertextModulus, + ) -> Self { + Self { + ggsw_list: GgswCiphertextList::new( + fill_with, + glwe_size, + polynomial_size, + decomp_base_log, + decomp_level_count, + GgswCiphertextCount(chunk_size.0), + ciphertext_modulus, + ), + } + } +} diff --git a/tfhe/src/core_crypto/entities/mod.rs b/tfhe/src/core_crypto/entities/mod.rs index f951c7b1c9..8ce31cb0ce 100644 --- a/tfhe/src/core_crypto/entities/mod.rs +++ b/tfhe/src/core_crypto/entities/mod.rs @@ -14,6 +14,7 @@ pub mod glwe_ciphertext_list; pub mod glwe_secret_key; pub mod gsw_ciphertext; pub mod lwe_bootstrap_key; +pub mod lwe_bootstrap_key_chunk; pub mod lwe_ciphertext; pub mod lwe_ciphertext_list; pub mod lwe_compact_ciphertext_list; @@ -71,6 +72,7 @@ pub use glwe_ciphertext_list::*; pub use glwe_secret_key::*; pub use gsw_ciphertext::*; pub use lwe_bootstrap_key::*; +pub use lwe_bootstrap_key_chunk::*; pub use lwe_ciphertext::*; pub use lwe_ciphertext_list::*; pub use lwe_compact_ciphertext_list::*;