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