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, ScalarZnxOps, ScratchOwned, Stats, VecZnxBig, VecZnxBigAlloc,
  3    VecZnxBigOps, 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
295#[test]
296fn automorphism() {
297    (1..4).for_each(|rank| {
298        println!("test automorphism rank: {}", rank);
299        test_automorphism(-5, 12, 15, 60, rank, 3.2);
300    });
301}
302
303fn test_automorphism(p: i64, log_n: usize, basek: usize, k: usize, rank: usize, sigma: f64) {
304    let module: Module<FFT64> = Module::<FFT64>::new(1 << log_n);
305    let rows: usize = (k + basek - 1) / basek;
306
307    let mut ct_in: GGSWCiphertext<Vec<u8>, FFT64> = GGSWCiphertext::new(&module, basek, k, rows, rank);
308    let mut ct_out: GGSWCiphertext<Vec<u8>, FFT64> = GGSWCiphertext::new(&module, basek, k, rows, rank);
309    let mut tensor_key: TensorKey<Vec<u8>, FFT64> = TensorKey::new(&module, basek, k, rows, rank);
310    let mut auto_key: AutomorphismKey<Vec<u8>, FFT64> = AutomorphismKey::new(&module, basek, k, rows, rank);
311    let mut pt_have: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::new(&module, basek, k);
312    let mut pt_want: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::new(&module, basek, k);
313    let mut pt_scalar: ScalarZnx<Vec<u8>> = module.new_scalar_znx(1);
314
315    let mut source_xs: Source = Source::new([0u8; 32]);
316    let mut source_xe: Source = Source::new([0u8; 32]);
317    let mut source_xa: Source = Source::new([0u8; 32]);
318
319    let mut scratch: ScratchOwned = ScratchOwned::new(
320        GGSWCiphertext::encrypt_sk_scratch_space(&module, rank, ct_in.size())
321            | GLWECiphertextFourier::decrypt_scratch_space(&module, ct_out.size())
322            | AutomorphismKey::encrypt_sk_scratch_space(&module, rank, auto_key.size())
323            | TensorKey::encrypt_sk_scratch_space(&module, rank, tensor_key.size())
324            | GGSWCiphertext::automorphism_scratch_space(
325                &module,
326                ct_out.size(),
327                ct_in.size(),
328                auto_key.size(),
329                tensor_key.size(),
330                rank,
331            ),
332    );
333
334    let var_xs: f64 = 0.5;
335
336    let mut sk: SecretKey<Vec<u8>> = SecretKey::new(&module, rank);
337    sk.fill_ternary_prob(var_xs, &mut source_xs);
338
339    let mut sk_dft: SecretKeyFourier<Vec<u8>, FFT64> = SecretKeyFourier::new(&module, rank);
340    sk_dft.dft(&module, &sk);
341
342    auto_key.encrypt_sk(
343        &module,
344        p,
345        &sk,
346        &mut source_xa,
347        &mut source_xe,
348        sigma,
349        scratch.borrow(),
350    );
351    tensor_key.encrypt_sk(
352        &module,
353        &sk_dft,
354        &mut source_xa,
355        &mut source_xe,
356        sigma,
357        scratch.borrow(),
358    );
359
360    pt_scalar.fill_ternary_hw(0, module.n(), &mut source_xs);
361
362    ct_in.encrypt_sk(
363        &module,
364        &pt_scalar,
365        &sk_dft,
366        &mut source_xa,
367        &mut source_xe,
368        sigma,
369        scratch.borrow(),
370    );
371
372    ct_out.automorphism(&module, &ct_in, &auto_key, &tensor_key, scratch.borrow());
373
374    module.scalar_znx_automorphism_inplace(p, &mut pt_scalar, 0);
375
376    let mut ct_glwe_fourier: GLWECiphertextFourier<Vec<u8>, FFT64> = GLWECiphertextFourier::new(&module, basek, k, rank);
377    let mut pt_dft: VecZnxDft<Vec<u8>, FFT64> = module.new_vec_znx_dft(1, ct_out.size());
378    let mut pt_big: VecZnxBig<Vec<u8>, FFT64> = module.new_vec_znx_big(1, ct_out.size());
379
380    (0..ct_out.rank() + 1).for_each(|col_j| {
381        (0..ct_out.rows()).for_each(|row_i| {
382            module.vec_znx_add_scalar_inplace(&mut pt_want, 0, row_i, &pt_scalar, 0);
383
384            // mul with sk[col_j-1]
385            if col_j > 0 {
386                module.vec_znx_dft(&mut pt_dft, 0, &pt_want, 0);
387                module.svp_apply_inplace(&mut pt_dft, 0, &sk_dft, col_j - 1);
388                module.vec_znx_idft_tmp_a(&mut pt_big, 0, &mut pt_dft, 0);
389                module.vec_znx_big_normalize(basek, &mut pt_want, 0, &pt_big, 0, scratch.borrow());
390            }
391
392            ct_out.get_row(&module, row_i, col_j, &mut ct_glwe_fourier);
393
394            ct_glwe_fourier.decrypt(&module, &mut pt_have, &sk_dft, scratch.borrow());
395
396            module.vec_znx_sub_ab_inplace(&mut pt_have, 0, &pt_want, 0);
397
398            let noise_have: f64 = pt_have.data.std(0, basek).log2();
399            let noise_want: f64 = noise_ggsw_keyswitch(
400                module.n() as f64,
401                basek,
402                col_j,
403                var_xs,
404                0f64,
405                sigma * sigma,
406                0f64,
407                rank as f64,
408                k,
409                k,
410            );
411
412            assert!(
413                (noise_have - noise_want).abs() <= 0.1,
414                "{} {}",
415                noise_have,
416                noise_want
417            );
418
419            pt_want.data.zero();
420        });
421    });
422}
423
424fn test_external_product(log_n: usize, basek: usize, k_ggsw: usize, rank: usize, sigma: f64) {
425    let module: Module<FFT64> = Module::<FFT64>::new(1 << log_n);
426
427    let rows: usize = (k_ggsw + basek - 1) / basek;
428
429    let mut ct_ggsw_rhs: GGSWCiphertext<Vec<u8>, FFT64> = GGSWCiphertext::new(&module, basek, k_ggsw, rows, rank);
430    let mut ct_ggsw_lhs_in: GGSWCiphertext<Vec<u8>, FFT64> = GGSWCiphertext::new(&module, basek, k_ggsw, rows, rank);
431    let mut ct_ggsw_lhs_out: GGSWCiphertext<Vec<u8>, FFT64> = GGSWCiphertext::new(&module, basek, k_ggsw, rows, rank);
432    let mut pt_ggsw_lhs: ScalarZnx<Vec<u8>> = module.new_scalar_znx(1);
433    let mut pt_ggsw_rhs: ScalarZnx<Vec<u8>> = module.new_scalar_znx(1);
434
435    let mut source_xs: Source = Source::new([0u8; 32]);
436    let mut source_xe: Source = Source::new([0u8; 32]);
437    let mut source_xa: Source = Source::new([0u8; 32]);
438
439    pt_ggsw_lhs.fill_ternary_prob(0, 0.5, &mut source_xs);
440
441    let k: usize = 1;
442
443    pt_ggsw_rhs.to_mut().raw_mut()[k] = 1; //X^{k}
444
445    let mut scratch: ScratchOwned = ScratchOwned::new(
446        GLWECiphertextFourier::decrypt_scratch_space(&module, ct_ggsw_lhs_out.size())
447            | GGSWCiphertext::encrypt_sk_scratch_space(&module, rank, ct_ggsw_lhs_in.size())
448            | GGSWCiphertext::external_product_scratch_space(
449                &module,
450                ct_ggsw_lhs_out.size(),
451                ct_ggsw_lhs_in.size(),
452                ct_ggsw_rhs.size(),
453                rank,
454            ),
455    );
456
457    let mut sk: SecretKey<Vec<u8>> = SecretKey::new(&module, rank);
458    sk.fill_ternary_prob(0.5, &mut source_xs);
459
460    let mut sk_dft: SecretKeyFourier<Vec<u8>, FFT64> = SecretKeyFourier::new(&module, rank);
461    sk_dft.dft(&module, &sk);
462
463    ct_ggsw_rhs.encrypt_sk(
464        &module,
465        &pt_ggsw_rhs,
466        &sk_dft,
467        &mut source_xa,
468        &mut source_xe,
469        sigma,
470        scratch.borrow(),
471    );
472
473    ct_ggsw_lhs_in.encrypt_sk(
474        &module,
475        &pt_ggsw_lhs,
476        &sk_dft,
477        &mut source_xa,
478        &mut source_xe,
479        sigma,
480        scratch.borrow(),
481    );
482
483    ct_ggsw_lhs_out.external_product(&module, &ct_ggsw_lhs_in, &ct_ggsw_rhs, scratch.borrow());
484
485    let mut ct_glwe_fourier: GLWECiphertextFourier<Vec<u8>, FFT64> = GLWECiphertextFourier::new(&module, basek, k_ggsw, rank);
486    let mut pt: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::new(&module, basek, k_ggsw);
487    let mut pt_dft: VecZnxDft<Vec<u8>, FFT64> = module.new_vec_znx_dft(1, ct_ggsw_lhs_out.size());
488    let mut pt_big: VecZnxBig<Vec<u8>, FFT64> = module.new_vec_znx_big(1, ct_ggsw_lhs_out.size());
489    let mut pt_want: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::new(&module, basek, k_ggsw);
490
491    module.vec_znx_rotate_inplace(k as i64, &mut pt_ggsw_lhs, 0);
492
493    (0..ct_ggsw_lhs_out.rank() + 1).for_each(|col_j| {
494        (0..ct_ggsw_lhs_out.rows()).for_each(|row_i| {
495            module.vec_znx_add_scalar_inplace(&mut pt_want, 0, row_i, &pt_ggsw_lhs, 0);
496
497            if col_j > 0 {
498                module.vec_znx_dft(&mut pt_dft, 0, &pt_want, 0);
499                module.svp_apply_inplace(&mut pt_dft, 0, &sk_dft, col_j - 1);
500                module.vec_znx_idft_tmp_a(&mut pt_big, 0, &mut pt_dft, 0);
501                module.vec_znx_big_normalize(basek, &mut pt_want, 0, &pt_big, 0, scratch.borrow());
502            }
503
504            ct_ggsw_lhs_out.get_row(&module, row_i, col_j, &mut ct_glwe_fourier);
505            ct_glwe_fourier.decrypt(&module, &mut pt, &sk_dft, scratch.borrow());
506
507            module.vec_znx_sub_ab_inplace(&mut pt, 0, &pt_want, 0);
508
509            let noise_have: f64 = pt.data.std(0, basek).log2();
510
511            let var_gct_err_lhs: f64 = sigma * sigma;
512            let var_gct_err_rhs: f64 = 0f64;
513
514            let var_msg: f64 = 1f64 / module.n() as f64; // X^{k}
515            let var_a0_err: f64 = sigma * sigma;
516            let var_a1_err: f64 = 1f64 / 12f64;
517
518            let noise_want: f64 = noise_ggsw_product(
519                module.n() as f64,
520                basek,
521                0.5,
522                var_msg,
523                var_a0_err,
524                var_a1_err,
525                var_gct_err_lhs,
526                var_gct_err_rhs,
527                rank as f64,
528                k_ggsw,
529                k_ggsw,
530            );
531
532            assert!(
533                (noise_have - noise_want).abs() <= 0.1,
534                "have: {} want: {}",
535                noise_have,
536                noise_want
537            );
538
539            pt_want.data.zero();
540        });
541    });
542}
543
544fn test_external_product_inplace(log_n: usize, basek: usize, k_ggsw: usize, rank: usize, sigma: f64) {
545    let module: Module<FFT64> = Module::<FFT64>::new(1 << log_n);
546    let rows: usize = (k_ggsw + basek - 1) / basek;
547
548    let mut ct_ggsw_rhs: GGSWCiphertext<Vec<u8>, FFT64> = GGSWCiphertext::new(&module, basek, k_ggsw, rows, rank);
549    let mut ct_ggsw_lhs: GGSWCiphertext<Vec<u8>, FFT64> = GGSWCiphertext::new(&module, basek, k_ggsw, rows, rank);
550    let mut pt_ggsw_lhs: ScalarZnx<Vec<u8>> = module.new_scalar_znx(1);
551    let mut pt_ggsw_rhs: ScalarZnx<Vec<u8>> = module.new_scalar_znx(1);
552
553    let mut source_xs: Source = Source::new([0u8; 32]);
554    let mut source_xe: Source = Source::new([0u8; 32]);
555    let mut source_xa: Source = Source::new([0u8; 32]);
556
557    pt_ggsw_lhs.fill_ternary_prob(0, 0.5, &mut source_xs);
558
559    let k: usize = 1;
560
561    pt_ggsw_rhs.to_mut().raw_mut()[k] = 1; //X^{k}
562
563    let mut scratch: ScratchOwned = ScratchOwned::new(
564        GLWESwitchingKey::encrypt_sk_scratch_space(&module, rank, ct_ggsw_rhs.size())
565            | GLWECiphertextFourier::decrypt_scratch_space(&module, ct_ggsw_lhs.size())
566            | GGSWCiphertext::encrypt_sk_scratch_space(&module, rank, ct_ggsw_lhs.size())
567            | GGSWCiphertext::external_product_inplace_scratch_space(&module, ct_ggsw_lhs.size(), ct_ggsw_rhs.size(), rank),
568    );
569
570    let mut sk: SecretKey<Vec<u8>> = SecretKey::new(&module, rank);
571    sk.fill_ternary_prob(0.5, &mut source_xs);
572
573    let mut sk_dft: SecretKeyFourier<Vec<u8>, FFT64> = SecretKeyFourier::new(&module, rank);
574    sk_dft.dft(&module, &sk);
575
576    ct_ggsw_rhs.encrypt_sk(
577        &module,
578        &pt_ggsw_rhs,
579        &sk_dft,
580        &mut source_xa,
581        &mut source_xe,
582        sigma,
583        scratch.borrow(),
584    );
585
586    ct_ggsw_lhs.encrypt_sk(
587        &module,
588        &pt_ggsw_lhs,
589        &sk_dft,
590        &mut source_xa,
591        &mut source_xe,
592        sigma,
593        scratch.borrow(),
594    );
595
596    ct_ggsw_lhs.external_product_inplace(&module, &ct_ggsw_rhs, scratch.borrow());
597
598    let mut ct_glwe_fourier: GLWECiphertextFourier<Vec<u8>, FFT64> = GLWECiphertextFourier::new(&module, basek, k_ggsw, rank);
599    let mut pt: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::new(&module, basek, k_ggsw);
600    let mut pt_dft: VecZnxDft<Vec<u8>, FFT64> = module.new_vec_znx_dft(1, ct_ggsw_lhs.size());
601    let mut pt_big: VecZnxBig<Vec<u8>, FFT64> = module.new_vec_znx_big(1, ct_ggsw_lhs.size());
602    let mut pt_want: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::new(&module, basek, k_ggsw);
603
604    module.vec_znx_rotate_inplace(k as i64, &mut pt_ggsw_lhs, 0);
605
606    (0..ct_ggsw_lhs.rank() + 1).for_each(|col_j| {
607        (0..ct_ggsw_lhs.rows()).for_each(|row_i| {
608            module.vec_znx_add_scalar_inplace(&mut pt_want, 0, row_i, &pt_ggsw_lhs, 0);
609
610            if col_j > 0 {
611                module.vec_znx_dft(&mut pt_dft, 0, &pt_want, 0);
612                module.svp_apply_inplace(&mut pt_dft, 0, &sk_dft, col_j - 1);
613                module.vec_znx_idft_tmp_a(&mut pt_big, 0, &mut pt_dft, 0);
614                module.vec_znx_big_normalize(basek, &mut pt_want, 0, &pt_big, 0, scratch.borrow());
615            }
616
617            ct_ggsw_lhs.get_row(&module, row_i, col_j, &mut ct_glwe_fourier);
618            ct_glwe_fourier.decrypt(&module, &mut pt, &sk_dft, scratch.borrow());
619
620            module.vec_znx_sub_ab_inplace(&mut pt, 0, &pt_want, 0);
621
622            let noise_have: f64 = pt.data.std(0, basek).log2();
623
624            let var_gct_err_lhs: f64 = sigma * sigma;
625            let var_gct_err_rhs: f64 = 0f64;
626
627            let var_msg: f64 = 1f64 / module.n() as f64; // X^{k}
628            let var_a0_err: f64 = sigma * sigma;
629            let var_a1_err: f64 = 1f64 / 12f64;
630
631            let noise_want: f64 = noise_ggsw_product(
632                module.n() as f64,
633                basek,
634                0.5,
635                var_msg,
636                var_a0_err,
637                var_a1_err,
638                var_gct_err_lhs,
639                var_gct_err_rhs,
640                rank as f64,
641                k_ggsw,
642                k_ggsw,
643            );
644
645            assert!(
646                (noise_have - noise_want).abs() <= 0.1,
647                "have: {} want: {}",
648                noise_have,
649                noise_want
650            );
651
652            pt_want.data.zero();
653        });
654    });
655}
656pub(crate) fn noise_ggsw_product(
657    n: f64,
658    basek: usize,
659    var_xs: f64,
660    var_msg: f64,
661    var_a0_err: f64,
662    var_a1_err: f64,
663    var_gct_err_lhs: f64,
664    var_gct_err_rhs: f64,
665    rank: f64,
666    a_logq: usize,
667    b_logq: usize,
668) -> f64 {
669    let a_logq: usize = a_logq.min(b_logq);
670    let a_cols: usize = (a_logq + basek - 1) / basek;
671
672    let b_scale = 2.0f64.powi(b_logq as i32);
673    let a_scale: f64 = 2.0f64.powi((b_logq - a_logq) as i32);
674
675    let base: f64 = (1 << (basek)) as f64;
676    let var_base: f64 = base * base / 12f64;
677
678    // lhs = a_cols * n * (var_base * var_gct_err_lhs + var_e_a * var_msg * p^2)
679    // rhs = a_cols * n * var_base * var_gct_err_rhs * var_xs
680    let mut noise: f64 = (rank + 1.0) * (a_cols as f64) * n * var_base * (var_gct_err_lhs + var_xs * var_gct_err_rhs);
681    noise += var_msg * var_a0_err * a_scale * a_scale * n;
682    noise += var_msg * var_a1_err * a_scale * a_scale * n * var_xs * rank;
683    noise = noise.sqrt();
684    noise /= b_scale;
685    noise.log2().min(-1.0) // max noise is [-2^{-1}, 2^{-1}]
686}