Compare commits

...

1 Commits

Author SHA1 Message Date
Carl-Zama
1c4c6385e8 feat(core_crypto): add glwe keyswitch 2024-11-04 19:26:55 +00:00
9 changed files with 2191 additions and 2 deletions

View File

@@ -0,0 +1,304 @@
//! 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(2);
/// let poly_size = PolynomialSize(512);
/// let glwe_noise_distribution = Gaussian::from_dispersion_parameter(
/// StandardDev(0.00000000000000000000007069849454709433),
/// 0.0,
/// );
/// let output_glwe_dimension = GlweDimension(1);
/// 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::<ActivatedRandomGenerator>::new(seeder.seed(), seeder);
/// let mut secret_generator =
/// SecretRandomGenerator::<ActivatedRandomGenerator>::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 mut cleartext_list = output_plaintext_list.into_container();
/// // Remove the encoding
/// cleartext_list
/// .iter_mut()
/// .for_each(|elt| *elt = decomposer.decode_plaintext(*elt));
/// // Get the list immutably
/// let cleartext_list = cleartext_list;
///
/// // 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<Scalar, KSKCont, InputCont, OutputCont>(
glwe_keyswitch_key: &GlweKeyswitchKey<KSKCont>,
input_glwe_ciphertext: &GlweCiphertext<InputCont>,
output_glwe_ciphertext: &mut GlweCiphertext<OutputCont>,
) where
Scalar: UnsignedInteger,
KSKCont: Container<Element = Scalar>,
InputCont: Container<Element = Scalar>,
OutputCont: ContainerMut<Element = Scalar>,
{
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<Scalar, KSKCont, InputCont, OutputCont>(
glwe_keyswitch_key: &GlweKeyswitchKey<KSKCont>,
input_glwe_ciphertext: &GlweCiphertext<InputCont>,
output_glwe_ciphertext: &mut GlweCiphertext<OutputCont>,
) where
Scalar: UnsignedInteger,
KSKCont: Container<Element = Scalar>,
InputCont: Container<Element = Scalar>,
OutputCont: ContainerMut<Element = Scalar>,
{
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
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 = 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<Scalar, KSKCont, InputCont, OutputCont>(
glwe_keyswitch_key: &GlweKeyswitchKey<KSKCont>,
input_glwe_ciphertext: &GlweCiphertext<InputCont>,
output_glwe_ciphertext: &mut GlweCiphertext<OutputCont>,
) where
Scalar: UnsignedInteger,
KSKCont: Container<Element = Scalar>,
InputCont: Container<Element = Scalar>,
OutputCont: ContainerMut<Element = Scalar>,
{
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(),
);
}
}
}

View File

@@ -0,0 +1,352 @@
//! 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::<ActivatedRandomGenerator>::new(seeder.seed(), seeder);
/// let mut secret_generator =
/// SecretRandomGenerator::<ActivatedRandomGenerator>::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<InputKeyCont>,
output_glwe_sk: &GlweSecretKey<OutputKeyCont>,
glwe_keyswitch_key: &mut GlweKeyswitchKey<KSKeyCont>,
noise_distribution: NoiseDistribution,
generator: &mut EncryptionRandomGenerator<Gen>,
) where
Scalar: Encryptable<Uniform, NoiseDistribution>,
NoiseDistribution: Distribution,
InputKeyCont: Container<Element = Scalar>,
OutputKeyCont: Container<Element = Scalar>,
KSKeyCont: ContainerMut<Element = Scalar>,
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<InputKeyCont>,
output_glwe_sk: &GlweSecretKey<OutputKeyCont>,
glwe_keyswitch_key: &mut GlweKeyswitchKey<KSKeyCont>,
noise_distribution: NoiseDistribution,
generator: &mut EncryptionRandomGenerator<Gen>,
) where
Scalar: Encryptable<Uniform, NoiseDistribution>,
NoiseDistribution: Distribution,
InputKeyCont: Container<Element = Scalar>,
OutputKeyCont: Container<Element = Scalar>,
KSKeyCont: ContainerMut<Element = Scalar>,
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 =
DecompositionTermSlice::new(level, decomp_base_log, input_key_polynomial.as_ref());
term.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<InputKeyCont>,
output_glwe_sk: &GlweSecretKey<OutputKeyCont>,
glwe_keyswitch_key: &mut GlweKeyswitchKey<KSKeyCont>,
noise_distribution: NoiseDistribution,
generator: &mut EncryptionRandomGenerator<Gen>,
) where
Scalar: Encryptable<Uniform, NoiseDistribution>,
NoiseDistribution: Distribution,
InputKeyCont: Container<Element = Scalar>,
OutputKeyCont: Container<Element = Scalar>,
KSKeyCont: ContainerMut<Element = Scalar>,
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<InputKeyCont>,
output_glwe_sk: &GlweSecretKey<OutputKeyCont>,
decomp_base_log: DecompositionBaseLog,
decomp_level_count: DecompositionLevelCount,
noise_distribution: NoiseDistribution,
ciphertext_modulus: CiphertextModulus<Scalar>,
generator: &mut EncryptionRandomGenerator<Gen>,
) -> GlweKeyswitchKeyOwned<Scalar>
where
Scalar: Encryptable<Uniform, NoiseDistribution>,
NoiseDistribution: Distribution,
InputKeyCont: Container<Element = Scalar>,
OutputKeyCont: Container<Element = Scalar>,
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
}

View File

@@ -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;
@@ -53,6 +55,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::*;

View File

