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