arnaucube
arnaucube/poulpyPublic Self-hosted repository
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}