@@ -334,6 +334,30 @@ pub fn polynomial_wrapping_add_mul_assign_custom_mod<Scalar, OutputCont, InputCo
}
}
/// Multiply a polynomial by a scalar modulo a custom modulus.
///
/// # Example
///
/// ```
/// use tfhe::core_crypto::algorithms::polynomial_algorithms::*;
/// use tfhe::core_crypto::entities::*;
/// let mut pol = Polynomial::from_container(vec![1u8, 2, 3, 4, 5, 6]);
/// let scalar = 127u8;
/// let custom_modulus = 249u8;
/// polynomial_wrapping_scalar_mul_assign_custom_mod(&mut pol, scalar, custom_modulus);
/// assert_eq!(pol.as_ref(), &[127u8, 5, 132, 10, 137, 15]);
/// ```
pub fn polynomial_wrapping_scalar_mul_assign_custom_mod<Scalar, PolyCont>(
output: &mut Polynomial<PolyCont>,
scalar: Scalar,
custom_modulus: Scalar,
) where
Scalar: UnsignedInteger,
PolyCont: ContainerMut<Element = Scalar>,
{
slice_wrapping_scalar_mul_assign_custom_mod(output.as_mut(), scalar, custom_modulus)
}
/// Divides (mod $(X^{N}+1)$), the output polynomial with a monic monomial of a given degree i.e.
/// $X^{degree}$.
///
@@ -919,6 +943,63 @@ pub fn polynomial_wrapping_sub_mul_assign_custom_mod<Scalar, OutputCont, InputCo
}
}
pub fn polynomial_list_wrapping_sub_scalar_mul_assign<Scalar, InputCont, OutputCont, PolyCont>(
output_poly_list: &mut PolynomialList<OutputCont>,
input_poly_list: &PolynomialList<InputCont>,
scalar_poly: &Polynomial<PolyCont>,
) where
Scalar: UnsignedInteger,
OutputCont: ContainerMut<Element = Scalar>,
InputCont: Container<Element = Scalar>,
PolyCont: Container<Element = Scalar>,
{
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(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<OutputCont>,
input_poly_list: &PolynomialList<InputCont>,
scalar_poly: &Polynomial<PolyCont>,
custom_modulus: Scalar,
) where
Scalar: UnsignedInteger,
OutputCont: ContainerMut<Element = Scalar>,
InputCont: Container<Element = Scalar>,
PolyCont: Container<Element = Scalar>,
{
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(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})$
///

View File

@@ -1,6 +1,7 @@
use crate::core_crypto::commons::ciphertext_modulus::CiphertextModulus;
use crate::core_crypto::commons::math::decomposition::{
SignedDecompositionIter, SignedDecompositionNonNativeIter, ValueSign,
SignedDecompositionIter, SignedDecompositionNonNativeIter, SliceSignedDecompositionIter,
SliceSignedDecompositionNonNativeIter, ValueSign,
};
use crate::core_crypto::commons::numeric::{CastInto, UnsignedInteger};
use crate::core_crypto::commons::parameters::{DecompositionBaseLog, DecompositionLevelCount};
@@ -174,6 +175,56 @@ where
res.wrapping_sub(need_balance << rep_bit_count)
}
pub fn init_decomposer_state_slice(&self, input: &[Scalar], output: &mut [Scalar]) {
assert_eq!(input.len(), output.len());
let rep_bit_count = self.level_count * self.base_log;
let non_rep_bit_count: usize = Scalar::BITS - rep_bit_count;
let mod_mask = Scalar::MAX >> non_rep_bit_count;
input
.iter()
.zip(output.iter_mut())
.for_each(|(input, output)| {
*output = *input >> (non_rep_bit_count - 1);
let rounding_bit = *output & Scalar::ONE;
*output += Scalar::ONE;
*output >>= 1;
*output &= mod_mask;
let need_balance =
balanced_rounding_condition_bit_trick(*output, rep_bit_count, rounding_bit);
*output = output.wrapping_sub(need_balance << rep_bit_count)
});
}
/// 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
}
/// Fills a mutable tensor-like objects with the closest representable values from another
/// tensor-like object.
///
/// # Example
///
/// ```rust
/// use tfhe::core_crypto::commons::math::decomposition::SignedDecomposer;
/// use tfhe::core_crypto::prelude::{DecompositionBaseLog, DecompositionLevelCount};
/// let decomposer =
/// SignedDecomposer::<u32>::new(DecompositionBaseLog(4), DecompositionLevelCount(3));
///
/// let input = vec![1_340_987_234_u32; 2];
/// let mut closest = vec![0u32; 2];
/// decomposer.fill_slice_with_closest_representable(&mut closest, &input);
/// assert!(closest.iter().all(|&x| x == 1_341_128_704_u32));
/// ```
pub fn fill_slice_with_closest_representable(&self, output: &mut [Scalar], input: &[Scalar]) {
assert_eq!(output.len(), input.len());
output
.iter_mut()
.zip(input.iter())
.for_each(|(dst, &src)| *dst = self.closest_representable(src));
}
/// Generate an iterator over the terms of the decomposition of the input.
///
/// # Warning
@@ -242,6 +293,89 @@ where
None
}
}
/// Generates an iterator-like object over tensors of terms of the decomposition of the input
/// tensor.
///
/// # Warning
///
/// The returned iterator yields the terms $(\tilde{\theta}^{(a)}\_i)\_{a\in\mathbb{N}}$ in
/// order of decreasing $i$.
///
/// # Example
///
/// ```rust
/// use tfhe::core_crypto::commons::math::decomposition::SignedDecomposer;
/// use tfhe::core_crypto::commons::numeric::UnsignedInteger;
/// use tfhe::core_crypto::prelude::{DecompositionBaseLog, DecompositionLevelCount};
/// let decomposer =
/// SignedDecomposer::<u32>::new(DecompositionBaseLog(4), DecompositionLevelCount(3));
/// let decomposable = vec![1_340_987_234_u32, 1_340_987_234_u32];
/// let mut decomp = decomposer.decompose_slice(&decomposable);
///
/// let mut count = 0;
/// while let Some(term) = decomp.next_term() {
/// assert!(1 <= term.level().0);
/// assert!(term.level().0 <= 3);
/// for elmt in term.as_slice().iter() {
/// let signed_term = elmt.into_signed();
/// let half_basis = 2i32.pow(4) / 2i32;
/// assert!(-half_basis <= signed_term);
/// assert!(signed_term < half_basis);
/// }
/// count += 1;
/// }
/// assert_eq!(count, 3);
/// ```
pub fn decompose_slice(&self, input: &[Scalar]) -> SliceSignedDecompositionIter<Scalar> {
// Note that there would be no sense of making the decomposition on an input which was
// not rounded to the closest representable first. We then perform it before decomposing.
let mut closest = vec![Scalar::ZERO; input.len()];
self.init_decomposer_state_slice(input, &mut closest);
SliceSignedDecompositionIter::new(
&closest,
DecompositionBaseLog(self.base_log),
DecompositionLevelCount(self.level_count),
)
}
/// Fills the output tensor with the recomposition of another tensor.
///
/// Returns `Some(())` if the decomposition was fresh, and the output was filled with a
/// recomposition, and `None`, if not.
///
/// # Example
///
/// ```rust
/// use tfhe::core_crypto::commons::math::decomposition::SignedDecomposer;
/// use tfhe::core_crypto::prelude::{DecompositionBaseLog, DecompositionLevelCount};
/// let decomposer =
/// SignedDecomposer::<u32>::new(DecompositionBaseLog(4), DecompositionLevelCount(3));
/// let decomposable = vec![1_340_987_234_u32; 2];
/// let mut rounded = vec![0u32; 2];
/// decomposer.fill_slice_with_closest_representable(&mut rounded, &decomposable);
/// let decomp = decomposer.decompose_slice(&rounded);
/// let mut recomposition = vec![0u32; 2];
/// decomposer
/// .fill_slice_with_recompose(decomp, &mut recomposition)
/// .unwrap();
/// assert_eq!(recomposition, rounded);
/// ```
pub fn fill_slice_with_recompose(
&self,
decomp: SliceSignedDecompositionIter<Scalar>,
output: &mut [Scalar],
) -> Option<()> {
let mut decomp = decomp;
if decomp.is_fresh() {
while let Some(term) = decomp.next_term() {
term.update_slice_with_recomposition_summand_wrapping_addition(output);
}
Some(())
} else {
None
}
}
}
/// A structure which allows to decompose unsigned integers into a set of smaller terms for moduli
@@ -437,6 +571,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 =
@@ -468,6 +622,36 @@ where
(abs_closest_representable, input_sign)
}
pub fn init_decomposer_state_slice(
&self,
input: &[Scalar],
output: &mut [Scalar],
signs: &mut [ValueSign],
) {
assert_eq!(input.len(), output.len());
assert_eq!(input.len(), signs.len());
let ciphertext_modulus_as_scalar: Scalar =
self.ciphertext_modulus.get_custom_modulus().cast_into();
let shift_to_native = Scalar::BITS - self.ciphertext_modulus_bit_count() as usize;
input
.iter()
.zip(output.iter_mut())
.zip(signs.iter_mut())
.for_each(|((input, output), sign)| {
if *input < ciphertext_modulus_as_scalar.div_ceil(Scalar::TWO) {
(*output, *sign) = (*input, ValueSign::Positive)
} else {
(*output, *sign) = (ciphertext_modulus_as_scalar - *input, ValueSign::Negative)
};
*output = native_closest_representable(
*output << shift_to_native,
self.level_count,
self.base_log,
) >> shift_to_native
});
}
/// Generate an iterator over the terms of the decomposition of the input.
///
/// # Warning
@@ -565,6 +749,153 @@ where
None
}
}
/// Fills a mutable tensor-like objects with the closest representable values from another
/// tensor-like object.
///
/// # Example
///
/// ```rust
/// use tfhe::core_crypto::commons::math::decomposition::{SignedDecomposerNonNative, ValueSign};
/// use tfhe::core_crypto::prelude::{
/// CiphertextModulus, DecompositionBaseLog, DecompositionLevelCount,
/// };
/// let decomposer = SignedDecomposerNonNative::new(
/// DecompositionBaseLog(4),
/// DecompositionLevelCount(3),
/// CiphertextModulus::try_new((1 << 48) + 1).unwrap(),
/// );
///
/// let input = vec![249280154129830u64; 2];
/// let mut closest = vec![0u64; 2];
/// let mut signs = vec![ValueSign::Positive; 2];
/// decomposer.init_decomposer_state_slice(&input, &mut closest, &mut signs);
/// assert!(closest.iter().all(|&x| x == 32160715112448u64));
/// decomposer.fill_slice_with_closest_representable(&mut closest, &input);
/// assert!(closest.iter().all(|&x| x == 249314261598209u64));
/// ```
pub fn fill_slice_with_closest_representable(&self, output: &mut [Scalar], input: &[Scalar]) {
assert_eq!(output.len(), input.len());
let mut signs = vec![ValueSign::Positive; input.len()];
self.init_decomposer_state_slice(input, output, &mut signs);
let modulus_as_scalar: Scalar = self.ciphertext_modulus.get_custom_modulus().cast_into();
output
.iter_mut()
.zip(signs.iter())
.for_each(|(output, sign)| match sign {
ValueSign::Positive => (),
ValueSign::Negative => *output = output.wrapping_neg_custom_mod(modulus_as_scalar),
});
}
/// Generates an iterator-like object over tensors of terms of the decomposition of the input
/// tensor.
///
/// # Warning
///
/// The returned iterator yields the terms $(\tilde{\theta}^{(a)}\_i)\_{a\in\mathbb{N}}$ in
/// order of decreasing $i$.
///
/// # Example
///
/// ```rust
/// use tfhe::core_crypto::commons::math::decomposition::SignedDecomposerNonNative;
/// use tfhe::core_crypto::commons::numeric::UnsignedInteger;
/// use tfhe::core_crypto::prelude::{
/// CiphertextModulus, DecompositionBaseLog, DecompositionLevelCount,
/// };
///
/// let decomposition_base_log = DecompositionBaseLog(4);
/// let decomposition_level_count = DecompositionLevelCount(3);
/// let ciphertext_modulus = CiphertextModulus::try_new((1 << 64) - (1 << 32) + 1).unwrap();
///
/// let decomposer = SignedDecomposerNonNative::new(
/// decomposition_base_log,
/// decomposition_level_count,
/// ciphertext_modulus,
/// );
///
/// let basis = 2i64.pow(decomposition_base_log.0.try_into().unwrap());
/// let half_basis = basis / 2;
///
/// let decomposable = [9223372032559808513u64, 1u64 << 63];
/// let mut decomp = decomposer.decompose_slice(&decomposable);
///
/// let mut count = 0;
/// while let Some(term) = decomp.next_term() {
/// assert!(1 <= term.level().0);
/// assert!(term.level().0 <= 3);
/// for elmt in term.as_slice().iter() {
/// let signed_term = elmt.into_signed();
/// assert!(-half_basis <= signed_term);
/// assert!(signed_term <= half_basis);
/// }
/// count += 1;
/// }
/// assert_eq!(count, 3);
/// ```
pub fn decompose_slice(
&self,
input: &[Scalar],
) -> SliceSignedDecompositionNonNativeIter<Scalar> {
let mut abs_closest_representables = vec![Scalar::ZERO; input.len()];
let mut signs = vec![ValueSign::Positive; input.len()];
self.init_decomposer_state_slice(input, &mut abs_closest_representables, &mut signs);
SliceSignedDecompositionNonNativeIter::new(
&abs_closest_representables,
&signs,
DecompositionBaseLog(self.base_log),
DecompositionLevelCount(self.level_count),
self.ciphertext_modulus,
)
}
/// Fills the output tensor with the recomposition of another tensor.
///
/// Returns `Some(())` if the decomposition was fresh, and the output was filled with a
/// recomposition, and `None`, if not.
///
/// # Example
///
/// ```rust
/// use tfhe::core_crypto::commons::math::decomposition::SignedDecomposerNonNative;
/// use tfhe::core_crypto::prelude::{
/// CiphertextModulus, DecompositionBaseLog, DecompositionLevelCount,
/// };
///
/// let ciphertext_modulus = CiphertextModulus::try_new((1 << 32) - (1 << 16) + 1).unwrap();
/// let decomposer = SignedDecomposerNonNative::new(
/// DecompositionBaseLog(4),
/// DecompositionLevelCount(3),
/// ciphertext_modulus,
/// );
/// let decomposable = vec![1_340_987_234_u32; 2];
/// let mut rounded = vec![0u32; 2];
/// decomposer.fill_slice_with_closest_representable(&mut rounded, &decomposable);
/// let decomp = decomposer.decompose_slice(&rounded);
/// let mut recomposition = vec![0u32; 2];
/// decomposer
/// .fill_slice_with_recompose(decomp, &mut recomposition)
/// .unwrap();
/// assert_eq!(recomposition, rounded);
/// ```
pub fn fill_slice_with_recompose(
&self,
decomp: SliceSignedDecompositionNonNativeIter<Scalar>,
output: &mut [Scalar],
) -> Option<()> {
let mut decomp = decomp;
if decomp.is_fresh() {
while let Some(term) = decomp.next_term() {
term.update_slice_with_recomposition_summand_wrapping_addition(output);
}
Some(())
} else {
None
}
}
}
#[cfg(test)]

