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