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_gglwe_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    (1..4).for_each(|rank_in| {
201        (1..4).for_each(|rank_out| {
202            println!("test keyswitch rank_in: {} rank_out: {}", rank_in, rank_out);
203            test_keyswitch(12, 12, 60, 45, 60, rank_in, rank_out, 3.2);
204        });
205    });
206}
207
208fn test_keyswitch(
209    log_n: usize,
210    basek: usize,
211    k_keyswitch: usize,
212    k_ct_in: usize,
213    k_ct_out: usize,
214    rank_in: usize,
215    rank_out: usize,
216    sigma: f64,
217) {
218    let module: Module<FFT64> = Module::<FFT64>::new(1 << log_n);
219    let rows: usize = (k_ct_in + basek - 1) / basek;
220
221    let mut ksk: GLWESwitchingKey<Vec<u8>, FFT64> = GLWESwitchingKey::new(&module, basek, k_keyswitch, rows, rank_in, rank_out);
222    let mut ct_in: GLWECiphertext<Vec<u8>> = GLWECiphertext::new(&module, basek, k_ct_in, rank_in);
223    let mut ct_out: GLWECiphertext<Vec<u8>> = GLWECiphertext::new(&module, basek, k_ct_out, rank_out);
224    let mut pt_want: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::new(&module, basek, k_ct_in);
225    let mut pt_have: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::new(&module, basek, k_ct_out);
226
227    let mut source_xs: Source = Source::new([0u8; 32]);
228    let mut source_xe: Source = Source::new([0u8; 32]);
229    let mut source_xa: Source = Source::new([0u8; 32]);
230
231    // Random input plaintext
232    pt_want
233        .data
234        .fill_uniform(basek, 0, pt_want.size(), &mut source_xa);
235
236    let mut scratch: ScratchOwned = ScratchOwned::new(
237        GLWESwitchingKey::encrypt_sk_scratch_space(&module, rank_in, ksk.size())
238            | GLWECiphertext::decrypt_scratch_space(&module, ct_out.size())
239            | GLWECiphertext::encrypt_sk_scratch_space(&module, rank_out, ct_in.size())
240            | GLWECiphertext::keyswitch_scratch_space(
241                &module,
242                ct_out.size(),
243                ct_in.size(),
244                ksk.size(),
245                rank_in,
246                rank_out,
247            ),
248    );
249
250    let mut sk_in: SecretKey<Vec<u8>> = SecretKey::new(&module, rank_in);
251    sk_in.fill_ternary_prob(0.5, &mut source_xs);
252
253    let mut sk_in_dft: SecretKeyFourier<Vec<u8>, FFT64> = SecretKeyFourier::new(&module, rank_in);
254    sk_in_dft.dft(&module, &sk_in);
255
256    let mut sk_out: SecretKey<Vec<u8>> = SecretKey::new(&module, rank_out);
257    sk_out.fill_ternary_prob(0.5, &mut source_xs);
258
259    let mut sk_out_dft: SecretKeyFourier<Vec<u8>, FFT64> = SecretKeyFourier::new(&module, rank_out);
260    sk_out_dft.dft(&module, &sk_out);
261
262    ksk.encrypt_sk(
263        &module,
264        &sk_in,
265        &sk_out_dft,
266        &mut source_xa,
267        &mut source_xe,
268        sigma,
269        scratch.borrow(),
270    );
271
272    ct_in.encrypt_sk(
273        &module,
274        &pt_want,
275        &sk_in_dft,
276        &mut source_xa,
277        &mut source_xe,
278        sigma,
279        scratch.borrow(),
280    );
281
282    ct_out.keyswitch(&module, &ct_in, &ksk, scratch.borrow());
283
284    ct_out.decrypt(&module, &mut pt_have, &sk_out_dft, scratch.borrow());
285
286    module.vec_znx_sub_ab_inplace(&mut pt_have, 0, &pt_want, 0);
287
288    let noise_have: f64 = pt_have.data.std(0, basek).log2();
289    let noise_want: f64 = noise_gglwe_product(
290        module.n() as f64,
291        basek,
292        0.5,
293        0.5,
294        0f64,
295        sigma * sigma,
296        0f64,
297        rank_in as f64,
298        k_ct_in,
299        k_keyswitch,
300    );
301
302    assert!(
303        (noise_have - noise_want).abs() <= 0.1,
304        "{} {}",
305        noise_have,
306        noise_want
307    );
308}
309
310#[test]
311fn keyswich_inplace() {
312    let module: Module<FFT64> = Module::<FFT64>::new(2048);
313    let basek: usize = 12;
314    let log_k_grlwe: usize = 60;
315    let log_k_rlwe: usize = 45;
316    let rows: usize = (log_k_rlwe + basek - 1) / basek;
317    let rank: usize = 1;
318
319    let sigma: f64 = 3.2;
320
321    let mut ct_grlwe: GLWESwitchingKey<Vec<u8>, FFT64> = GLWESwitchingKey::new(&module, basek, log_k_grlwe, rows, rank, rank);
322    let mut ct_rlwe: GLWECiphertext<Vec<u8>> = GLWECiphertext::new(&module, basek, log_k_rlwe, rank);
323    let mut pt_want: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::new(&module, basek, log_k_rlwe);
324    let mut pt_have: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::new(&module, basek, log_k_rlwe);
325
326    let mut source_xs: Source = Source::new([0u8; 32]);
327    let mut source_xe: Source = Source::new([0u8; 32]);
328    let mut source_xa: Source = Source::new([0u8; 32]);
329
330    // Random input plaintext
331    pt_want
332        .data
333        .fill_uniform(basek, 0, pt_want.size(), &mut source_xa);
334
335    let mut scratch: ScratchOwned = ScratchOwned::new(
336        GLWESwitchingKey::encrypt_sk_scratch_space(&module, rank, ct_grlwe.size())
337            | GLWECiphertext::decrypt_scratch_space(&module, ct_rlwe.size())
338            | GLWECiphertext::encrypt_sk_scratch_space(&module, rank, ct_rlwe.size())
339            | GLWECiphertext::keyswitch_inplace_scratch_space(&module, ct_rlwe.size(), ct_grlwe.size(), rank),
340    );
341
342    let mut sk0: SecretKey<Vec<u8>> = SecretKey::new(&module, rank);
343    sk0.fill_ternary_prob(0.5, &mut source_xs);
344
345    let mut sk0_dft: SecretKeyFourier<Vec<u8>, FFT64> = SecretKeyFourier::new(&module, rank);
346    sk0_dft.dft(&module, &sk0);
347
348    let mut sk1: SecretKey<Vec<u8>> = SecretKey::new(&module, rank);
349    sk1.fill_ternary_prob(0.5, &mut source_xs);
350
351    let mut sk1_dft: SecretKeyFourier<Vec<u8>, FFT64> = SecretKeyFourier::new(&module, rank);
352    sk1_dft.dft(&module, &sk1);
353
354    ct_grlwe.encrypt_sk(
355        &module,
356        &sk0,
357        &sk1_dft,
358        &mut source_xa,
359        &mut source_xe,
360        sigma,
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        scratch.borrow(),
372    );
373
374    ct_rlwe.keyswitch_inplace(&module, &ct_grlwe, scratch.borrow());
375
376    ct_rlwe.decrypt(&module, &mut pt_have, &sk1_dft, scratch.borrow());
377
378    module.vec_znx_sub_ab_inplace(&mut pt_have, 0, &pt_want, 0);
379
380    let noise_have: f64 = pt_have.data.std(0, basek).log2();
381    let noise_want: f64 = noise_gglwe_product(
382        module.n() as f64,
383        basek,
384        0.5,
385        0.5,
386        0f64,
387        sigma * sigma,
388        0f64,
389        rank as f64,
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
414    let mut ct_rgsw: GGSWCiphertext<Vec<u8>, FFT64> = GGSWCiphertext::new(&module, basek, log_k_grlwe, rows, rank);
415    let mut ct_rlwe_in: GLWECiphertext<Vec<u8>> = GLWECiphertext::new(&module, basek, log_k_rlwe_in, rank);
416    let mut ct_rlwe_out: GLWECiphertext<Vec<u8>> = GLWECiphertext::new(&module, basek, log_k_rlwe_out, rank);
417    let mut pt_rgsw: ScalarZnx<Vec<u8>> = module.new_scalar_znx(1);
418    let mut pt_want: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::new(&module, basek, log_k_rlwe_in);
419    let mut pt_have: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::new(&module, basek, log_k_rlwe_out);
420
421    let mut source_xs: Source = Source::new([0u8; 32]);
422    let mut source_xe: Source = Source::new([0u8; 32]);
423    let mut source_xa: Source = Source::new([0u8; 32]);
424
425    // Random input plaintext
426    pt_want
427        .data
428        .fill_uniform(basek, 0, pt_want.size(), &mut source_xa);
429
430    pt_want.to_mut().at_mut(0, 0)[1] = 1;
431
432    let k: usize = 1;
433
434    pt_rgsw.raw_mut()[k] = 1; // X^{k}
435
436    let mut scratch: ScratchOwned = ScratchOwned::new(
437        GGSWCiphertext::encrypt_sk_scratch_space(&module, rank, ct_rgsw.size())
438            | GLWECiphertext::decrypt_scratch_space(&module, ct_rlwe_out.size())
439            | GLWECiphertext::encrypt_sk_scratch_space(&module, rank, ct_rlwe_in.size())
440            | GLWECiphertext::external_product_scratch_space(
441                &module,
442                ct_rlwe_out.size(),
443                ct_rlwe_in.size(),
444                ct_rgsw.size(),
445                rank,
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        scratch.borrow(),
463    );
464
465    ct_rlwe_in.encrypt_sk(
466        &module,
467        &pt_want,
468        &sk_dft,
469        &mut source_xa,
470        &mut source_xe,
471        sigma,
472        scratch.borrow(),
473    );
474
475    ct_rlwe_out.external_product(&module, &ct_rlwe_in, &ct_rgsw, scratch.borrow());
476
477    ct_rlwe_out.decrypt(&module, &mut pt_have, &sk_dft, scratch.borrow());
478
479    module.vec_znx_rotate_inplace(k as i64, &mut pt_want, 0);
480
481    module.vec_znx_sub_ab_inplace(&mut pt_have, 0, &pt_want, 0);
482
483    let noise_have: f64 = pt_have.data.std(0, basek).log2();
484
485    let var_gct_err_lhs: f64 = sigma * sigma;
486    let var_gct_err_rhs: f64 = 0f64;
487
488    let var_msg: f64 = 1f64 / module.n() as f64; // X^{k}
489    let var_a0_err: f64 = sigma * sigma;
490    let var_a1_err: f64 = 1f64 / 12f64;
491
492    let noise_want: f64 = noise_rgsw_product(
493        module.n() as f64,
494        basek,
495        0.5,
496        var_msg,
497        var_a0_err,
498        var_a1_err,
499        var_gct_err_lhs,
500        var_gct_err_rhs,
501        log_k_rlwe_in,
502        log_k_grlwe,
503    );
504
505    assert!(
506        (noise_have - noise_want).abs() <= 0.1,
507        "{} {}",
508        noise_have,
509        noise_want
510    );
511}
512
513#[test]
514fn external_product_inplace() {
515    let module: Module<FFT64> = Module::<FFT64>::new(2048);
516    let basek: usize = 12;
517    let log_k_grlwe: usize = 60;
518    let log_k_rlwe_in: usize = 45;
519    let log_k_rlwe_out: usize = 60;
520    let rows: usize = (log_k_rlwe_in + basek - 1) / basek;
521    let rank: usize = 1;
522
523    let sigma: f64 = 3.2;
524
525    let mut ct_rgsw: GGSWCiphertext<Vec<u8>, FFT64> = GGSWCiphertext::new(&module, basek, log_k_grlwe, rows, rank);
526    let mut ct_rlwe: GLWECiphertext<Vec<u8>> = GLWECiphertext::new(&module, basek, 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, basek, log_k_rlwe_in);
529    let mut pt_have: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::new(&module, basek, 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(basek, 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(), rank),
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        scratch.borrow(),
567    );
568
569    ct_rlwe.encrypt_sk(
570        &module,
571        &pt_want,
572        &sk_dft,
573        &mut source_xa,
574        &mut source_xe,
575        sigma,
576        scratch.borrow(),
577    );
578
579    ct_rlwe.external_product_inplace(&module, &ct_rgsw, scratch.borrow());
580
581    ct_rlwe.decrypt(&module, &mut pt_have, &sk_dft, scratch.borrow());
582
583    module.vec_znx_rotate_inplace(k as i64, &mut pt_want, 0);
584
585    module.vec_znx_sub_ab_inplace(&mut pt_have, 0, &pt_want, 0);
586
587    let noise_have: f64 = pt_have.data.std(0, basek).log2();
588
589    let var_gct_err_lhs: f64 = sigma * sigma;
590    let var_gct_err_rhs: f64 = 0f64;
591
592    let var_msg: f64 = 1f64 / module.n() as f64; // X^{k}
593    let var_a0_err: f64 = sigma * sigma;
594    let var_a1_err: f64 = 1f64 / 12f64;
595
596    let noise_want: f64 = noise_rgsw_product(
597        module.n() as f64,
598        basek,
599        0.5,
600        var_msg,
601        var_a0_err,
602        var_a1_err,
603        var_gct_err_lhs,
604        var_gct_err_rhs,
605        log_k_rlwe_in,
606        log_k_grlwe,
607    );
608
609    assert!(
610        (noise_have - noise_want).abs() <= 0.1,
611        "{} {}",
612        noise_have,
613        noise_want
614    );
615}