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