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    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}