View File

@@ -1,6 +1,7 @@
use crate::core_crypto::commons::ciphertext_modulus::CiphertextModulus;
use crate::core_crypto::commons::math::decomposition::{
DecompositionLevel, DecompositionTerm, DecompositionTermNonNative, SignedDecomposerNonNative,
DecompositionLevel, DecompositionTerm, DecompositionTermNonNative, DecompositionTermSlice,
DecompositionTermSliceNonNative, SignedDecomposerNonNative,
};
use crate::core_crypto::commons::numeric::UnsignedInteger;
use crate::core_crypto::commons::parameters::{DecompositionBaseLog, DecompositionLevelCount};
@@ -148,6 +149,149 @@ pub(crate) fn decompose_one_level<S: UnsignedInteger>(
res.wrapping_sub(carry << base_log)
}
/// An iterator-like object that yields the terms of the signed decomposition of a tensor of values.
///
/// # Note
///
/// On each call to [`SliceSignedDecompositionIter::next_term`], this structure yields a new
/// [`DecompositionTermSlice`], backed by a `Vec` owned by the structure. This vec is mutated at
/// each call of the `next_term` method, and as such the term must be dropped before `next_term` is
/// called again.
///
/// Such a pattern can not be implemented with iterators yet (without GATs), which is why this
/// iterator must be explicitly called.
///
/// # Warning
///
/// This iterator yields the decomposition in reverse order. That means that the highest level
/// will be yielded first.
pub struct SliceSignedDecompositionIter<T>
where
T: UnsignedInteger,
{
// The base log of the decomposition
base_log: usize,
// The number of levels of the decomposition
level_count: usize,
// The current level
current_level: usize,
// A mask which allows to compute the mod B of a value. For B=2^4, this guy is of the form:
// ...0001111
mod_b_mask: T,
// The internal states of each decomposition
states: Vec<T>,
// In order to avoid allocating a new Vec every time we yield a decomposition term, we store
// a Vec inside the structure and yield slices pointing to it.
outputs: Vec<T>,
// A flag which stores whether the iterator is a fresh one (for the recompose method).
fresh: bool,
}
impl<T> SliceSignedDecompositionIter<T>
where
T: UnsignedInteger,
{
// Creates a new tensor decomposition iterator.
pub(crate) fn new(
input: &[T],
base_log: DecompositionBaseLog,
level: DecompositionLevelCount,
) -> Self {
let len = input.len();
Self {
base_log: base_log.0,
level_count: level.0,
current_level: level.0,
mod_b_mask: (T::ONE << base_log.0) - T::ONE,
outputs: vec![T::ZERO; len],
states: input.to_vec(),
fresh: true,
}
}
pub(crate) fn is_fresh(&self) -> bool {
self.fresh
}
/// Returns the logarithm in base two of the base of this decomposition.
///
/// If the decomposition uses a base $B=2^b$, this returns $b$.
///
/// # Example
///
/// ```rust
/// use tfhe::core_crypto::commons::math::decomposition::SignedDecomposer;
/// use tfhe::core_crypto::prelude::{DecompositionBaseLog, DecompositionLevelCount};
/// let decomposer =
/// SignedDecomposer::<u32>::new(DecompositionBaseLog(4), DecompositionLevelCount(3));
/// let decomposable = vec![1_340_987_234_u32; 2];
/// let decomp = decomposer.decompose_slice(&decomposable);
/// assert_eq!(decomp.base_log(), DecompositionBaseLog(4));
/// ```
pub fn base_log(&self) -> DecompositionBaseLog {
DecompositionBaseLog(self.base_log)
}
/// Returns the number of levels of this decomposition.
///
/// If the decomposition uses $l$ levels, this returns $l$.
///
/// # Example
///
/// ```rust
/// use tfhe::core_crypto::commons::math::decomposition::SignedDecomposer;
/// use tfhe::core_crypto::prelude::{DecompositionBaseLog, DecompositionLevelCount};
/// let decomposer =
/// SignedDecomposer::<u32>::new(DecompositionBaseLog(4), DecompositionLevelCount(3));
/// let decomposable = vec![1_340_987_234_u32; 2];
/// let decomp = decomposer.decompose_slice(&decomposable);
/// assert_eq!(decomp.level_count(), DecompositionLevelCount(3));
/// ```
pub fn level_count(&self) -> DecompositionLevelCount {
DecompositionLevelCount(self.level_count)
}
/// Yield the next term of the decomposition, if any.
///
/// # Note
///
/// Because this function returns a borrowed tensor, owned by the iterator, the term must be
/// dropped before `next_term` is called again.
///
/// # Example
///
/// ```rust
/// use tfhe::core_crypto::commons::math::decomposition::{DecompositionLevel, SignedDecomposer};
/// use tfhe::core_crypto::prelude::{DecompositionBaseLog, DecompositionLevelCount};
/// let decomposer =
/// SignedDecomposer::<u32>::new(DecompositionBaseLog(4), DecompositionLevelCount(3));
/// let decomposable = vec![1_340_987_234_u32; 2];
/// let mut decomp = decomposer.decompose_slice(&decomposable);
/// let term = decomp.next_term().unwrap();
/// assert_eq!(term.level(), DecompositionLevel(3));
/// assert_eq!(term.as_slice()[0], 4294967295);
/// ```
pub fn next_term(&mut self) -> Option<DecompositionTermSlice<'_, T>> {
// The iterator is not fresh anymore.
self.fresh = false;
// We check if the decomposition is over
if self.current_level == 0 {
return None;
}
// We iterate over the elements of the outputs and decompose
for (output_i, state_i) in self.outputs.iter_mut().zip(self.states.iter_mut()) {
*output_i = decompose_one_level(self.base_log, state_i, self.mod_b_mask);
}
self.current_level -= 1;
// We return the term tensor.
Some(DecompositionTermSlice::new(
DecompositionLevel(self.current_level + 1),
DecompositionBaseLog(self.base_log),
&self.outputs,
))
}
}
/// An iterator that yields the terms of the signed decomposition of an integer.
///
/// # Warning
@@ -293,6 +437,191 @@ where
}
}
/// An iterator-like object that yields the terms of the signed decomposition of a tensor of values.
///
/// # Note
///
/// On each call to [`SliceSignedDecompositionNonNativeIter::next_term`], this structure yields a
/// new
/// [`DecompositionTermSlice`], backed by a `Vec` owned by the structure. This vec is mutated at
/// each call of the `next_term` method, and as such the term must be dropped before `next_term` is
/// called again.
///
/// Such a pattern can not be implemented with iterators yet (without GATs), which is why this
/// iterator must be explicitly called.
///
/// # Warning
///
/// This iterator yields the decomposition in reverse order. That means that the highest level
/// will be yielded first.
pub struct SliceSignedDecompositionNonNativeIter<T>
where
T: UnsignedInteger,
{
// The base log of the decomposition
base_log: usize,
// The number of levels of the decomposition
level_count: usize,
// The current level
current_level: usize,
// A mask which allows to compute the mod B of a value. For B=2^4, this guy is of the form:
// ...0001111
mod_b_mask: T,
// Ciphertext modulus
ciphertext_modulus: CiphertextModulus<T>,
// The internal states of each decomposition
states: Vec<T>,
// In order to avoid allocating a new Vec every time we yield a decomposition term, we store
// a Vec inside the structure and yield slices pointing to it.
outputs: Vec<T>,
// A flag which stores whether the iterator is a fresh one (for the recompose method).
fresh: bool,
// The signs of the input values, for the algorithm we use, returned values require adaption
// depending on the sign of the input
signs: Vec<ValueSign>,
}
impl<T> SliceSignedDecompositionNonNativeIter<T>
where
T: UnsignedInteger,
{
// Creates a new tensor decomposition iterator.
pub(crate) fn new(
input: &[T],
input_signs: &[ValueSign],
base_log: DecompositionBaseLog,
level: DecompositionLevelCount,
ciphertext_modulus: CiphertextModulus<T>,
) -> Self {
Self {
base_log: base_log.0,
level_count: level.0,
current_level: level.0,
mod_b_mask: (T::ONE << base_log.0) - T::ONE,
ciphertext_modulus,
outputs: vec![T::ZERO; input.len()],
states: input
.iter()
.map(|i| {
*i >> (ciphertext_modulus.get_custom_modulus().ceil_ilog2() as usize
- base_log.0 * level.0)
})
.collect(),
fresh: true,
signs: input_signs.to_vec(),
}
}
pub(crate) fn is_fresh(&self) -> bool {
self.fresh
}
/// Returns the logarithm in base two of the base of this decomposition.
///
/// If the decomposition uses a base $B=2^b$, this returns $b$.
///
/// # Example
///
/// ```rust
/// use tfhe::core_crypto::commons::math::decomposition::SignedDecomposerNonNative;
/// use tfhe::core_crypto::prelude::{
/// CiphertextModulus, DecompositionBaseLog, DecompositionLevelCount,
/// };
/// let decomposer = SignedDecomposerNonNative::<u32>::new(
/// DecompositionBaseLog(4),
/// DecompositionLevelCount(3),
/// CiphertextModulus::try_new((1 << 32) - (1 << 16) + 1).unwrap(),
/// );
/// let decomposable = vec![1_340_987_234_u32; 2];
/// let decomp = decomposer.decompose_slice(&decomposable);
/// assert_eq!(decomp.base_log(), DecompositionBaseLog(4));
/// ```
pub fn base_log(&self) -> DecompositionBaseLog {
DecompositionBaseLog(self.base_log)
}
/// Returns the number of levels of this decomposition.
///
/// If the decomposition uses $l$ levels, this returns $l$.
///
/// # Example
///
/// ```rust
/// use tfhe::core_crypto::commons::math::decomposition::SignedDecomposerNonNative;
/// use tfhe::core_crypto::prelude::{
/// CiphertextModulus, DecompositionBaseLog, DecompositionLevelCount,
/// };
/// let decomposer = SignedDecomposerNonNative::<u32>::new(
/// DecompositionBaseLog(4),
/// DecompositionLevelCount(3),
/// CiphertextModulus::try_new((1 << 32) - (1 << 16) + 1).unwrap(),
/// );
/// let decomposable = vec![1_340_987_234_u32; 2];
/// let decomp = decomposer.decompose_slice(&decomposable);
/// assert_eq!(decomp.level_count(), DecompositionLevelCount(3));
/// ```
pub fn level_count(&self) -> DecompositionLevelCount {
DecompositionLevelCount(self.level_count)
}
/// Yield the next term of the decomposition, if any.
///
/// # Note
///
/// Because this function returns a borrowed tensor, owned by the iterator, the term must be
/// dropped before `next_term` is called again.
///
/// # Example
///
/// ```rust
/// use tfhe::core_crypto::commons::math::decomposition::{
/// DecompositionLevel, SignedDecomposerNonNative,
/// };
/// use tfhe::core_crypto::prelude::{
/// CiphertextModulus, DecompositionBaseLog, DecompositionLevelCount,
/// };
/// let decomposer = SignedDecomposerNonNative::<u32>::new(
/// DecompositionBaseLog(4),
/// DecompositionLevelCount(3),
/// CiphertextModulus::try_new((1 << 32) - (1 << 16) + 1).unwrap(),
/// );
/// let decomposable = vec![1_340_987_234_u32; 2];
/// let mut decomp = decomposer.decompose_slice(&decomposable);
/// let term = decomp.next_term().unwrap();
/// assert_eq!(term.level(), DecompositionLevel(3));
/// assert_eq!(term.as_slice()[0], u32::MAX);
/// ```
pub fn next_term(&mut self) -> Option<DecompositionTermSliceNonNative<'_, T>> {
// The iterator is not fresh anymore.
self.fresh = false;
// We check if the decomposition is over
if self.current_level == 0 {
return None;
}
// We iterate over the elements of the outputs and decompose
for ((output_i, state_i), sign_i) in self
.outputs
.iter_mut()
.zip(self.states.iter_mut())
.zip(self.signs.iter())
{
*output_i = decompose_one_level(self.base_log, state_i, self.mod_b_mask);
*output_i = match sign_i {
ValueSign::Positive => *output_i,
ValueSign::Negative => output_i.wrapping_neg(),
};
}
self.current_level -= 1;
// We return the term tensor.
Some(DecompositionTermSliceNonNative::new(
DecompositionLevel(self.current_level + 1),
DecompositionBaseLog(self.base_log),
&self.outputs,
self.ciphertext_modulus,
))
}
}
/// Specialized high performance implementation of a non native decomposer over a collection of
/// elements, used notably in the PBS.
pub struct TensorSignedDecompositionLendingIterNonNative<'buffers> {

