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