poulpy/core/src/gglwe/test_fft64/gglwe.rs
core/src/gglwe/test_fft64/gglwe.rsRaw
1use backend::{FFT64, Module, ScalarZnx, ScalarZnxAlloc, ScalarZnxToMut, ScratchOwned, Stats, VecZnxOps, ZnxViewMut};
2use sampling::source::Source;
3
4use crate::{
5 FourierGLWECiphertext, FourierGLWESecret, GGSWCiphertext, GLWEPlaintext, GLWESecret, GLWESwitchingKey, GetRow, Infos,
6 noise::{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_in, rank_out)
148 | FourierGLWECiphertext::decrypt_scratch_space(&module, basek, k_ksk),
149 );
150
151 let mut sk_in: GLWESecret<Vec<u8>> = GLWESecret::alloc(&module, rank_in);
152 sk_in.fill_ternary_prob(0.5, &mut source_xs);
153
154 let mut sk_out: GLWESecret<Vec<u8>> = GLWESecret::alloc(&module, rank_out);
155 sk_out.fill_ternary_prob(0.5, &mut source_xs);
156 let sk_out_dft: FourierGLWESecret<Vec<u8>, FFT64> = FourierGLWESecret::from(&module, &sk_out);
157
158 ksk.encrypt_sk(
159 &module,
160 &sk_in,
161 &sk_out_dft,
162 &mut source_xa,
163 &mut source_xe,
164 sigma,
165 scratch.borrow(),
166 );
167
168 let mut ct_glwe_fourier: FourierGLWECiphertext<Vec<u8>, FFT64> =
169 FourierGLWECiphertext::alloc(&module, basek, k_ksk, rank_out);
170
171 (0..ksk.rank_in()).for_each(|col_i| {
172 (0..ksk.rows()).for_each(|row_i| {
173 ksk.get_row(&module, row_i, col_i, &mut ct_glwe_fourier);
174 ct_glwe_fourier.decrypt(&module, &mut pt, &sk_out_dft, scratch.borrow());
175 module.vec_znx_sub_scalar_inplace(
176 &mut pt.data,
177 0,
178 (digits - 1) + row_i * digits,
179 &sk_in.data,
180 col_i,
181 );
182 let std_pt: f64 = pt.data.std(0, basek) * (k_ksk as f64).exp2();
183 assert!((sigma - std_pt).abs() <= 0.5, "{} {}", sigma, std_pt);
184 });
185 });
186}
187
188fn test_key_switch(
189 log_n: usize,
190 basek: usize,
191 k_out: usize,
192 k_in: usize,
193 k_ksk: usize,
194 digits: usize,
195 rank_in_s0s1: usize,
196 rank_out_s0s1: usize,
197 rank_out_s1s2: usize,
198 sigma: f64,
199) {
200 let module: Module<FFT64> = Module::<FFT64>::new(1 << log_n);
201 let rows: usize = k_in.div_ceil(basek * digits);
202 let digits_in: usize = 1;
203
204 let mut ct_gglwe_s0s1: GLWESwitchingKey<Vec<u8>, FFT64> = GLWESwitchingKey::alloc(
205 &module,
206 basek,
207 k_in,
208 rows,
209 digits_in,
210 rank_in_s0s1,
211 rank_out_s0s1,
212 );
213 let mut ct_gglwe_s1s2: GLWESwitchingKey<Vec<u8>, FFT64> = GLWESwitchingKey::alloc(
214 &module,
215 basek,
216 k_ksk,
217 rows,
218 digits,
219 rank_out_s0s1,
220 rank_out_s1s2,
221 );
222 let mut ct_gglwe_s0s2: GLWESwitchingKey<Vec<u8>, FFT64> = GLWESwitchingKey::alloc(
223 &module,
224 basek,
225 k_out,
226 rows,
227 digits_in,
228 rank_in_s0s1,
229 rank_out_s1s2,
230 );
231
232 let mut source_xs: Source = Source::new([0u8; 32]);
233 let mut source_xe: Source = Source::new([0u8; 32]);
234 let mut source_xa: Source = Source::new([0u8; 32]);
235
236 let mut scratch: ScratchOwned = ScratchOwned::new(
237 GLWESwitchingKey::encrypt_sk_scratch_space(
238 &module,
239 basek,
240 k_ksk,
241 rank_in_s0s1,
242 rank_in_s0s1 | rank_out_s0s1,
243 ) | FourierGLWECiphertext::decrypt_scratch_space(&module, basek, k_out)
244 | GLWESwitchingKey::keyswitch_scratch_space(
245 &module,
246 basek,
247 k_out,
248 k_in,
249 k_ksk,
250 digits,
251 ct_gglwe_s1s2.rank_in(),
252 ct_gglwe_s1s2.rank_out(),
253 ),
254 );
255
256 let mut sk0: GLWESecret<Vec<u8>> = GLWESecret::alloc(&module, rank_in_s0s1);
257 sk0.fill_ternary_prob(0.5, &mut source_xs);
258
259 let mut sk1: GLWESecret<Vec<u8>> = GLWESecret::alloc(&module, rank_out_s0s1);
260 sk1.fill_ternary_prob(0.5, &mut source_xs);
261 let sk1_dft: FourierGLWESecret<Vec<u8>, FFT64> = FourierGLWESecret::from(&module, &sk1);
262
263 let mut sk2: GLWESecret<Vec<u8>> = GLWESecret::alloc(&module, rank_out_s1s2);
264 sk2.fill_ternary_prob(0.5, &mut source_xs);
265 let sk2_dft: FourierGLWESecret<Vec<u8>, FFT64> = FourierGLWESecret::from(&module, &sk2);
266
267 // gglwe_{s1}(s0) = s0 -> s1
268 ct_gglwe_s0s1.encrypt_sk(
269 &module,
270 &sk0,
271 &sk1_dft,
272 &mut source_xa,
273 &mut source_xe,
274 sigma,
275 scratch.borrow(),
276 );
277
278 // gglwe_{s2}(s1) -> s1 -> s2
279 ct_gglwe_s1s2.encrypt_sk(
280 &module,
281 &sk1,
282 &sk2_dft,
283 &mut source_xa,
284 &mut source_xe,
285 sigma,
286 scratch.borrow(),
287 );
288
289 // gglwe_{s1}(s0) (x) gglwe_{s2}(s1) = gglwe_{s2}(s0)
290 ct_gglwe_s0s2.keyswitch(&module, &ct_gglwe_s0s1, &ct_gglwe_s1s2, scratch.borrow());
291
292 let mut ct_glwe_dft: FourierGLWECiphertext<Vec<u8>, FFT64> =
293 FourierGLWECiphertext::alloc(&module, basek, k_out, rank_out_s1s2);
294 let mut pt: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::alloc(&module, basek, k_out);
295
296 (0..ct_gglwe_s0s2.rank_in()).for_each(|col_i| {
297 (0..ct_gglwe_s0s2.rows()).for_each(|row_i| {
298 ct_gglwe_s0s2.get_row(&module, row_i, col_i, &mut ct_glwe_dft);
299 ct_glwe_dft.decrypt(&module, &mut pt, &sk2_dft, scratch.borrow());
300 module.vec_znx_sub_scalar_inplace(
301 &mut pt.data,
302 0,
303 (digits_in - 1) + row_i * digits_in,
304 &sk0.data,
305 col_i,
306 );
307
308 let noise_have: f64 = pt.data.std(0, basek).log2();
309 let noise_want: f64 = log2_std_noise_gglwe_product(
310 module.n() as f64,
311 basek * digits,
312 0.5,
313 0.5,
314 0f64,
315 sigma * sigma,
316 0f64,
317 rank_out_s0s1 as f64,
318 k_in,
319 k_ksk,
320 );
321
322 assert!(
323 (noise_have - noise_want).abs() <= 1.0,
324 "{} {}",
325 noise_have,
326 noise_want
327 );
328 });
329 });
330}
331
332fn test_key_switch_inplace(
333 log_n: usize,
334 basek: usize,
335 k_ct: usize,
336 k_ksk: usize,
337 digits: usize,
338 rank_in: usize,
339 rank_out: usize,
340 sigma: f64,
341) {
342 let module: Module<FFT64> = Module::<FFT64>::new(1 << log_n);
343 let rows: usize = k_ct.div_ceil(basek * digits);
344 let digits_in: usize = 1;
345
346 let mut ct_gglwe_s0s1: GLWESwitchingKey<Vec<u8>, FFT64> =
347 GLWESwitchingKey::alloc(&module, basek, k_ct, rows, digits_in, rank_in, rank_out);
348 let mut ct_gglwe_s1s2: GLWESwitchingKey<Vec<u8>, FFT64> =
349 GLWESwitchingKey::alloc(&module, basek, k_ksk, rows, digits, rank_out, rank_out);
350
351 let mut source_xs: Source = Source::new([0u8; 32]);
352 let mut source_xe: Source = Source::new([0u8; 32]);
353 let mut source_xa: Source = Source::new([0u8; 32]);
354
355 let mut scratch: ScratchOwned = ScratchOwned::new(
356 GLWESwitchingKey::encrypt_sk_scratch_space(&module, basek, k_ksk, rank_in, rank_out)
357 | FourierGLWECiphertext::decrypt_scratch_space(&module, basek, k_ksk)
358 | GLWESwitchingKey::keyswitch_inplace_scratch_space(&module, basek, k_ct, k_ksk, digits, rank_out),
359 );
360
361 let mut sk0: GLWESecret<Vec<u8>> = GLWESecret::alloc(&module, rank_in);
362 sk0.fill_ternary_prob(0.5, &mut source_xs);
363
364 let mut sk1: GLWESecret<Vec<u8>> = GLWESecret::alloc(&module, rank_out);
365 sk1.fill_ternary_prob(0.5, &mut source_xs);
366 let sk1_dft: FourierGLWESecret<Vec<u8>, FFT64> = FourierGLWESecret::from(&module, &sk1);
367
368 let mut sk2: GLWESecret<Vec<u8>> = GLWESecret::alloc(&module, rank_out);
369 sk2.fill_ternary_prob(0.5, &mut source_xs);
370 let sk2_dft: FourierGLWESecret<Vec<u8>, FFT64> = FourierGLWESecret::from(&module, &sk2);
371
372 // gglwe_{s1}(s0) = s0 -> s1
373 ct_gglwe_s0s1.encrypt_sk(
374 &module,
375 &sk0,
376 &sk1_dft,
377 &mut source_xa,
378 &mut source_xe,
379 sigma,
380 scratch.borrow(),
381 );
382
383 // gglwe_{s2}(s1) -> s1 -> s2
384 ct_gglwe_s1s2.encrypt_sk(
385 &module,
386 &sk1,
387 &sk2_dft,
388 &mut source_xa,
389 &mut source_xe,
390 sigma,
391 scratch.borrow(),
392 );
393
394 // gglwe_{s1}(s0) (x) gglwe_{s2}(s1) = gglwe_{s2}(s0)
395 ct_gglwe_s0s1.keyswitch_inplace(&module, &ct_gglwe_s1s2, scratch.borrow());
396
397 let ct_gglwe_s0s2: GLWESwitchingKey<Vec<u8>, FFT64> = ct_gglwe_s0s1;
398
399 let mut ct_glwe_dft: FourierGLWECiphertext<Vec<u8>, FFT64> = FourierGLWECiphertext::alloc(&module, basek, k_ct, rank_out);
400 let mut pt: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::alloc(&module, basek, k_ct);
401
402 (0..ct_gglwe_s0s2.rank_in()).for_each(|col_i| {
403 (0..ct_gglwe_s0s2.rows()).for_each(|row_i| {
404 ct_gglwe_s0s2.get_row(&module, row_i, col_i, &mut ct_glwe_dft);
405 ct_glwe_dft.decrypt(&module, &mut pt, &sk2_dft, scratch.borrow());
406 module.vec_znx_sub_scalar_inplace(
407 &mut pt.data,
408 0,
409 (digits_in - 1) + row_i * digits_in,
410 &sk0.data,
411 col_i,
412 );
413
414 let noise_have: f64 = pt.data.std(0, basek).log2();
415 let noise_want: f64 = log2_std_noise_gglwe_product(
416 module.n() as f64,
417 basek * digits,
418 0.5,
419 0.5,
420 0f64,
421 sigma * sigma,
422 0f64,
423 rank_out as f64,
424 k_ct,
425 k_ksk,
426 );
427
428 assert!(
429 (noise_have - noise_want).abs() <= 1.0,
430 "{} {}",
431 noise_have,
432 noise_want
433 );
434 });
435 });
436}
437
438fn test_external_product(
439 log_n: usize,
440 basek: usize,
441 k_out: usize,
442 k_in: usize,
443 k_ggsw: usize,
444 digits: usize,
445 rank_in: usize,
446 rank_out: usize,
447 sigma: f64,
448) {
449 let module: Module<FFT64> = Module::<FFT64>::new(1 << log_n);
450
451 let rows: usize = k_in.div_ceil(basek * digits);
452 let digits_in: usize = 1;
453
454 let mut ct_gglwe_in: GLWESwitchingKey<Vec<u8>, FFT64> =
455 GLWESwitchingKey::alloc(&module, basek, k_in, rows, digits_in, rank_in, rank_out);
456 let mut ct_gglwe_out: GLWESwitchingKey<Vec<u8>, FFT64> =
457 GLWESwitchingKey::alloc(&module, basek, k_out, rows, digits_in, rank_in, rank_out);
458 let mut ct_rgsw: GGSWCiphertext<Vec<u8>, FFT64> = GGSWCiphertext::alloc(&module, basek, k_ggsw, rows, digits, rank_out);
459
460 let mut pt_rgsw: ScalarZnx<Vec<u8>> = module.new_scalar_znx(1);
461
462 let mut source_xs: Source = Source::new([0u8; 32]);
463 let mut source_xe: Source = Source::new([0u8; 32]);
464 let mut source_xa: Source = Source::new([0u8; 32]);
465
466 let mut scratch: ScratchOwned = ScratchOwned::new(
467 GLWESwitchingKey::encrypt_sk_scratch_space(&module, basek, k_in, rank_in, rank_out)
468 | FourierGLWECiphertext::decrypt_scratch_space(&module, basek, k_out)
469 | GLWESwitchingKey::external_product_scratch_space(&module, basek, k_out, k_in, k_ggsw, digits, rank_out)
470 | GGSWCiphertext::encrypt_sk_scratch_space(&module, basek, k_ggsw, rank_out),
471 );
472
473 let r: usize = 1;
474
475 pt_rgsw.to_mut().raw_mut()[r] = 1; // X^{r}
476
477 let mut sk_in: GLWESecret<Vec<u8>> = GLWESecret::alloc(&module, rank_in);
478 sk_in.fill_ternary_prob(0.5, &mut source_xs);
479
480 let mut sk_out: GLWESecret<Vec<u8>> = GLWESecret::alloc(&module, rank_out);
481 sk_out.fill_ternary_prob(0.5, &mut source_xs);
482 let sk_out_dft: FourierGLWESecret<Vec<u8>, FFT64> = FourierGLWESecret::from(&module, &sk_out);
483
484 // gglwe_{s1}(s0) = s0 -> s1
485 ct_gglwe_in.encrypt_sk(
486 &module,
487 &sk_in,
488 &sk_out_dft,
489 &mut source_xa,
490 &mut source_xe,
491 sigma,
492 scratch.borrow(),
493 );
494
495 ct_rgsw.encrypt_sk(
496 &module,
497 &pt_rgsw,
498 &sk_out_dft,
499 &mut source_xa,
500 &mut source_xe,
501 sigma,
502 scratch.borrow(),
503 );
504
505 // gglwe_(m) (x) RGSW_(X^k) = gglwe_(m * X^k)
506 ct_gglwe_out.external_product(&module, &ct_gglwe_in, &ct_rgsw, scratch.borrow());
507
508 let mut ct_glwe_dft: FourierGLWECiphertext<Vec<u8>, FFT64> = FourierGLWECiphertext::alloc(&module, basek, k_out, rank_out);
509 let mut pt: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::alloc(&module, basek, k_out);
510
511 (0..rank_in).for_each(|i| {
512 module.vec_znx_rotate_inplace(r as i64, &mut sk_in.data, i); // * X^{r}
513 });
514
515 (0..rank_in).for_each(|col_i| {
516 (0..ct_gglwe_out.rows()).for_each(|row_i| {
517 ct_gglwe_out.get_row(&module, row_i, col_i, &mut ct_glwe_dft);
518 ct_glwe_dft.decrypt(&module, &mut pt, &sk_out_dft, scratch.borrow());
519
520 module.vec_znx_sub_scalar_inplace(
521 &mut pt.data,
522 0,
523 (digits_in - 1) + row_i * digits_in,
524 &sk_in.data,
525 col_i,
526 );
527
528 let noise_have: f64 = pt.data.std(0, basek).log2();
529
530 let var_gct_err_lhs: f64 = sigma * sigma;
531 let var_gct_err_rhs: f64 = 0f64;
532
533 let var_msg: f64 = 1f64 / module.n() as f64; // X^{k}
534 let var_a0_err: f64 = sigma * sigma;
535 let var_a1_err: f64 = 1f64 / 12f64;
536
537 let noise_want: f64 = noise_ggsw_product(
538 module.n() as f64,
539 basek * digits,
540 0.5,
541 var_msg,
542 var_a0_err,
543 var_a1_err,
544 var_gct_err_lhs,
545 var_gct_err_rhs,
546 rank_out as f64,
547 k_in,
548 k_ggsw,
549 );
550
551 assert!(
552 (noise_have - noise_want).abs() <= 1.0,
553 "{} {}",
554 noise_have,
555 noise_want
556 );
557 });
558 });
559}
560
561fn test_external_product_inplace(
562 log_n: usize,
563 basek: usize,
564 k_ct: usize,
565 k_ggsw: usize,
566 digits: usize,
567 rank_in: usize,
568 rank_out: usize,
569 sigma: f64,
570) {
571 let module: Module<FFT64> = Module::<FFT64>::new(1 << log_n);
572
573 let rows: usize = k_ct.div_ceil(basek * digits);
574
575 let digits_in: usize = 1;
576
577 let mut ct_gglwe: GLWESwitchingKey<Vec<u8>, FFT64> =
578 GLWESwitchingKey::alloc(&module, basek, k_ct, rows, digits_in, rank_in, rank_out);
579 let mut ct_rgsw: GGSWCiphertext<Vec<u8>, FFT64> = GGSWCiphertext::alloc(&module, basek, k_ggsw, rows, digits, rank_out);
580
581 let mut pt_rgsw: ScalarZnx<Vec<u8>> = module.new_scalar_znx(1);
582
583 let mut source_xs: Source = Source::new([0u8; 32]);
584 let mut source_xe: Source = Source::new([0u8; 32]);
585 let mut source_xa: Source = Source::new([0u8; 32]);
586
587 let mut scratch: ScratchOwned = ScratchOwned::new(
588 GLWESwitchingKey::encrypt_sk_scratch_space(&module, basek, k_ct, rank_in, rank_out)
589 | FourierGLWECiphertext::decrypt_scratch_space(&module, basek, k_ct)
590 | GLWESwitchingKey::external_product_inplace_scratch_space(&module, basek, k_ct, k_ggsw, digits, rank_out)
591 | GGSWCiphertext::encrypt_sk_scratch_space(&module, basek, k_ggsw, rank_out),
592 );
593
594 let r: usize = 1;
595
596 pt_rgsw.to_mut().raw_mut()[r] = 1; // X^{r}
597
598 let mut sk_in: GLWESecret<Vec<u8>> = GLWESecret::alloc(&module, rank_in);
599 sk_in.fill_ternary_prob(0.5, &mut source_xs);
600
601 let mut sk_out: GLWESecret<Vec<u8>> = GLWESecret::alloc(&module, rank_out);
602 sk_out.fill_ternary_prob(0.5, &mut source_xs);
603 let sk_out_dft: FourierGLWESecret<Vec<u8>, FFT64> = FourierGLWESecret::from(&module, &sk_out);
604
605 // gglwe_{s1}(s0) = s0 -> s1
606 ct_gglwe.encrypt_sk(
607 &module,
608 &sk_in,
609 &sk_out_dft,
610 &mut source_xa,
611 &mut source_xe,
612 sigma,
613 scratch.borrow(),
614 );
615
616 ct_rgsw.encrypt_sk(
617 &module,
618 &pt_rgsw,
619 &sk_out_dft,
620 &mut source_xa,
621 &mut source_xe,
622 sigma,
623 scratch.borrow(),
624 );
625
626 // gglwe_(m) (x) RGSW_(X^k) = gglwe_(m * X^k)
627 ct_gglwe.external_product_inplace(&module, &ct_rgsw, scratch.borrow());
628
629 let mut ct_glwe_dft: FourierGLWECiphertext<Vec<u8>, FFT64> = FourierGLWECiphertext::alloc(&module, basek, k_ct, rank_out);
630 let mut pt: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::alloc(&module, basek, k_ct);
631
632 (0..rank_in).for_each(|i| {
633 module.vec_znx_rotate_inplace(r as i64, &mut sk_in.data, i); // * X^{r}
634 });
635
636 (0..rank_in).for_each(|col_i| {
637 (0..ct_gglwe.rows()).for_each(|row_i| {
638 ct_gglwe.get_row(&module, row_i, col_i, &mut ct_glwe_dft);
639 ct_glwe_dft.decrypt(&module, &mut pt, &sk_out_dft, scratch.borrow());
640
641 module.vec_znx_sub_scalar_inplace(
642 &mut pt.data,
643 0,
644 (digits_in - 1) + row_i * digits_in,
645 &sk_in.data,
646 col_i,
647 );
648
649 let noise_have: f64 = pt.data.std(0, basek).log2();
650
651 let var_gct_err_lhs: f64 = sigma * sigma;
652 let var_gct_err_rhs: f64 = 0f64;
653
654 let var_msg: f64 = 1f64 / module.n() as f64; // X^{k}
655 let var_a0_err: f64 = sigma * sigma;
656 let var_a1_err: f64 = 1f64 / 12f64;
657
658 let noise_want: f64 = noise_ggsw_product(
659 module.n() as f64,
660 basek * digits,
661 0.5,
662 var_msg,
663 var_a0_err,
664 var_a1_err,
665 var_gct_err_lhs,
666 var_gct_err_rhs,
667 rank_out as f64,
668 k_ct,
669 k_ggsw,
670 );
671
672 assert!(
673 (noise_have - noise_want).abs() <= 1.0,
674 "{} {}",
675 noise_have,
676 noise_want
677 );
678 });
679 });
680}