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