poulpy/core/src/test_fft64/gglwe.rs
core/src/test_fft64/gglwe.rsRaw
1use backend::{FFT64, Module, ScalarZnx, ScalarZnxAlloc, ScalarZnxToMut, ScratchOwned, Stats, VecZnxOps, ZnxViewMut};
2use sampling::source::Source;
3
4use crate::{
5 GGSWCiphertext, GLWECiphertextFourier, GLWEPlaintext, GLWESecret, GLWESwitchingKey, GetRow, Infos,
6 test_fft64::{log2_std_noise_gglwe_product, noise_ggsw_product},
7};
8
9#[test]
10fn encrypt_sk() {
11 let log_n: usize = 8;
12 let basek: usize = 12;
13 let k_ksk: usize = 54;
14 let digits: usize = k_ksk / basek;
15 (1..4).for_each(|rank_in| {
16 (1..4).for_each(|rank_out| {
17 (1..digits + 1).for_each(|di| {
18 println!(
19 "test encrypt_sk digits: {} ranks: ({} {})",
20 di, rank_in, rank_out
21 );
22 test_encrypt_sk(log_n, basek, k_ksk, di, rank_in, rank_out, 3.2);
23 });
24 });
25 });
26}
27
28#[test]
29fn key_switch() {
30 let log_n: usize = 8;
31 let basek: usize = 12;
32 let k_in: usize = 60;
33 let digits: usize = k_in.div_ceil(basek);
34 (1..4).for_each(|rank_in_s0s1| {
35 (1..4).for_each(|rank_out_s0s1| {
36 (1..4).for_each(|rank_out_s1s2| {
37 (1..digits + 1).for_each(|di| {
38 let k_ksk: usize = k_in + basek * di;
39 println!(
40 "test key_switch digits: {} ranks: ({},{},{})",
41 di, rank_in_s0s1, rank_out_s0s1, rank_out_s1s2
42 );
43 let k_out: usize = k_ksk; // Better capture noise.
44 test_key_switch(
45 log_n,
46 basek,
47 k_out,
48 k_in,
49 k_ksk,
50 di,
51 rank_in_s0s1,
52 rank_out_s0s1,
53 rank_out_s1s2,
54 3.2,
55 );
56 })
57 })
58 });
59 });
60}
61
62#[test]
63fn key_switch_inplace() {
64 let log_n: usize = 8;
65 let basek: usize = 12;
66 let k_ct: usize = 60;
67 let digits: usize = k_ct.div_ceil(basek);
68 (1..4).for_each(|rank_in_s0s1| {
69 (1..4).for_each(|rank_out_s0s1| {
70 (1..digits + 1).for_each(|di| {
71 let k_ksk: usize = k_ct + basek * di;
72 println!(
73 "test key_switch_inplace digits: {} ranks: ({},{})",
74 di, rank_in_s0s1, rank_out_s0s1
75 );
76 test_key_switch_inplace(
77 log_n,
78 basek,
79 k_ct,
80 k_ksk,
81 di,
82 rank_in_s0s1,
83 rank_out_s0s1,
84 3.2,
85 );
86 });
87 });
88 });
89}
90
91#[test]
92fn external_product() {
93 let log_n: usize = 8;
94 let basek: usize = 12;
95 let k_in: usize = 60;
96 let digits: usize = k_in.div_ceil(basek);
97 (1..4).for_each(|rank_in| {
98 (1..4).for_each(|rank_out| {
99 (1..digits + 1).for_each(|di| {
100 let k_ggsw: usize = k_in + basek * di;
101 println!(
102 "test external_product digits: {} ranks: ({} {})",
103 di, rank_in, rank_out
104 );
105 let k_out: usize = k_in; // Better capture noise.
106 test_external_product(
107 log_n, basek, k_out, k_in, k_ggsw, di, rank_in, rank_out, 3.2,
108 );
109 });
110 });
111 });
112}
113
114#[test]
115fn external_product_inplace() {
116 let log_n: usize = 5;
117 let basek: usize = 12;
118 let k_ct: usize = 60;
119 let digits: usize = k_ct.div_ceil(basek);
120 (1..4).for_each(|rank_in| {
121 (1..4).for_each(|rank_out| {
122 (1..digits).for_each(|di| {
123 let k_ggsw: usize = k_ct + basek * di;
124 println!(
125 "test external_product_inplace digits: {} ranks: ({} {})",
126 di, rank_in, rank_out
127 );
128 test_external_product_inplace(log_n, basek, k_ct, k_ggsw, di, rank_in, rank_out, 3.2);
129 });
130 });
131 });
132}
133
134fn test_encrypt_sk(log_n: usize, basek: usize, k_ksk: usize, digits: usize, rank_in: usize, rank_out: usize, sigma: f64) {
135 let module: Module<FFT64> = Module::<FFT64>::new(1 << log_n);
136 let rows: usize = (k_ksk - digits * basek) / (digits * basek);
137
138 let mut ksk: GLWESwitchingKey<Vec<u8>, FFT64> =
139 GLWESwitchingKey::alloc(&module, basek, k_ksk, rows, digits, rank_in, rank_out);
140 let mut pt: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::alloc(&module, basek, k_ksk);
141
142 let mut source_xs: Source = Source::new([0u8; 32]);
143 let mut source_xe: Source = Source::new([0u8; 32]);
144 let mut source_xa: Source = Source::new([0u8; 32]);
145
146 let mut scratch: ScratchOwned = ScratchOwned::new(
147 GLWESwitchingKey::encrypt_sk_scratch_space(&module, basek, k_ksk, rank_out)
148 | GLWECiphertextFourier::decrypt_scratch_space(&module, basek, k_ksk),
149 );
150
151 let mut sk_in: GLWESecret<Vec<u8>, FFT64> = GLWESecret::alloc(&module, rank_in);
152 sk_in.fill_ternary_prob(&module, 0.5, &mut source_xs);
153
154 let mut sk_out: GLWESecret<Vec<u8>, FFT64> = GLWESecret::alloc(&module, rank_out);
155 sk_out.fill_ternary_prob(&module, 0.5, &mut source_xs);
156
157 ksk.generate_from_sk(
158 &module,
159 &sk_in,
160 &sk_out,
161 &mut source_xa,
162 &mut source_xe,
163 sigma,
164 scratch.borrow(),
165 );
166
167 let mut ct_glwe_fourier: GLWECiphertextFourier<Vec<u8>, FFT64> =
168 GLWECiphertextFourier::alloc(&module, basek, k_ksk, rank_out);
169
170 (0..ksk.rank_in()).for_each(|col_i| {
171 (0..ksk.rows()).for_each(|row_i| {
172 ksk.get_row(&module, row_i, col_i, &mut ct_glwe_fourier);
173 ct_glwe_fourier.decrypt(&module, &mut pt, &sk_out, scratch.borrow());
174 module.vec_znx_sub_scalar_inplace(
175 &mut pt.data,
176 0,
177 (digits - 1) + row_i * digits,
178 &sk_in.data,
179 col_i,
180 );
181 let std_pt: f64 = pt.data.std(0, basek) * (k_ksk as f64).exp2();
182 assert!((sigma - std_pt).abs() <= 0.5, "{} {}", sigma, std_pt);
183 });
184 });
185}
186
187fn test_key_switch(
188 log_n: usize,
189 basek: usize,
190 k_out: usize,
191 k_in: usize,
192 k_ksk: usize,
193 digits: usize,
194 rank_in_s0s1: usize,
195 rank_out_s0s1: usize,
196 rank_out_s1s2: usize,
197 sigma: f64,
198) {
199 let module: Module<FFT64> = Module::<FFT64>::new(1 << log_n);
200 let rows: usize = k_in.div_ceil(basek * digits);
201 let digits_in: usize = 1;
202
203 let mut ct_gglwe_s0s1: GLWESwitchingKey<Vec<u8>, FFT64> = GLWESwitchingKey::alloc(
204 &module,
205 basek,
206 k_in,
207 rows,
208 digits_in,
209 rank_in_s0s1,
210 rank_out_s0s1,
211 );
212 let mut ct_gglwe_s1s2: GLWESwitchingKey<Vec<u8>, FFT64> = GLWESwitchingKey::alloc(
213 &module,
214 basek,
215 k_ksk,
216 rows,
217 digits,
218 rank_out_s0s1,
219 rank_out_s1s2,
220 );
221 let mut ct_gglwe_s0s2: GLWESwitchingKey<Vec<u8>, FFT64> = GLWESwitchingKey::alloc(
222 &module,
223 basek,
224 k_out,
225 rows,
226 digits_in,
227 rank_in_s0s1,
228 rank_out_s1s2,
229 );
230
231 let mut source_xs: Source = Source::new([0u8; 32]);
232 let mut source_xe: Source = Source::new([0u8; 32]);
233 let mut source_xa: Source = Source::new([0u8; 32]);
234
235 let mut scratch: ScratchOwned = ScratchOwned::new(
236 GLWESwitchingKey::encrypt_sk_scratch_space(&module, basek, k_ksk, rank_in_s0s1 | rank_out_s0s1)
237 | GLWECiphertextFourier::decrypt_scratch_space(&module, basek, k_out)
238 | GLWESwitchingKey::keyswitch_scratch_space(
239 &module,
240 basek,
241 k_out,
242 k_in,
243 k_ksk,
244 digits,
245 ct_gglwe_s1s2.rank_in(),
246 ct_gglwe_s1s2.rank_out(),
247 ),
248 );
249
250 let mut sk0: GLWESecret<Vec<u8>, FFT64> = GLWESecret::alloc(&module, rank_in_s0s1);
251 sk0.fill_ternary_prob(&module, 0.5, &mut source_xs);
252
253 let mut sk1: GLWESecret<Vec<u8>, FFT64> = GLWESecret::alloc(&module, rank_out_s0s1);
254 sk1.fill_ternary_prob(&module, 0.5, &mut source_xs);
255
256 let mut sk2: GLWESecret<Vec<u8>, FFT64> = GLWESecret::alloc(&module, rank_out_s1s2);
257 sk2.fill_ternary_prob(&module, 0.5, &mut source_xs);
258
259 // gglwe_{s1}(s0) = s0 -> s1
260 ct_gglwe_s0s1.generate_from_sk(
261 &module,
262 &sk0,
263 &sk1,
264 &mut source_xa,
265 &mut source_xe,
266 sigma,
267 scratch.borrow(),
268 );
269
270 // gglwe_{s2}(s1) -> s1 -> s2
271 ct_gglwe_s1s2.generate_from_sk(
272 &module,
273 &sk1,
274 &sk2,
275 &mut source_xa,
276 &mut source_xe,
277 sigma,
278 scratch.borrow(),
279 );
280
281 // gglwe_{s1}(s0) (x) gglwe_{s2}(s1) = gglwe_{s2}(s0)
282 ct_gglwe_s0s2.keyswitch(&module, &ct_gglwe_s0s1, &ct_gglwe_s1s2, scratch.borrow());
283
284 let mut ct_glwe_dft: GLWECiphertextFourier<Vec<u8>, FFT64> =
285 GLWECiphertextFourier::alloc(&module, basek, k_out, rank_out_s1s2);
286 let mut pt: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::alloc(&module, basek, k_out);
287
288 (0..ct_gglwe_s0s2.rank_in()).for_each(|col_i| {
289 (0..ct_gglwe_s0s2.rows()).for_each(|row_i| {
290 ct_gglwe_s0s2.get_row(&module, row_i, col_i, &mut ct_glwe_dft);
291 ct_glwe_dft.decrypt(&module, &mut pt, &sk2, scratch.borrow());
292 module.vec_znx_sub_scalar_inplace(
293 &mut pt.data,
294 0,
295 (digits_in - 1) + row_i * digits_in,
296 &sk0.data,
297 col_i,
298 );
299
300 let noise_have: f64 = pt.data.std(0, basek).log2();
301 let noise_want: f64 = log2_std_noise_gglwe_product(
302 module.n() as f64,
303 basek * digits,
304 0.5,
305 0.5,
306 0f64,
307 sigma * sigma,
308 0f64,
309 rank_out_s0s1 as f64,
310 k_in,
311 k_ksk,
312 );
313
314 assert!(
315 (noise_have - noise_want).abs() <= 1.0,
316 "{} {}",
317 noise_have,
318 noise_want
319 );
320 });
321 });
322}
323
324fn test_key_switch_inplace(
325 log_n: usize,
326 basek: usize,
327 k_ct: usize,
328 k_ksk: usize,
329 digits: usize,
330 rank_in: usize,
331 rank_out: usize,
332 sigma: f64,
333) {
334 let module: Module<FFT64> = Module::<FFT64>::new(1 << log_n);
335 let rows: usize = k_ct.div_ceil(basek * digits);
336 let digits_in: usize = 1;
337
338 let mut ct_gglwe_s0s1: GLWESwitchingKey<Vec<u8>, FFT64> =
339 GLWESwitchingKey::alloc(&module, basek, k_ct, rows, digits_in, rank_in, rank_out);
340 let mut ct_gglwe_s1s2: GLWESwitchingKey<Vec<u8>, FFT64> =
341 GLWESwitchingKey::alloc(&module, basek, k_ksk, rows, digits, rank_out, rank_out);
342
343 let mut source_xs: Source = Source::new([0u8; 32]);
344 let mut source_xe: Source = Source::new([0u8; 32]);
345 let mut source_xa: Source = Source::new([0u8; 32]);
346
347 let mut scratch: ScratchOwned = ScratchOwned::new(
348 GLWESwitchingKey::encrypt_sk_scratch_space(&module, basek, k_ksk, rank_out)
349 | GLWECiphertextFourier::decrypt_scratch_space(&module, basek, k_ksk)
350 | GLWESwitchingKey::keyswitch_inplace_scratch_space(&module, basek, k_ct, k_ksk, digits, rank_out),
351 );
352
353 let mut sk0: GLWESecret<Vec<u8>, FFT64> = GLWESecret::alloc(&module, rank_in);
354 sk0.fill_ternary_prob(&module, 0.5, &mut source_xs);
355
356 let mut sk1: GLWESecret<Vec<u8>, FFT64> = GLWESecret::alloc(&module, rank_out);
357 sk1.fill_ternary_prob(&module, 0.5, &mut source_xs);
358
359 let mut sk2: GLWESecret<Vec<u8>, FFT64> = GLWESecret::alloc(&module, rank_out);
360 sk2.fill_ternary_prob(&module, 0.5, &mut source_xs);
361
362 // gglwe_{s1}(s0) = s0 -> s1
363 ct_gglwe_s0s1.generate_from_sk(
364 &module,
365 &sk0,
366 &sk1,
367 &mut source_xa,
368 &mut source_xe,
369 sigma,
370 scratch.borrow(),
371 );
372
373 // gglwe_{s2}(s1) -> s1 -> s2
374 ct_gglwe_s1s2.generate_from_sk(
375 &module,
376 &sk1,
377 &sk2,
378 &mut source_xa,
379 &mut source_xe,
380 sigma,
381 scratch.borrow(),
382 );
383
384 // gglwe_{s1}(s0) (x) gglwe_{s2}(s1) = gglwe_{s2}(s0)
385 ct_gglwe_s0s1.keyswitch_inplace(&module, &ct_gglwe_s1s2, scratch.borrow());
386
387 let ct_gglwe_s0s2: GLWESwitchingKey<Vec<u8>, FFT64> = ct_gglwe_s0s1;
388
389 let mut ct_glwe_dft: GLWECiphertextFourier<Vec<u8>, FFT64> = GLWECiphertextFourier::alloc(&module, basek, k_ct, rank_out);
390 let mut pt: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::alloc(&module, basek, k_ct);
391
392 (0..ct_gglwe_s0s2.rank_in()).for_each(|col_i| {
393 (0..ct_gglwe_s0s2.rows()).for_each(|row_i| {
394 ct_gglwe_s0s2.get_row(&module, row_i, col_i, &mut ct_glwe_dft);
395 ct_glwe_dft.decrypt(&module, &mut pt, &sk2, scratch.borrow());
396 module.vec_znx_sub_scalar_inplace(
397 &mut pt.data,
398 0,
399 (digits_in - 1) + row_i * digits_in,
400 &sk0.data,
401 col_i,
402 );
403
404 let noise_have: f64 = pt.data.std(0, basek).log2();
405 let noise_want: f64 = log2_std_noise_gglwe_product(
406 module.n() as f64,
407 basek * digits,
408 0.5,
409 0.5,
410 0f64,
411 sigma * sigma,
412 0f64,
413 rank_out as f64,
414 k_ct,
415 k_ksk,
416 );
417
418 assert!(
419 (noise_have - noise_want).abs() <= 1.0,
420 "{} {}",
421 noise_have,
422 noise_want
423 );
424 });
425 });
426}
427
428fn test_external_product(
429 log_n: usize,
430 basek: usize,
431 k_out: usize,
432 k_in: usize,
433 k_ggsw: usize,
434 digits: usize,
435 rank_in: usize,
436 rank_out: usize,
437 sigma: f64,
438) {
439 let module: Module<FFT64> = Module::<FFT64>::new(1 << log_n);
440
441 let rows: usize = k_in.div_ceil(basek * digits);
442 let digits_in: usize = 1;
443
444 let mut ct_gglwe_in: GLWESwitchingKey<Vec<u8>, FFT64> =
445 GLWESwitchingKey::alloc(&module, basek, k_in, rows, digits_in, rank_in, rank_out);
446 let mut ct_gglwe_out: GLWESwitchingKey<Vec<u8>, FFT64> =
447 GLWESwitchingKey::alloc(&module, basek, k_out, rows, digits_in, rank_in, rank_out);
448 let mut ct_rgsw: GGSWCiphertext<Vec<u8>, FFT64> = GGSWCiphertext::alloc(&module, basek, k_ggsw, rows, digits, rank_out);
449
450 let mut pt_rgsw: ScalarZnx<Vec<u8>> = module.new_scalar_znx(1);
451
452 let mut source_xs: Source = Source::new([0u8; 32]);
453 let mut source_xe: Source = Source::new([0u8; 32]);
454 let mut source_xa: Source = Source::new([0u8; 32]);
455
456 let mut scratch: ScratchOwned = ScratchOwned::new(
457 GLWESwitchingKey::encrypt_sk_scratch_space(&module, basek, k_in, rank_out)
458 | GLWECiphertextFourier::decrypt_scratch_space(&module, basek, k_out)
459 | GLWESwitchingKey::external_product_scratch_space(&module, basek, k_out, k_in, k_ggsw, digits, rank_out)
460 | GGSWCiphertext::encrypt_sk_scratch_space(&module, basek, k_ggsw, rank_out),
461 );
462
463 let r: usize = 1;
464
465 pt_rgsw.to_mut().raw_mut()[r] = 1; // X^{r}
466
467 let mut sk_in: GLWESecret<Vec<u8>, FFT64> = GLWESecret::alloc(&module, rank_in);
468 sk_in.fill_ternary_prob(&module, 0.5, &mut source_xs);
469
470 let mut sk_out: GLWESecret<Vec<u8>, FFT64> = GLWESecret::alloc(&module, rank_out);
471 sk_out.fill_ternary_prob(&module, 0.5, &mut source_xs);
472
473 // gglwe_{s1}(s0) = s0 -> s1
474 ct_gglwe_in.generate_from_sk(
475 &module,
476 &sk_in,
477 &sk_out,
478 &mut source_xa,
479 &mut source_xe,
480 sigma,
481 scratch.borrow(),
482 );
483
484 ct_rgsw.encrypt_sk(
485 &module,
486 &pt_rgsw,
487 &sk_out,
488 &mut source_xa,
489 &mut source_xe,
490 sigma,
491 scratch.borrow(),
492 );
493
494 // gglwe_(m) (x) RGSW_(X^k) = gglwe_(m * X^k)
495 ct_gglwe_out.external_product(&module, &ct_gglwe_in, &ct_rgsw, scratch.borrow());
496
497 let mut ct_glwe_dft: GLWECiphertextFourier<Vec<u8>, FFT64> = GLWECiphertextFourier::alloc(&module, basek, k_out, rank_out);
498 let mut pt: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::alloc(&module, basek, k_out);
499
500 (0..rank_in).for_each(|i| {
501 module.vec_znx_rotate_inplace(r as i64, &mut sk_in.data, i); // * X^{r}
502 });
503
504 (0..rank_in).for_each(|col_i| {
505 (0..ct_gglwe_out.rows()).for_each(|row_i| {
506 ct_gglwe_out.get_row(&module, row_i, col_i, &mut ct_glwe_dft);
507 ct_glwe_dft.decrypt(&module, &mut pt, &sk_out, scratch.borrow());
508
509 module.vec_znx_sub_scalar_inplace(
510 &mut pt.data,
511 0,
512 (digits_in - 1) + row_i * digits_in,
513 &sk_in.data,
514 col_i,
515 );
516
517 let noise_have: f64 = pt.data.std(0, basek).log2();
518
519 let var_gct_err_lhs: f64 = sigma * sigma;
520 let var_gct_err_rhs: f64 = 0f64;
521
522 let var_msg: f64 = 1f64 / module.n() as f64; // X^{k}
523 let var_a0_err: f64 = sigma * sigma;
524 let var_a1_err: f64 = 1f64 / 12f64;
525
526 let noise_want: f64 = noise_ggsw_product(
527 module.n() as f64,
528 basek * digits,
529 0.5,
530 var_msg,
531 var_a0_err,
532 var_a1_err,
533 var_gct_err_lhs,
534 var_gct_err_rhs,
535 rank_out as f64,
536 k_in,
537 k_ggsw,
538 );
539
540 assert!(
541 (noise_have - noise_want).abs() <= 1.0,
542 "{} {}",
543 noise_have,
544 noise_want
545 );
546 });
547 });
548}
549
550fn test_external_product_inplace(
551 log_n: usize,
552 basek: usize,
553 k_ct: usize,
554 k_ggsw: usize,
555 digits: usize,
556 rank_in: usize,
557 rank_out: usize,
558 sigma: f64,
559) {
560 let module: Module<FFT64> = Module::<FFT64>::new(1 << log_n);
561
562 let rows: usize = k_ct.div_ceil(basek * digits);
563
564 let digits_in: usize = 1;
565
566 let mut ct_gglwe: GLWESwitchingKey<Vec<u8>, FFT64> =
567 GLWESwitchingKey::alloc(&module, basek, k_ct, rows, digits_in, rank_in, rank_out);
568 let mut ct_rgsw: GGSWCiphertext<Vec<u8>, FFT64> = GGSWCiphertext::alloc(&module, basek, k_ggsw, rows, digits, rank_out);
569
570 let mut pt_rgsw: ScalarZnx<Vec<u8>> = module.new_scalar_znx(1);
571
572 let mut source_xs: Source = Source::new([0u8; 32]);
573 let mut source_xe: Source = Source::new([0u8; 32]);
574 let mut source_xa: Source = Source::new([0u8; 32]);
575
576 let mut scratch: ScratchOwned = ScratchOwned::new(
577 GLWESwitchingKey::encrypt_sk_scratch_space(&module, basek, k_ct, rank_out)
578 | GLWECiphertextFourier::decrypt_scratch_space(&module, basek, k_ct)
579 | GLWESwitchingKey::external_product_inplace_scratch_space(&module, basek, k_ct, k_ggsw, digits, rank_out)
580 | GGSWCiphertext::encrypt_sk_scratch_space(&module, basek, k_ggsw, rank_out),
581 );
582
583 let r: usize = 1;
584
585 pt_rgsw.to_mut().raw_mut()[r] = 1; // X^{r}
586
587 let mut sk_in: GLWESecret<Vec<u8>, FFT64> = GLWESecret::alloc(&module, rank_in);
588 sk_in.fill_ternary_prob(&module, 0.5, &mut source_xs);
589
590 let mut sk_out: GLWESecret<Vec<u8>, FFT64> = GLWESecret::alloc(&module, rank_out);
591 sk_out.fill_ternary_prob(&module, 0.5, &mut source_xs);
592
593 // gglwe_{s1}(s0) = s0 -> s1
594 ct_gglwe.generate_from_sk(
595 &module,
596 &sk_in,
597 &sk_out,
598 &mut source_xa,
599 &mut source_xe,
600 sigma,
601 scratch.borrow(),
602 );
603
604 ct_rgsw.encrypt_sk(
605 &module,
606 &pt_rgsw,
607 &sk_out,
608 &mut source_xa,
609 &mut source_xe,
610 sigma,
611 scratch.borrow(),
612 );
613
614 // gglwe_(m) (x) RGSW_(X^k) = gglwe_(m * X^k)
615 ct_gglwe.external_product_inplace(&module, &ct_rgsw, scratch.borrow());
616
617 let mut ct_glwe_dft: GLWECiphertextFourier<Vec<u8>, FFT64> = GLWECiphertextFourier::alloc(&module, basek, k_ct, rank_out);
618 let mut pt: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::alloc(&module, basek, k_ct);
619
620 (0..rank_in).for_each(|i| {
621 module.vec_znx_rotate_inplace(r as i64, &mut sk_in.data, i); // * X^{r}
622 });
623
624 (0..rank_in).for_each(|col_i| {
625 (0..ct_gglwe.rows()).for_each(|row_i| {
626 ct_gglwe.get_row(&module, row_i, col_i, &mut ct_glwe_dft);
627 ct_glwe_dft.decrypt(&module, &mut pt, &sk_out, scratch.borrow());
628
629 module.vec_znx_sub_scalar_inplace(
630 &mut pt.data,
631 0,
632 (digits_in - 1) + row_i * digits_in,
633 &sk_in.data,
634 col_i,
635 );
636
637 let noise_have: f64 = pt.data.std(0, basek).log2();
638
639 let var_gct_err_lhs: f64 = sigma * sigma;
640 let var_gct_err_rhs: f64 = 0f64;
641
642 let var_msg: f64 = 1f64 / module.n() as f64; // X^{k}
643 let var_a0_err: f64 = sigma * sigma;
644 let var_a1_err: f64 = 1f64 / 12f64;
645
646 let noise_want: f64 = noise_ggsw_product(
647 module.n() as f64,
648 basek * digits,
649 0.5,
650 var_msg,
651 var_a0_err,
652 var_a1_err,
653 var_gct_err_lhs,
654 var_gct_err_rhs,
655 rank_out as f64,
656 k_ct,
657 k_ggsw,
658 );
659
660 assert!(
661 (noise_have - noise_want).abs() <= 1.0,
662 "{} {}",
663 noise_have,
664 noise_want
665 );
666 });
667 });
668}