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