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