poulpy/core/src/test_fft64/gglwe.rs
core/src/test_fft64/gglwe.rsRaw
1use base2k::{FFT64, Module, ScalarZnx, ScalarZnxAlloc, ScratchOwned, Stats, VecZnxOps, ZnxViewMut};
2use sampling::source::Source;
3
4use crate::{
5 elem::{GetRow, Infos},
6 ggsw_ciphertext::GGSWCiphertext,
7 glwe_ciphertext_fourier::GLWECiphertextFourier,
8 glwe_plaintext::GLWEPlaintext,
9 keys::{SecretKey, SecretKeyFourier},
10 keyswitch_key::GLWESwitchingKey,
11 test_fft64::ggsw::noise_rgsw_product,
12};
13
14#[test]
15fn encrypt_sk() {
16 let module: Module<FFT64> = Module::<FFT64>::new(2048);
17 let log_base2k: usize = 8;
18 let log_k_ct: usize = 54;
19 let rows: usize = 4;
20 let rank: usize = 1;
21 let rank_out: usize = 1;
22
23 let sigma: f64 = 3.2;
24
25 let mut ct: GLWESwitchingKey<Vec<u8>, FFT64> = GLWESwitchingKey::new(&module, log_base2k, log_k_ct, rows, rank, rank_out);
26 let mut pt: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::new(&module, log_base2k, log_k_ct);
27 let mut pt_scalar: ScalarZnx<Vec<u8>> = module.new_scalar_znx(1);
28
29 let mut source_xs: Source = Source::new([0u8; 32]);
30 let mut source_xe: Source = Source::new([0u8; 32]);
31 let mut source_xa: Source = Source::new([0u8; 32]);
32
33 pt_scalar.fill_ternary_hw(0, module.n(), &mut source_xs);
34
35 let mut scratch: ScratchOwned = ScratchOwned::new(
36 GLWESwitchingKey::encrypt_sk_scratch_space(&module, rank, ct.size())
37 | GLWECiphertextFourier::decrypt_scratch_space(&module, ct.size()),
38 );
39
40 let mut sk: SecretKey<Vec<u8>> = SecretKey::new(&module, rank);
41 // sk.fill_ternary_prob(0.5, &mut source_xs);
42 sk.fill_zero();
43
44 let mut sk_dft: SecretKeyFourier<Vec<u8>, FFT64> = SecretKeyFourier::new(&module, rank);
45 sk_dft.dft(&module, &sk);
46
47 ct.encrypt_sk(
48 &module,
49 &pt_scalar,
50 &sk_dft,
51 &mut source_xa,
52 &mut source_xe,
53 sigma,
54 scratch.borrow(),
55 );
56
57 let mut ct_rlwe_dft: GLWECiphertextFourier<Vec<u8>, FFT64> = GLWECiphertextFourier::new(&module, log_base2k, log_k_ct, rank);
58
59 (0..ct.rows()).for_each(|row_i| {
60 ct.get_row(&module, row_i, 0, &mut ct_rlwe_dft);
61 ct_rlwe_dft.decrypt(&module, &mut pt, &sk_dft, scratch.borrow());
62 module.vec_znx_sub_scalar_inplace(&mut pt, 0, row_i, &pt_scalar, 0);
63 let std_pt: f64 = pt.data.std(0, log_base2k) * (log_k_ct as f64).exp2();
64 assert!((sigma - std_pt).abs() <= 0.2, "{} {}", sigma, std_pt);
65 });
66}
67
68#[test]
69fn keyswitch() {
70 let module: Module<FFT64> = Module::<FFT64>::new(2048);
71 let log_base2k: usize = 12;
72 let log_k_grlwe: usize = 60;
73 let rows: usize = (log_k_grlwe + log_base2k - 1) / log_base2k;
74
75 let rank: usize = 1;
76
77 let sigma: f64 = 3.2;
78
79 let mut ct_grlwe_s0s1: GLWESwitchingKey<Vec<u8>, FFT64> =
80 GLWESwitchingKey::new(&module, log_base2k, log_k_grlwe, rows, rank, rank);
81 let mut ct_grlwe_s1s2: GLWESwitchingKey<Vec<u8>, FFT64> =
82 GLWESwitchingKey::new(&module, log_base2k, log_k_grlwe, rows, rank, rank);
83 let mut ct_grlwe_s0s2: GLWESwitchingKey<Vec<u8>, FFT64> =
84 GLWESwitchingKey::new(&module, log_base2k, log_k_grlwe, rows, rank, rank);
85
86 let mut source_xs: Source = Source::new([0u8; 32]);
87 let mut source_xe: Source = Source::new([0u8; 32]);
88 let mut source_xa: Source = Source::new([0u8; 32]);
89
90 let mut scratch: ScratchOwned = ScratchOwned::new(
91 GLWESwitchingKey::encrypt_sk_scratch_space(&module, rank, ct_grlwe_s0s1.size())
92 | GLWECiphertextFourier::decrypt_scratch_space(&module, ct_grlwe_s0s2.size())
93 | GLWESwitchingKey::keyswitch_scratch_space(
94 &module,
95 ct_grlwe_s0s2.size(),
96 ct_grlwe_s0s1.size(),
97 ct_grlwe_s1s2.size(),
98 ),
99 );
100
101 let mut sk0: SecretKey<Vec<u8>> = SecretKey::new(&module, rank);
102 sk0.fill_ternary_prob(0.5, &mut source_xs);
103
104 let mut sk0_dft: SecretKeyFourier<Vec<u8>, FFT64> = SecretKeyFourier::new(&module, rank);
105 sk0_dft.dft(&module, &sk0);
106
107 let mut sk1: SecretKey<Vec<u8>> = SecretKey::new(&module, rank);
108 sk1.fill_ternary_prob(0.5, &mut source_xs);
109
110 let mut sk1_dft: SecretKeyFourier<Vec<u8>, FFT64> = SecretKeyFourier::new(&module, rank);
111 sk1_dft.dft(&module, &sk1);
112
113 let mut sk2: SecretKey<Vec<u8>> = SecretKey::new(&module, rank);
114 sk2.fill_ternary_prob(0.5, &mut source_xs);
115
116 let mut sk2_dft: SecretKeyFourier<Vec<u8>, FFT64> = SecretKeyFourier::new(&module, rank);
117 sk2_dft.dft(&module, &sk2);
118
119 // GRLWE_{s1}(s0) = s0 -> s1
120 ct_grlwe_s0s1.encrypt_sk(
121 &module,
122 &sk0.data,
123 &sk1_dft,
124 &mut source_xa,
125 &mut source_xe,
126 sigma,
127 scratch.borrow(),
128 );
129
130 // GRLWE_{s2}(s1) -> s1 -> s2
131 ct_grlwe_s1s2.encrypt_sk(
132 &module,
133 &sk1.data,
134 &sk2_dft,
135 &mut source_xa,
136 &mut source_xe,
137 sigma,
138 scratch.borrow(),
139 );
140
141 // GRLWE_{s1}(s0) (x) GRLWE_{s2}(s1) = GRLWE_{s2}(s0)
142 ct_grlwe_s0s2.keyswitch(&module, &ct_grlwe_s0s1, &ct_grlwe_s1s2, scratch.borrow());
143
144 let mut ct_rlwe_dft_s0s2: GLWECiphertextFourier<Vec<u8>, FFT64> =
145 GLWECiphertextFourier::new(&module, log_base2k, log_k_grlwe, rank);
146 let mut pt: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::new(&module, log_base2k, log_k_grlwe);
147
148 (0..ct_grlwe_s0s2.rows()).for_each(|row_i| {
149 ct_grlwe_s0s2.get_row(&module, row_i, 0, &mut ct_rlwe_dft_s0s2);
150 ct_rlwe_dft_s0s2.decrypt(&module, &mut pt, &sk2_dft, scratch.borrow());
151 module.vec_znx_sub_scalar_inplace(&mut pt, 0, row_i, &sk0, 0);
152
153 let noise_have: f64 = pt.data.std(0, log_base2k).log2();
154 let noise_want: f64 = noise_grlwe_rlwe_product(
155 module.n() as f64,
156 log_base2k,
157 0.5,
158 0.5,
159 0f64,
160 sigma * sigma,
161 0f64,
162 log_k_grlwe,
163 log_k_grlwe,
164 );
165
166 assert!(
167 (noise_have - noise_want).abs() <= 0.1,
168 "{} {}",
169 noise_have,
170 noise_want
171 );
172 });
173}
174
175#[test]
176fn keyswitch_inplace() {
177 let module: Module<FFT64> = Module::<FFT64>::new(2048);
178 let log_base2k: usize = 12;
179 let log_k_grlwe: usize = 60;
180 let rows: usize = (log_k_grlwe + log_base2k - 1) / log_base2k;
181
182 let rank: usize = 1;
183 let rank_out: usize = 1;
184
185 let sigma: f64 = 3.2;
186
187 let mut ct_grlwe_s0s1: GLWESwitchingKey<Vec<u8>, FFT64> =
188 GLWESwitchingKey::new(&module, log_base2k, log_k_grlwe, rows, rank, rank_out);
189 let mut ct_grlwe_s1s2: GLWESwitchingKey<Vec<u8>, FFT64> =
190 GLWESwitchingKey::new(&module, log_base2k, log_k_grlwe, rows, rank, rank_out);
191
192 let mut source_xs: Source = Source::new([0u8; 32]);
193 let mut source_xe: Source = Source::new([0u8; 32]);
194 let mut source_xa: Source = Source::new([0u8; 32]);
195
196 let mut scratch: ScratchOwned = ScratchOwned::new(
197 GLWESwitchingKey::encrypt_sk_scratch_space(&module, rank, ct_grlwe_s0s1.size())
198 | GLWECiphertextFourier::decrypt_scratch_space(&module, ct_grlwe_s0s1.size())
199 | GLWESwitchingKey::keyswitch_inplace_scratch_space(&module, ct_grlwe_s0s1.size(), ct_grlwe_s1s2.size()),
200 );
201
202 let mut sk0: SecretKey<Vec<u8>> = SecretKey::new(&module, rank);
203 sk0.fill_ternary_prob(0.5, &mut source_xs);
204
205 let mut sk0_dft: SecretKeyFourier<Vec<u8>, FFT64> = SecretKeyFourier::new(&module, rank);
206 sk0_dft.dft(&module, &sk0);
207
208 let mut sk1: SecretKey<Vec<u8>> = SecretKey::new(&module, rank);
209 sk1.fill_ternary_prob(0.5, &mut source_xs);
210
211 let mut sk1_dft: SecretKeyFourier<Vec<u8>, FFT64> = SecretKeyFourier::new(&module, rank);
212 sk1_dft.dft(&module, &sk1);
213
214 let mut sk2: SecretKey<Vec<u8>> = SecretKey::new(&module, rank);
215 sk2.fill_ternary_prob(0.5, &mut source_xs);
216
217 let mut sk2_dft: SecretKeyFourier<Vec<u8>, FFT64> = SecretKeyFourier::new(&module, rank);
218 sk2_dft.dft(&module, &sk2);
219
220 // GRLWE_{s1}(s0) = s0 -> s1
221 ct_grlwe_s0s1.encrypt_sk(
222 &module,
223 &sk0.data,
224 &sk1_dft,
225 &mut source_xa,
226 &mut source_xe,
227 sigma,
228 scratch.borrow(),
229 );
230
231 // GRLWE_{s2}(s1) -> s1 -> s2
232 ct_grlwe_s1s2.encrypt_sk(
233 &module,
234 &sk1.data,
235 &sk2_dft,
236 &mut source_xa,
237 &mut source_xe,
238 sigma,
239 scratch.borrow(),
240 );
241
242 // GRLWE_{s1}(s0) (x) GRLWE_{s2}(s1) = GRLWE_{s2}(s0)
243 ct_grlwe_s0s1.keyswitch_inplace(&module, &ct_grlwe_s1s2, scratch.borrow());
244
245 let ct_grlwe_s0s2: GLWESwitchingKey<Vec<u8>, FFT64> = ct_grlwe_s0s1;
246
247 let mut ct_rlwe_dft_s0s2: GLWECiphertextFourier<Vec<u8>, FFT64> =
248 GLWECiphertextFourier::new(&module, log_base2k, log_k_grlwe, rank);
249 let mut pt: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::new(&module, log_base2k, log_k_grlwe);
250
251 (0..ct_grlwe_s0s2.rows()).for_each(|row_i| {
252 ct_grlwe_s0s2.get_row(&module, row_i, 0, &mut ct_rlwe_dft_s0s2);
253 ct_rlwe_dft_s0s2.decrypt(&module, &mut pt, &sk2_dft, scratch.borrow());
254 module.vec_znx_sub_scalar_inplace(&mut pt, 0, row_i, &sk0, 0);
255
256 let noise_have: f64 = pt.data.std(0, log_base2k).log2();
257 let noise_want: f64 = noise_grlwe_rlwe_product(
258 module.n() as f64,
259 log_base2k,
260 0.5,
261 0.5,
262 0f64,
263 sigma * sigma,
264 0f64,
265 log_k_grlwe,
266 log_k_grlwe,
267 );
268
269 assert!(
270 (noise_have - noise_want).abs() <= 0.1,
271 "{} {}",
272 noise_have,
273 noise_want
274 );
275 });
276}
277
278#[test]
279fn external_product() {
280 let module: Module<FFT64> = Module::<FFT64>::new(2048);
281 let log_base2k: usize = 12;
282 let log_k_grlwe: usize = 60;
283 let rows: usize = (log_k_grlwe + log_base2k - 1) / log_base2k;
284
285 let rank: usize = 1;
286 let rank_out: usize = 1;
287
288 let sigma: f64 = 3.2;
289
290 let mut ct_grlwe_in: GLWESwitchingKey<Vec<u8>, FFT64> =
291 GLWESwitchingKey::new(&module, log_base2k, log_k_grlwe, rows, rank, rank_out);
292 let mut ct_grlwe_out: GLWESwitchingKey<Vec<u8>, FFT64> =
293 GLWESwitchingKey::new(&module, log_base2k, log_k_grlwe, rows, rank, rank_out);
294 let mut ct_rgsw: GGSWCiphertext<Vec<u8>, FFT64> = GGSWCiphertext::new(&module, log_base2k, log_k_grlwe, rows, rank);
295
296 let mut pt_rgsw: ScalarZnx<Vec<u8>> = module.new_scalar_znx(1);
297 let mut pt_grlwe: ScalarZnx<Vec<u8>> = module.new_scalar_znx(1);
298
299 let mut source_xs: Source = Source::new([0u8; 32]);
300 let mut source_xe: Source = Source::new([0u8; 32]);
301 let mut source_xa: Source = Source::new([0u8; 32]);
302
303 let mut scratch: ScratchOwned = ScratchOwned::new(
304 GLWESwitchingKey::encrypt_sk_scratch_space(&module, rank, ct_grlwe_in.size())
305 | GLWECiphertextFourier::decrypt_scratch_space(&module, ct_grlwe_out.size())
306 | GLWESwitchingKey::external_product_scratch_space(
307 &module,
308 ct_grlwe_out.size(),
309 ct_grlwe_in.size(),
310 ct_rgsw.size(),
311 )
312 | GGSWCiphertext::encrypt_sk_scratch_space(&module, rank, ct_rgsw.size()),
313 );
314
315 let k: usize = 1;
316
317 pt_rgsw.raw_mut()[k] = 1; // X^{k}
318
319 pt_grlwe.fill_ternary_prob(0, 0.5, &mut source_xs);
320
321 let mut sk: SecretKey<Vec<u8>> = SecretKey::new(&module, rank);
322 sk.fill_ternary_prob(0.5, &mut source_xs);
323
324 let mut sk_dft: SecretKeyFourier<Vec<u8>, FFT64> = SecretKeyFourier::new(&module, rank);
325 sk_dft.dft(&module, &sk);
326
327 // GRLWE_{s1}(s0) = s0 -> s1
328 ct_grlwe_in.encrypt_sk(
329 &module,
330 &pt_grlwe,
331 &sk_dft,
332 &mut source_xa,
333 &mut source_xe,
334 sigma,
335 scratch.borrow(),
336 );
337
338 ct_rgsw.encrypt_sk(
339 &module,
340 &pt_rgsw,
341 &sk_dft,
342 &mut source_xa,
343 &mut source_xe,
344 sigma,
345 scratch.borrow(),
346 );
347
348 // GRLWE_(m) (x) RGSW_(X^k) = GRLWE_(m * X^k)
349 ct_grlwe_out.external_product(&module, &ct_grlwe_in, &ct_rgsw, scratch.borrow());
350
351 let mut ct_rlwe_dft_s0s2: GLWECiphertextFourier<Vec<u8>, FFT64> =
352 GLWECiphertextFourier::new(&module, log_base2k, log_k_grlwe, rank);
353 let mut pt: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::new(&module, log_base2k, log_k_grlwe);
354
355 module.vec_znx_rotate_inplace(k as i64, &mut pt_grlwe, 0);
356
357 (0..ct_grlwe_out.rows()).for_each(|row_i| {
358 ct_grlwe_out.get_row(&module, row_i, 0, &mut ct_rlwe_dft_s0s2);
359 ct_rlwe_dft_s0s2.decrypt(&module, &mut pt, &sk_dft, scratch.borrow());
360 module.vec_znx_sub_scalar_inplace(&mut pt, 0, row_i, &pt_grlwe, 0);
361
362 let noise_have: f64 = pt.data.std(0, log_base2k).log2();
363
364 let var_gct_err_lhs: f64 = sigma * sigma;
365 let var_gct_err_rhs: f64 = 0f64;
366
367 let var_msg: f64 = 1f64 / module.n() as f64; // X^{k}
368 let var_a0_err: f64 = sigma * sigma;
369 let var_a1_err: f64 = 1f64 / 12f64;
370
371 let noise_want: f64 = noise_rgsw_product(
372 module.n() as f64,
373 log_base2k,
374 0.5,
375 var_msg,
376 var_a0_err,
377 var_a1_err,
378 var_gct_err_lhs,
379 var_gct_err_rhs,
380 log_k_grlwe,
381 log_k_grlwe,
382 );
383
384 assert!(
385 (noise_have - noise_want).abs() <= 0.1,
386 "{} {}",
387 noise_have,
388 noise_want
389 );
390 });
391}
392
393#[test]
394fn external_product_inplace() {
395 let module: Module<FFT64> = Module::<FFT64>::new(2048);
396 let log_base2k: usize = 12;
397 let log_k_grlwe: usize = 60;
398 let rows: usize = (log_k_grlwe + log_base2k - 1) / log_base2k;
399
400 let rank: usize = 1;
401 let rank_out: usize = 1;
402
403 let sigma: f64 = 3.2;
404
405 let mut ct_grlwe: GLWESwitchingKey<Vec<u8>, FFT64> =
406 GLWESwitchingKey::new(&module, log_base2k, log_k_grlwe, rows, rank, rank_out);
407 let mut ct_rgsw: GGSWCiphertext<Vec<u8>, FFT64> = GGSWCiphertext::new(&module, log_base2k, log_k_grlwe, rows, rank);
408
409 let mut pt_rgsw: ScalarZnx<Vec<u8>> = module.new_scalar_znx(1);
410 let mut pt_grlwe: ScalarZnx<Vec<u8>> = module.new_scalar_znx(1);
411
412 let mut source_xs: Source = Source::new([0u8; 32]);
413 let mut source_xe: Source = Source::new([0u8; 32]);
414 let mut source_xa: Source = Source::new([0u8; 32]);
415
416 let mut scratch: ScratchOwned = ScratchOwned::new(
417 GLWESwitchingKey::encrypt_sk_scratch_space(&module, rank, ct_grlwe.size())
418 | GLWECiphertextFourier::decrypt_scratch_space(&module, ct_grlwe.size())
419 | GLWESwitchingKey::external_product_inplace_scratch_space(&module, ct_grlwe.size(), ct_rgsw.size())
420 | GGSWCiphertext::encrypt_sk_scratch_space(&module, rank, ct_rgsw.size()),
421 );
422
423 let k: usize = 1;
424
425 pt_rgsw.raw_mut()[k] = 1; // X^{k}
426
427 pt_grlwe.fill_ternary_prob(0, 0.5, &mut source_xs);
428
429 let mut sk: SecretKey<Vec<u8>> = SecretKey::new(&module, rank);
430 sk.fill_ternary_prob(0.5, &mut source_xs);
431
432 let mut sk_dft: SecretKeyFourier<Vec<u8>, FFT64> = SecretKeyFourier::new(&module, rank);
433 sk_dft.dft(&module, &sk);
434
435 // GRLWE_{s1}(s0) = s0 -> s1
436 ct_grlwe.encrypt_sk(
437 &module,
438 &pt_grlwe,
439 &sk_dft,
440 &mut source_xa,
441 &mut source_xe,
442 sigma,
443 scratch.borrow(),
444 );
445
446 ct_rgsw.encrypt_sk(
447 &module,
448 &pt_rgsw,
449 &sk_dft,
450 &mut source_xa,
451 &mut source_xe,
452 sigma,
453 scratch.borrow(),
454 );
455
456 // GRLWE_(m) (x) RGSW_(X^k) = GRLWE_(m * X^k)
457 ct_grlwe.external_product_inplace(&module, &ct_rgsw, scratch.borrow());
458
459 let mut ct_rlwe_dft_s0s2: GLWECiphertextFourier<Vec<u8>, FFT64> =
460 GLWECiphertextFourier::new(&module, log_base2k, log_k_grlwe, rank);
461 let mut pt: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::new(&module, log_base2k, log_k_grlwe);
462
463 module.vec_znx_rotate_inplace(k as i64, &mut pt_grlwe, 0);
464
465 (0..ct_grlwe.rows()).for_each(|row_i| {
466 ct_grlwe.get_row(&module, row_i, 0, &mut ct_rlwe_dft_s0s2);
467 ct_rlwe_dft_s0s2.decrypt(&module, &mut pt, &sk_dft, scratch.borrow());
468 module.vec_znx_sub_scalar_inplace(&mut pt, 0, row_i, &pt_grlwe, 0);
469
470 let noise_have: f64 = pt.data.std(0, log_base2k).log2();
471
472 let var_gct_err_lhs: f64 = sigma * sigma;
473 let var_gct_err_rhs: f64 = 0f64;
474
475 let var_msg: f64 = 1f64 / module.n() as f64; // X^{k}
476 let var_a0_err: f64 = sigma * sigma;
477 let var_a1_err: f64 = 1f64 / 12f64;
478
479 let noise_want: f64 = noise_rgsw_product(
480 module.n() as f64,
481 log_base2k,
482 0.5,
483 var_msg,
484 var_a0_err,
485 var_a1_err,
486 var_gct_err_lhs,
487 var_gct_err_rhs,
488 log_k_grlwe,
489 log_k_grlwe,
490 );
491
492 assert!(
493 (noise_have - noise_want).abs() <= 0.1,
494 "{} {}",
495 noise_have,
496 noise_want
497 );
498 });
499}
500
501pub(crate) fn noise_grlwe_rlwe_product(
502 n: f64,
503 log_base2k: usize,
504 var_xs: f64,
505 var_msg: f64,
506 var_a_err: f64,
507 var_gct_err_lhs: f64,
508 var_gct_err_rhs: f64,
509 a_logq: usize,
510 b_logq: usize,
511) -> f64 {
512 let a_logq: usize = a_logq.min(b_logq);
513 let a_cols: usize = (a_logq + log_base2k - 1) / log_base2k;
514
515 let b_scale = 2.0f64.powi(b_logq as i32);
516 let a_scale: f64 = 2.0f64.powi((b_logq - a_logq) as i32);
517
518 let base: f64 = (1 << (log_base2k)) as f64;
519 let var_base: f64 = base * base / 12f64;
520
521 // lhs = a_cols * n * (var_base * var_gct_err_lhs + var_e_a * var_msg * p^2)
522 // rhs = a_cols * n * var_base * var_gct_err_rhs * var_xs
523 let mut noise: f64 = (a_cols as f64) * n * var_base * (var_gct_err_lhs + var_xs * var_gct_err_rhs);
524 noise += var_msg * var_a_err * a_scale * a_scale * n;
525 noise = noise.sqrt();
526 noise /= b_scale;
527 noise.log2().min(-1.0) // max noise is [-2^{-1}, 2^{-1}]
528}