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