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