View File

@@ -223,3 +223,284 @@ where
DecompositionLevel(self.level)
}
}
/// A tensor whose elements are the terms of the decomposition of another tensor.
///
/// If we decompose each elements of a set of values $(\theta^{(a)})\_{a\in\mathbb{N}}$ as a set of
/// sums $(\sum\_{i=1}^l\tilde{\theta}^{(a)}\_i\frac{q}{B^i})\_{a\in\mathbb{N}}$, this represents a
/// set of $(\tilde{\theta}^{(a)}\_i)\_{a\in\mathbb{N}}$.
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct DecompositionTermSlice<'a, T>
where
T: UnsignedInteger,
{
level: usize,
base_log: usize,
slice: &'a [T],
}
impl<'a, T> DecompositionTermSlice<'a, T>
where
T: UnsignedInteger,
{
// Creates a new tensor decomposition term.
pub(crate) fn new(
level: DecompositionLevel,
base_log: DecompositionBaseLog,
slice: &'a [T],
) -> Self {
Self {
level: level.0,
base_log: base_log.0,
slice,
}
}
/// Fills the output tensor with the terms turned to summands.
///
/// If our term tensor represents a set of $(\tilde{\theta}^{(a)}\_i)\_{a\in\mathbb{N}}$ of the
/// decomposition, this method fills the output tensor with a set of
/// $(\tilde{\theta}^{(a)}\_i\frac{q}{B^i})\_{a\in\mathbb{N}}$.
///
/// # Example
///
/// ```rust
/// use tfhe::core_crypto::commons::math::decomposition::SignedDecomposer;
/// use tfhe::core_crypto::prelude::{DecompositionBaseLog, DecompositionLevelCount};
/// let decomposer =
/// SignedDecomposer::<u32>::new(DecompositionBaseLog(4), DecompositionLevelCount(3));
/// let input = vec![2u32.pow(19); 2];
/// let mut decomp = decomposer.decompose_slice(&input);
/// let term = decomp.next_term().unwrap();
/// let mut output = vec![0u32; 2];
/// term.fill_slice_with_recomposition_summand(&mut output);
/// assert!(output.iter().all(|&x| x == 1048576));
/// ```
pub fn fill_slice_with_recomposition_summand(&self, output: &mut [T]) {
assert_eq!(self.slice.len(), output.len());
output
.iter_mut()
.zip(self.slice.iter())
.for_each(|(dst, &value)| {
let shift: usize = <T as Numeric>::BITS - self.base_log * self.level;
*dst = value << shift
});
}
pub(crate) fn update_slice_with_recomposition_summand_wrapping_addition(
&self,
output: &mut [T],
) {
assert_eq!(self.slice.len(), output.len());
let shift: usize = <T as Numeric>::BITS - self.base_log * self.level;
output
.iter_mut()
.zip(self.slice.iter())
.for_each(|(out, &value)| {
*out = (*out).wrapping_add(value << shift);
});
}
/// Returns a tensor with the values of term.
///
/// # Example
///
/// ```rust
/// use tfhe::core_crypto::commons::math::decomposition::SignedDecomposer;
/// use tfhe::core_crypto::prelude::{DecompositionBaseLog, DecompositionLevelCount};
/// let decomposer =
/// SignedDecomposer::<u32>::new(DecompositionBaseLog(4), DecompositionLevelCount(3));
/// let input = vec![2u32.pow(19); 2];
/// let mut decomp = decomposer.decompose_slice(&input);
/// let term = decomp.next_term().unwrap();
/// assert_eq!(term.as_slice()[0], 1);
/// ```
pub fn as_slice(&self) -> &'a [T] {
self.slice
}
/// Returns the level of this decomposition term tensor.
///
/// # Example
///
/// ```rust
/// use tfhe::core_crypto::commons::math::decomposition::{DecompositionLevel, SignedDecomposer};
/// use tfhe::core_crypto::prelude::{DecompositionBaseLog, DecompositionLevelCount};
/// let decomposer =
/// SignedDecomposer::<u32>::new(DecompositionBaseLog(4), DecompositionLevelCount(3));
/// let input = vec![2u32.pow(19); 2];
/// let mut decomp = decomposer.decompose_slice(&input);
/// let term = decomp.next_term().unwrap();
/// assert_eq!(term.level(), DecompositionLevel(3));
/// ```
pub fn level(&self) -> DecompositionLevel {
DecompositionLevel(self.level)
}
}
/// A tensor whose elements are the terms of the decomposition of another tensor.
///
/// If we decompose each elements of a set of values $(\theta^{(a)})\_{a\in\mathbb{N}}$ as a set of
/// sums $(\sum\_{i=1}^l\tilde{\theta}^{(a)}\_i\frac{q}{B^i})\_{a\in\mathbb{N}}$, this represents a
/// set of $(\tilde{\theta}^{(a)}\_i)\_{a\in\mathbb{N}}$.
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct DecompositionTermSliceNonNative<'a, T>
where
T: UnsignedInteger,
{
level: usize,
base_log: usize,
slice: &'a [T],
ciphertext_modulus: CiphertextModulus<T>,
}
impl<'a, T> DecompositionTermSliceNonNative<'a, T>
where
T: UnsignedInteger,
{
// Creates a new tensor decomposition term.
pub(crate) fn new(
level: DecompositionLevel,
base_log: DecompositionBaseLog,
slice: &'a [T],
ciphertext_modulus: CiphertextModulus<T>,
) -> Self {
Self {
level: level.0,
base_log: base_log.0,
slice,
ciphertext_modulus,
}
}
/// Fills the output tensor with the terms turned to summands.
///
/// If our term tensor represents a set of $(\tilde{\theta}^{(a)}\_i)\_{a\in\mathbb{N}}$ of the
/// decomposition, this method fills the output tensor with a set of
/// $(\tilde{\theta}^{(a)}\_i\frac{q}{B^i})\_{a\in\mathbb{N}}$.
///
/// # Example
///
/// ```rust
/// use tfhe::core_crypto::commons::math::decomposition::SignedDecomposerNonNative;
/// use tfhe::core_crypto::prelude::{
/// CiphertextModulus, DecompositionBaseLog, DecompositionLevelCount,
/// };
/// let decomposer = SignedDecomposerNonNative::<u32>::new(
/// DecompositionBaseLog(4),
/// DecompositionLevelCount(3),
/// CiphertextModulus::try_new((1 << 32) - 1).unwrap(),
/// );
/// let input = vec![2u32.pow(19); 2];
/// let mut decomp = decomposer.decompose_slice(&input);
/// let term = decomp.next_term().unwrap();
/// let mut output = vec![0; 2];
/// term.to_approximate_recomposition_summand(&mut output);
/// assert!(output.iter().all(|&x| x == 1048576));
/// ```
pub fn to_approximate_recomposition_summand(&self, output: &mut [T]) {
assert_eq!(self.slice.len(), output.len());
let modulus_as_t = T::cast_from(self.ciphertext_modulus.get_custom_modulus());
let ciphertext_modulus_bit_count: usize = modulus_as_t.ceil_ilog2().try_into().unwrap();
let shift: usize = ciphertext_modulus_bit_count - self.base_log * self.level;
output
.iter_mut()
.zip(self.slice.iter())
.for_each(|(dst, &value)| {
if value.into_signed() >= T::Signed::ZERO {
*dst = value << shift
} else {
*dst = modulus_as_t.wrapping_add(value << shift)
}
});
}
/// Compute the value of the term modulo the modulus given when building the
/// [`DecompositionTermSliceNonNative`]
pub fn modular_value(&self, output: &mut [T]) {
assert_eq!(self.slice.len(), output.len());
let modulus_as_t = T::cast_from(self.ciphertext_modulus.get_custom_modulus());
self.slice
.iter()
.zip(output.iter_mut())
.for_each(|(&value, output)| {
if value.into_signed() >= T::Signed::ZERO {
*output = value
} else {
*output = modulus_as_t.wrapping_add(value)
}
});
}
pub(crate) fn update_slice_with_recomposition_summand_wrapping_addition(
&self,
output: &mut [T],
) {
assert_eq!(self.slice.len(), output.len());
let modulus_as_t = T::cast_from(self.ciphertext_modulus.get_custom_modulus());
let ciphertext_modulus_bit_count: usize = modulus_as_t.ceil_ilog2().try_into().unwrap();
let shift: usize = ciphertext_modulus_bit_count - self.base_log * self.level;
output
.iter_mut()
.zip(self.slice.iter())
.for_each(|(out, &value)| {
if value.into_signed() >= T::Signed::ZERO {
*out = (*out).wrapping_add_custom_mod(value << shift, modulus_as_t)
} else {
*out = (*out).wrapping_add_custom_mod(
modulus_as_t.wrapping_add(value << shift),
modulus_as_t,
)
}
});
}
/// Returns a tensor with the values of term.
///
/// # Example
///
/// ```rust
/// use tfhe::core_crypto::commons::math::decomposition::SignedDecomposerNonNative;
/// use tfhe::core_crypto::prelude::{
/// CiphertextModulus, DecompositionBaseLog, DecompositionLevelCount,
/// };
/// let decomposer = SignedDecomposerNonNative::<u32>::new(
/// DecompositionBaseLog(4),
/// DecompositionLevelCount(3),
/// CiphertextModulus::try_new((1 << 32) - 1).unwrap(),
/// );
/// let input = vec![2u32.pow(19); 2];
/// let mut decomp = decomposer.decompose_slice(&input);
/// let term = decomp.next_term().unwrap();
/// assert_eq!(term.as_slice()[0], 1);
/// ```
pub fn as_slice(&self) -> &'a [T] {
self.slice
}
/// Returns the level of this decomposition term tensor.
///
/// # Example
///
/// ```rust
/// use tfhe::core_crypto::commons::math::decomposition::{
/// DecompositionLevel, SignedDecomposerNonNative,
/// };
/// use tfhe::core_crypto::prelude::{
/// CiphertextModulus, DecompositionBaseLog, DecompositionLevelCount,
/// };
/// let decomposer = SignedDecomposerNonNative::<u32>::new(
/// DecompositionBaseLog(4),
/// DecompositionLevelCount(3),
/// CiphertextModulus::try_new((1 << 32) - 1).unwrap(),
/// );
/// let input = vec![2u32.pow(19); 2];
/// let mut decomp = decomposer.decompose_slice(&input);
/// let term = decomp.next_term().unwrap();
/// assert_eq!(term.level(), DecompositionLevel(3));
/// ```
pub fn level(&self) -> DecompositionLevel {
DecompositionLevel(self.level)
}
}

