arnaucube
arnaucube/poulpyPublic Self-hosted repository
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, div_ceil,
  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 = div_ceil(k_in, 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 = div_ceil(k_ct, 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 = div_ceil(k_in, 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 = div_ceil(k_ct, 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 = div_ceil(k_in, 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 = div_ceil(k_ct, 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 = div_ceil(k_in, 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 = div_ceil(k_ct, 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}