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