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