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