View File

@@ -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 <k\_{\mathsf{in}}$ we have $\overline{\mathsf{CT}\_i} \in
/// \mathsf{GLev}\_{\vec{S}\_{\mathsf{out}}}^{\beta, \ell}\left(S\_i\right)$.
///
/// ## GLWE Keyswitch
///
/// This homomorphic procedure transforms an input
/// [`GLWE ciphertext`](`crate::core_crypto::entities::GlweCiphertext`)
/// $\mathsf{CT}\_{\mathsf{in}} =
/// \left( \vec{A}\_{\mathsf{in}} , B\_{\mathsf{in}}\right) \in \mathsf{GLWE}^{k\_{\mathsf{in}}}\_
/// {\vec{S}\_{\mathsf{in}}}( \mathsf{PT} ) \subseteq R\_q^{(k\_{\mathsf{in}}+1)}$ into an
/// output [`GLWE ciphertext`](`crate::core_crypto::entities::GlweCiphertext`)
/// $\mathsf{CT}\_{\mathsf{out}} =
/// \left( \vec{A}\_{\mathsf{out}} , B\_{\mathsf{out}}\right) \in
/// \mathsf{GLWE}^{k\_{\mathsf{out}}}\_{\vec{S}\_{\mathsf{out}}}( \mathsf{PT} )\subseteq
/// R\_q^{(k\_{\mathsf{out}}+1)}$ where $k\_{\mathsf{in}} = |\vec{S}\_{\mathsf{in}}|$ and
/// $k\_{\mathsf{out}} = |\vec{S}\_{\mathsf{out}}|$. It requires a
/// [`key switching key`](`crate::core_crypto::entities::GlweKeyswitchKey`).
/// The input ciphertext is encrypted under the
/// [`GLWE secret key`](`crate::core_crypto::entities::GlweSecretKey`)
/// $\vec{S}\_{\mathsf{in}}$ and the output ciphertext is
/// encrypted under the [`GLWE secret key`](`crate::core_crypto::entities::GlweSecretKey`)
/// $\vec{S}\_{\mathsf{out}}$.
///
/// $$\mathsf{CT}\_{\mathsf{in}} \in \mathsf{GLWE}^{k\_{\mathsf{in}}}\_{\vec{S}\_{\mathsf{in}}}(
/// \mathsf{PT} ) ~~~~~~~~~~\mathsf{KSK}\_{\vec{S}\_{\mathsf{in}}\rightarrow
/// \vec{S}\_{\mathsf{out}}}$$ $$ \mathsf{keyswitch}\left(\mathsf{CT}\_{\mathsf{in}} , \mathsf{KSK}
/// \right) \rightarrow \mathsf{CT}\_{\mathsf{out}} \in
/// \mathsf{GLWE}^{k\_{\mathsf{out}}}\_{\vec{S}\_{\mathsf{out}}} \left( \mathsf{PT} \right)$$
///
/// ## Algorithm
/// ###### inputs:
/// - $\mathsf{CT}\_{\mathsf{in}} = \left( \vec{A}\_{\mathsf{in}} , B\_{\mathsf{in}}\right) \in
/// \mathsf{GLWE}^{k\_{\mathsf{in}}}\_{\vec{S}\_{\mathsf{in}}}( \mathsf{PT} )$: a [`GLWE
/// ciphertext`](`GlweCiphertext`) with $\vec{A}\_{\mathsf{in}}=\left(A\_0, \cdots
/// A\_{k\_{\mathsf{in}}-1}\right)$
/// - $\mathsf{KSK}\_{\vec{S}\_{\mathsf{in}}\rightarrow \vec{S}\_{\mathsf{out}}}$: a [`key switching
/// key`](`crate::core_crypto::entities::GlweKeyswitchKey`)
///
/// ###### outputs:
/// - $\mathsf{CT}\_{\mathsf{out}} \in \mathsf{GLWE}^{k\_{\mathsf{out}}}\_{\vec{S}\_{\mathsf{out}}}
/// \left( \mathsf{PT} \right)$: a [`GLWE
/// ciphertext`](`crate::core_crypto::entities::GlweCiphertext`)
///
/// ###### algorithm:
/// 1. set $\mathsf{cCT}=\left( 0 , \cdots , 0 , B\_{\mathsf{in}} \right) \in
/// R\_q^{(k\_{\mathsf{out}}+1)}$
/// 2. compute $\mathsf{CT}\_{\mathsf{out}} = \mathsf{CT} - \sum\_{i=0}^{k\_{\mathsf{in}}-1}
/// \mathsf{decompProduct}\left( A\_i , \overline{\mathsf{CT}\_i} \right)$
/// 3. output $\mathsf{CT}\_{\mathsf{out}}$
#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct GlweKeyswitchKey<C: Container>
where
C::Element: UnsignedInteger,
{
data: C,
decomp_base_log: DecompositionBaseLog,
decomp_level_count: DecompositionLevelCount,
output_glwe_size: GlweSize,
poly_size: PolynomialSize,
ciphertext_modulus: CiphertextModulus<C::Element>,
}
impl<T: UnsignedInteger, C: Container<Element = T>> AsRef<[T]> for GlweKeyswitchKey<C> {
fn as_ref(&self) -> &[T] {
self.data.as_ref()
}
}
impl<T: UnsignedInteger, C: ContainerMut<Element = T>> AsMut<[T]> for GlweKeyswitchKey<C> {
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<Scalar: UnsignedInteger, C: Container<Element = Scalar>> GlweKeyswitchKey<C> {
/// 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<u64> = 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<C::Element>,
) -> 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<C::Element> {
self.ciphertext_modulus
}
}
impl<Scalar: UnsignedInteger, C: ContainerMut<Element = Scalar>> GlweKeyswitchKey<C> {
/// 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<Scalar> = GlweKeyswitchKey<Vec<Scalar>>;
/// 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<Scalar: UnsignedInteger> GlweKeyswitchKeyOwned<Scalar> {
/// 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<Scalar>,
) -> 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<Scalar: UnsignedInteger> {
pub decomp_base_log: DecompositionBaseLog,
pub decomp_level_count: DecompositionLevelCount,
pub output_glwe_size: GlweSize,
pub polynomial_size: PolynomialSize,
pub ciphertext_modulus: CiphertextModulus<Scalar>,
}
impl<Scalar: UnsignedInteger, C: Container<Element = Scalar>> CreateFrom<C>
for GlweKeyswitchKey<C>
{
type Metadata = GlweKeyswitchKeyCreationMetadata<Scalar>;
#[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<Scalar: UnsignedInteger, C: Container<Element = Scalar>> ContiguousEntityContainer
for GlweKeyswitchKey<C>
{
type Element = C::Element;
type EntityViewMetadata = GlweCiphertextListCreationMetadata<Self::Element>;
type EntityView<'this>
= GlweCiphertextListView<'this, Self::Element>
where
Self: 'this;
type SelfViewMetadata = GlweKeyswitchKeyCreationMetadata<Self::Element>;
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<Scalar: UnsignedInteger, C: ContainerMut<Element = Scalar>> ContiguousEntityContainerMut
for GlweKeyswitchKey<C>
{
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<u64>,
}
impl<C: Container<Element = u64>> ParameterSetConformant for GlweKeyswitchKey<C> {
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
}
}

View File

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