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