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