Skip to content

Commit 1c4c638

Browse files
committed
feat(core_crypto): add glwe keyswitch
1 parent a9601fc commit 1c4c638

File tree

9 files changed

+2191
-2
lines changed

9 files changed

+2191
-2
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,304 @@
1+
//! Module containing primitives pertaining to [`GLWE ciphertext
2+
//! keyswitch`](`GlweKeyswitchKey#glwe-keyswitch`).
3+
4+
use crate::core_crypto::algorithms::polynomial_algorithms::*;
5+
use crate::core_crypto::commons::math::decomposition::{
6+
SignedDecomposer, SignedDecomposerNonNative,
7+
};
8+
use crate::core_crypto::commons::numeric::UnsignedInteger;
9+
use crate::core_crypto::commons::traits::*;
10+
use crate::core_crypto::entities::*;
11+
12+
/// Keyswitch a [`GLWE ciphertext`](`GlweCiphertext`) encrypted under a
13+
/// [`GLWE secret key`](`GlweSecretKey`) to another [`GLWE secret key`](`GlweSecretKey`).
14+
///
15+
/// # Formal Definition
16+
///
17+
/// See [`GLWE keyswitch key`](`GlweKeyswitchKey#glwe-keyswitch`).
18+
///
19+
/// # Example
20+
///
21+
/// ```
22+
/// use tfhe::core_crypto::prelude::*;
23+
///
24+
/// // DISCLAIMER: these toy example parameters are not guaranteed to be secure or yield correct
25+
/// // computations
26+
/// // Define parameters for GlweKeyswitchKey creation
27+
/// let input_glwe_dimension = GlweDimension(2);
28+
/// let poly_size = PolynomialSize(512);
29+
/// let glwe_noise_distribution = Gaussian::from_dispersion_parameter(
30+
/// StandardDev(0.00000000000000000000007069849454709433),
31+
/// 0.0,
32+
/// );
33+
/// let output_glwe_dimension = GlweDimension(1);
34+
/// let decomp_base_log = DecompositionBaseLog(21);
35+
/// let decomp_level_count = DecompositionLevelCount(2);
36+
/// let ciphertext_modulus = CiphertextModulus::new_native();
37+
/// let delta = 1 << 59;
38+
///
39+
/// // Create the PRNG
40+
/// let mut seeder = new_seeder();
41+
/// let seeder = seeder.as_mut();
42+
/// let mut encryption_generator =
43+
/// EncryptionRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed(), seeder);
44+
/// let mut secret_generator =
45+
/// SecretRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed());
46+
///
47+
/// // Create the LweSecretKey
48+
/// let input_glwe_secret_key = allocate_and_generate_new_binary_glwe_secret_key(
49+
/// input_glwe_dimension,
50+
/// poly_size,
51+
/// &mut secret_generator,
52+
/// );
53+
/// let output_glwe_secret_key = allocate_and_generate_new_binary_glwe_secret_key(
54+
/// output_glwe_dimension,
55+
/// poly_size,
56+
/// &mut secret_generator,
57+
/// );
58+
///
59+
/// let ksk = allocate_and_generate_new_glwe_keyswitch_key(
60+
/// &input_glwe_secret_key,
61+
/// &output_glwe_secret_key,
62+
/// decomp_base_log,
63+
/// decomp_level_count,
64+
/// glwe_noise_distribution,
65+
/// ciphertext_modulus,
66+
/// &mut encryption_generator,
67+
/// );
68+
///
69+
/// // Create the plaintext
70+
/// let msg = 3u64;
71+
/// let plaintext_list = PlaintextList::new(msg * delta, PlaintextCount(poly_size.0));
72+
///
73+
/// // Create a new GlweCiphertext
74+
/// let mut input_glwe = GlweCiphertext::new(
75+
/// 0u64,
76+
/// input_glwe_dimension.to_glwe_size(),
77+
/// poly_size,
78+
/// ciphertext_modulus,
79+
/// );
80+
///
81+
/// encrypt_glwe_ciphertext(
82+
/// &input_glwe_secret_key,
83+
/// &mut input_glwe,
84+
/// &plaintext_list,
85+
/// glwe_noise_distribution,
86+
/// &mut encryption_generator,
87+
/// );
88+
///
89+
/// let mut output_glwe = GlweCiphertext::new(
90+
/// 0u64,
91+
/// output_glwe_secret_key.glwe_dimension().to_glwe_size(),
92+
/// output_glwe_secret_key.polynomial_size(),
93+
/// ciphertext_modulus,
94+
/// );
95+
///
96+
/// keyswitch_glwe_ciphertext(&ksk, &input_glwe, &mut output_glwe);
97+
///
98+
/// // Round and remove encoding
99+
/// // First create a decomposer working on the high 5 bits corresponding to our encoding.
100+
/// let decomposer = SignedDecomposer::new(DecompositionBaseLog(5), DecompositionLevelCount(1));
101+
///
102+
/// let mut output_plaintext_list = PlaintextList::new(0u64, plaintext_list.plaintext_count());
103+
///
104+
/// decrypt_glwe_ciphertext(
105+
/// &output_glwe_secret_key,
106+
/// &output_glwe,
107+
/// &mut output_plaintext_list,
108+
/// );
109+
///
110+
/// // Get the raw vector
111+
/// let mut cleartext_list = output_plaintext_list.into_container();
112+
/// // Remove the encoding
113+
/// cleartext_list
114+
/// .iter_mut()
115+
/// .for_each(|elt| *elt = decomposer.decode_plaintext(*elt));
116+
/// // Get the list immutably
117+
/// let cleartext_list = cleartext_list;
118+
///
119+
/// // Check we recovered the original message for each plaintext we encrypted
120+
/// cleartext_list.iter().for_each(|&elt| assert_eq!(elt, msg));
121+
/// ```
122+
pub fn keyswitch_glwe_ciphertext<Scalar, KSKCont, InputCont, OutputCont>(
123+
glwe_keyswitch_key: &GlweKeyswitchKey<KSKCont>,
124+
input_glwe_ciphertext: &GlweCiphertext<InputCont>,
125+
output_glwe_ciphertext: &mut GlweCiphertext<OutputCont>,
126+
) where
127+
Scalar: UnsignedInteger,
128+
KSKCont: Container<Element = Scalar>,
129+
InputCont: Container<Element = Scalar>,
130+
OutputCont: ContainerMut<Element = Scalar>,
131+
{
132+
if glwe_keyswitch_key
133+
.ciphertext_modulus()
134+
.is_compatible_with_native_modulus()
135+
{
136+
keyswitch_glwe_ciphertext_native_mod_compatible(
137+
glwe_keyswitch_key,
138+
input_glwe_ciphertext,
139+
output_glwe_ciphertext,
140+
)
141+
} else {
142+
keyswitch_glwe_ciphertext_other_mod(
143+
glwe_keyswitch_key,
144+
input_glwe_ciphertext,
145+
output_glwe_ciphertext,
146+
)
147+
}
148+
}
149+
150+
pub fn keyswitch_glwe_ciphertext_native_mod_compatible<Scalar, KSKCont, InputCont, OutputCont>(
151+
glwe_keyswitch_key: &GlweKeyswitchKey<KSKCont>,
152+
input_glwe_ciphertext: &GlweCiphertext<InputCont>,
153+
output_glwe_ciphertext: &mut GlweCiphertext<OutputCont>,
154+
) where
155+
Scalar: UnsignedInteger,
156+
KSKCont: Container<Element = Scalar>,
157+
InputCont: Container<Element = Scalar>,
158+
OutputCont: ContainerMut<Element = Scalar>,
159+
{
160+
assert!(
161+
glwe_keyswitch_key.input_key_glwe_dimension()
162+
== input_glwe_ciphertext.glwe_size().to_glwe_dimension(),
163+
"Mismatched input GlweDimension. \
164+
GlweKeyswitchKey input GlweDimension: {:?}, input GlweCiphertext GlweDimension {:?}.",
165+
glwe_keyswitch_key.input_key_glwe_dimension(),
166+
input_glwe_ciphertext.glwe_size().to_glwe_dimension(),
167+
);
168+
assert!(
169+
glwe_keyswitch_key.output_key_glwe_dimension()
170+
== output_glwe_ciphertext.glwe_size().to_glwe_dimension(),
171+
"Mismatched output GlweDimension. \
172+
GlweKeyswitchKey output GlweDimension: {:?}, output GlweCiphertext GlweDimension {:?}.",
173+
glwe_keyswitch_key.output_key_glwe_dimension(),
174+
output_glwe_ciphertext.glwe_size().to_glwe_dimension(),
175+
);
176+
assert!(
177+
glwe_keyswitch_key.polynomial_size() == input_glwe_ciphertext.polynomial_size(),
178+
"Mismatched input PolynomialSize. \
179+
GlweKeyswithcKey input PolynomialSize: {:?}, input GlweCiphertext PolynomialSize {:?}.",
180+
glwe_keyswitch_key.polynomial_size(),
181+
input_glwe_ciphertext.polynomial_size(),
182+
);
183+
assert!(
184+
glwe_keyswitch_key.polynomial_size() == output_glwe_ciphertext.polynomial_size(),
185+
"Mismatched output PolynomialSize. \
186+
GlweKeyswitchKey output PolynomialSize: {:?}, output GlweCiphertext PolynomialSize {:?}.",
187+
glwe_keyswitch_key.polynomial_size(),
188+
output_glwe_ciphertext.polynomial_size(),
189+
);
190+
assert!(glwe_keyswitch_key
191+
.ciphertext_modulus()
192+
.is_compatible_with_native_modulus());
193+
194+
// Clear the output ciphertext, as it will get updated gradually
195+
output_glwe_ciphertext.as_mut().fill(Scalar::ZERO);
196+
197+
// Copy the input body to the output ciphertext
198+
polynomial_wrapping_add_assign(
199+
&mut output_glwe_ciphertext.get_mut_body().as_mut_polynomial(),
200+
&input_glwe_ciphertext.get_body().as_polynomial(),
201+
);
202+
203+
// We instantiate a decomposer
204+
let decomposer = SignedDecomposer::new(
205+
glwe_keyswitch_key.decomposition_base_log(),
206+
glwe_keyswitch_key.decomposition_level_count(),
207+
);
208+
209+
for (keyswitch_key_block, input_mask_element) in glwe_keyswitch_key
210+
.iter()
211+
.zip(input_glwe_ciphertext.get_mask().as_polynomial_list().iter())
212+
{
213+
let mut decomposition_iter = decomposer.decompose_slice(input_mask_element.as_ref());
214+
// loop over the number of levels
215+
for level_key_ciphertext in keyswitch_key_block.iter() {
216+
let decomposed = decomposition_iter.next_term().unwrap();
217+
polynomial_list_wrapping_sub_scalar_mul_assign(
218+
&mut output_glwe_ciphertext.as_mut_polynomial_list(),
219+
&level_key_ciphertext.as_polynomial_list(),
220+
&Polynomial::from_container(decomposed.as_slice()),
221+
);
222+
}
223+
}
224+
}
225+
226+
pub fn keyswitch_glwe_ciphertext_other_mod<Scalar, KSKCont, InputCont, OutputCont>(
227+
glwe_keyswitch_key: &GlweKeyswitchKey<KSKCont>,
228+
input_glwe_ciphertext: &GlweCiphertext<InputCont>,
229+
output_glwe_ciphertext: &mut GlweCiphertext<OutputCont>,
230+
) where
231+
Scalar: UnsignedInteger,
232+
KSKCont: Container<Element = Scalar>,
233+
InputCont: Container<Element = Scalar>,
234+
OutputCont: ContainerMut<Element = Scalar>,
235+
{
236+
assert!(
237+
glwe_keyswitch_key.input_key_glwe_dimension()
238+
== input_glwe_ciphertext.glwe_size().to_glwe_dimension(),
239+
"Mismatched input GlweDimension. \
240+
GlweKeyswitchKey input GlweDimension: {:?}, input GlweCiphertext GlweDimension {:?}.",
241+
glwe_keyswitch_key.input_key_glwe_dimension(),
242+
input_glwe_ciphertext.glwe_size().to_glwe_dimension(),
243+
);
244+
assert!(
245+
glwe_keyswitch_key.output_key_glwe_dimension()
246+
== output_glwe_ciphertext.glwe_size().to_glwe_dimension(),
247+
"Mismatched output GlweDimension. \
248+
GlweKeyswitchKey output GlweDimension: {:?}, output GlweCiphertext GlweDimension {:?}.",
249+
glwe_keyswitch_key.output_key_glwe_dimension(),
250+
output_glwe_ciphertext.glwe_size().to_glwe_dimension(),
251+
);
252+
assert!(
253+
glwe_keyswitch_key.polynomial_size() == input_glwe_ciphertext.polynomial_size(),
254+
"Mismatched input PolynomialSize. \
255+
GlweKeyswithcKey input PolynomialSize: {:?}, input GlweCiphertext PolynomialSize {:?}.",
256+
glwe_keyswitch_key.polynomial_size(),
257+
input_glwe_ciphertext.polynomial_size(),
258+
);
259+
assert!(
260+
glwe_keyswitch_key.polynomial_size() == output_glwe_ciphertext.polynomial_size(),
261+
"Mismatched output PolynomialSize. \
262+
GlweKeyswitchKey output PolynomialSize: {:?}, output GlweCiphertext PolynomialSize {:?}.",
263+
glwe_keyswitch_key.polynomial_size(),
264+
output_glwe_ciphertext.polynomial_size(),
265+
);
266+
let ciphertext_modulus = glwe_keyswitch_key.ciphertext_modulus();
267+
assert!(!ciphertext_modulus.is_compatible_with_native_modulus());
268+
269+
// Clear the output ciphertext, as it will get updated gradually
270+
output_glwe_ciphertext.as_mut().fill(Scalar::ZERO);
271+
272+
// Copy the input body to the output ciphertext (no need to use non native addition here)
273+
polynomial_wrapping_add_assign(
274+
&mut output_glwe_ciphertext.get_mut_body().as_mut_polynomial(),
275+
&input_glwe_ciphertext.get_body().as_polynomial(),
276+
);
277+
278+
// We instantiate a decomposer
279+
let decomposer = SignedDecomposerNonNative::new(
280+
glwe_keyswitch_key.decomposition_base_log(),
281+
glwe_keyswitch_key.decomposition_level_count(),
282+
ciphertext_modulus,
283+
);
284+
285+
let mut scalar_poly = Polynomial::new(Scalar::ZERO, input_glwe_ciphertext.polynomial_size());
286+
287+
for (keyswitch_key_block, input_mask_element) in glwe_keyswitch_key
288+
.iter()
289+
.zip(input_glwe_ciphertext.get_mask().as_polynomial_list().iter())
290+
{
291+
let mut decomposition_iter = decomposer.decompose_slice(input_mask_element.as_ref());
292+
// loop over the number of levels
293+
for level_key_ciphertext in keyswitch_key_block.iter() {
294+
let decomposed = decomposition_iter.next_term().unwrap();
295+
decomposed.modular_value(scalar_poly.as_mut());
296+
polynomial_list_wrapping_sub_scalar_mul_assign_custom_mod(
297+
&mut output_glwe_ciphertext.as_mut_polynomial_list(),
298+
&level_key_ciphertext.as_polynomial_list(),
299+
&scalar_poly,
300+
ciphertext_modulus.get_custom_modulus().cast_into(),
301+
);
302+
}
303+
}
304+
}

0 commit comments

Comments
 (0)