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
 43#[test]
 44fn keyswitch() {
 45    (1..4).for_each(|rank_in| {
 46        (1..4).for_each(|rank_out| {
 47            println!("test keyswitch rank_in: {} rank_out: {}", rank_in, rank_out);
 48            test_keyswitch(12, 12, 60, 45, 60, rank_in, rank_out, 3.2);
 49        });
 50    });
 51}
 52
 53#[test]
 54fn keyswitch_inplace() {
 55    (1..4).for_each(|rank| {
 56        println!("test keyswitch_inplace rank: {}", rank);
 57        test_keyswitch_inplace(12, 12, 60, 45, rank, 3.2);
 58    });
 59}
 60
 61fn test_encrypt_sk(log_n: usize, basek: usize, k_ct: usize, k_pt: usize, sigma: f64, rank: usize) {
 62    let module: Module<FFT64> = Module::<FFT64>::new(1 << log_n);
 63
 64    let mut ct: GLWECiphertext<Vec<u8>> = GLWECiphertext::new(&module, basek, k_ct, rank);
 65    let mut pt: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::new(&module, basek, k_pt);
 66
 67    let mut source_xs: Source = Source::new([0u8; 32]);
 68    let mut source_xe: Source = Source::new([0u8; 32]);
 69    let mut source_xa: Source = Source::new([0u8; 32]);
 70
 71    let mut scratch: ScratchOwned = ScratchOwned::new(
 72        GLWECiphertext::encrypt_sk_scratch_space(&module, ct.size()) | GLWECiphertext::decrypt_scratch_space(&module, ct.size()),
 73    );
 74
 75    let mut sk: SecretKey<Vec<u8>> = SecretKey::new(&module, rank);
 76    sk.fill_ternary_prob(0.5, &mut source_xs);
 77
 78    let mut sk_dft: SecretKeyFourier<Vec<u8>, FFT64> = SecretKeyFourier::new(&module, rank);
 79    sk_dft.dft(&module, &sk);
 80
 81    let mut data_want: Vec<i64> = vec![0i64; module.n()];
 82
 83    data_want
 84        .iter_mut()
 85        .for_each(|x| *x = source_xa.next_i64() & 0xFF);
 86
 87    pt.data.encode_vec_i64(0, basek, k_pt, &data_want, 10);
 88
 89    ct.encrypt_sk(
 90        &module,
 91        &pt,
 92        &sk_dft,
 93        &mut source_xa,
 94        &mut source_xe,
 95        sigma,
 96        scratch.borrow(),
 97    );
 98
 99    pt.data.zero();
100
101    ct.decrypt(&module, &mut pt, &sk_dft, scratch.borrow());
102
103    let mut data_have: Vec<i64> = vec![0i64; module.n()];
104
105    pt.data
106        .decode_vec_i64(0, basek, pt.size() * basek, &mut data_have);
107
108    // TODO: properly assert the decryption noise through std(dec(ct) - pt)
109    let scale: f64 = (1 << (pt.size() * basek - k_pt)) as f64;
110    izip!(data_want.iter(), data_have.iter()).for_each(|(a, b)| {
111        let b_scaled = (*b as f64) / scale;
112        assert!(
113            (*a as f64 - b_scaled).abs() < 0.1,
114            "{} {}",
115            *a as f64,
116            b_scaled
117        )
118    });
119}
120
121fn test_encrypt_zero_sk(log_n: usize, basek: usize, k_ct: usize, sigma: f64, rank: usize) {
122    let module: Module<FFT64> = Module::<FFT64>::new(1 << log_n);
123
124    let mut pt: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::new(&module, basek, k_ct);
125
126    let mut source_xs: Source = Source::new([0u8; 32]);
127    let mut source_xe: Source = Source::new([1u8; 32]);
128    let mut source_xa: Source = Source::new([0u8; 32]);
129
130    let mut sk: SecretKey<Vec<u8>> = SecretKey::new(&module, rank);
131    sk.fill_ternary_prob(0.5, &mut source_xs);
132    let mut sk_dft: SecretKeyFourier<Vec<u8>, FFT64> = SecretKeyFourier::new(&module, rank);
133    sk_dft.dft(&module, &sk);
134
135    let mut ct_dft: GLWECiphertextFourier<Vec<u8>, FFT64> = GLWECiphertextFourier::new(&module, basek, k_ct, rank);
136
137    let mut scratch: ScratchOwned = ScratchOwned::new(
138        GLWECiphertextFourier::decrypt_scratch_space(&module, ct_dft.size())
139            | GLWECiphertextFourier::encrypt_sk_scratch_space(&module, rank, ct_dft.size()),
140    );
141
142    ct_dft.encrypt_zero_sk(
143        &module,
144        &sk_dft,
145        &mut source_xa,
146        &mut source_xe,
147        sigma,
148        scratch.borrow(),
149    );
150    ct_dft.decrypt(&module, &mut pt, &sk_dft, scratch.borrow());
151
152    assert!((sigma - pt.data.std(0, basek) * (k_ct as f64).exp2()) <= 0.2);
153}
154
155fn test_encrypt_pk(log_n: usize, basek: usize, k_ct: usize, k_pk: usize, sigma: f64, rank: usize) {
156    let module: Module<FFT64> = Module::<FFT64>::new(1 << log_n);
157
158    let mut ct: GLWECiphertext<Vec<u8>> = GLWECiphertext::new(&module, basek, k_ct, rank);
159    let mut pt_want: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::new(&module, basek, k_ct);
160
161    let mut source_xs: Source = Source::new([0u8; 32]);
162    let mut source_xe: Source = Source::new([0u8; 32]);
163    let mut source_xa: Source = Source::new([0u8; 32]);
164    let mut source_xu: Source = Source::new([0u8; 32]);
165
166    let mut sk: SecretKey<Vec<u8>> = SecretKey::new(&module, rank);
167    sk.fill_ternary_prob(0.5, &mut source_xs);
168    let mut sk_dft: SecretKeyFourier<Vec<u8>, FFT64> = SecretKeyFourier::new(&module, rank);
169    sk_dft.dft(&module, &sk);
170
171    let mut pk: GLWEPublicKey<Vec<u8>, FFT64> = GLWEPublicKey::new(&module, basek, k_pk, rank);
172    pk.generate(&module, &sk_dft, &mut source_xa, &mut source_xe, sigma);
173
174    let mut scratch: ScratchOwned = ScratchOwned::new(
175        GLWECiphertext::encrypt_sk_scratch_space(&module, ct.size())
176            | GLWECiphertext::decrypt_scratch_space(&module, ct.size())
177            | GLWECiphertext::encrypt_pk_scratch_space(&module, pk.size()),
178    );
179
180    let mut data_want: Vec<i64> = vec![0i64; module.n()];
181
182    data_want
183        .iter_mut()
184        .for_each(|x| *x = source_xa.next_i64() & 0);
185
186    pt_want.data.encode_vec_i64(0, basek, k_ct, &data_want, 10);
187
188    ct.encrypt_pk(
189        &module,
190        &pt_want,
191        &pk,
192        &mut source_xu,
193        &mut source_xe,
194        sigma,
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    let noise_have: f64 = pt_want.data.std(0, basek).log2();
205    let noise_want: f64 = ((((rank as f64) + 1.0) * module.n() as f64 * 0.5 * sigma * sigma).sqrt()).log2() - (k_ct as f64);
206
207    assert!(
208        (noise_have - noise_want).abs() < 0.2,
209        "{} {}",
210        noise_have,
211        noise_want
212    );
213}
214
215fn test_keyswitch(
216    log_n: usize,
217    basek: usize,
218    k_keyswitch: usize,
219    k_ct_in: usize,
220    k_ct_out: usize,
221    rank_in: usize,
222    rank_out: usize,
223    sigma: f64,
224) {
225    let module: Module<FFT64> = Module::<FFT64>::new(1 << log_n);
226    let rows: usize = (k_ct_in + basek - 1) / basek;
227
228    let mut ksk: GLWESwitchingKey<Vec<u8>, FFT64> = GLWESwitchingKey::new(&module, basek, k_keyswitch, rows, rank_in, rank_out);
229    let mut ct_in: GLWECiphertext<Vec<u8>> = GLWECiphertext::new(&module, basek, k_ct_in, rank_in);
230    let mut ct_out: GLWECiphertext<Vec<u8>> = GLWECiphertext::new(&module, basek, k_ct_out, rank_out);
231    let mut pt_want: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::new(&module, basek, k_ct_in);
232    let mut pt_have: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::new(&module, basek, k_ct_out);
233
234    let mut source_xs: Source = Source::new([0u8; 32]);
235    let mut source_xe: Source = Source::new([0u8; 32]);
236    let mut source_xa: Source = Source::new([0u8; 32]);
237
238    // Random input plaintext
239    pt_want
240        .data
241        .fill_uniform(basek, 0, pt_want.size(), &mut source_xa);
242
243    let mut scratch: ScratchOwned = ScratchOwned::new(
244        GLWESwitchingKey::encrypt_sk_scratch_space(&module, rank_in, ksk.size())
245            | GLWECiphertext::decrypt_scratch_space(&module, ct_out.size())
246            | GLWECiphertext::encrypt_sk_scratch_space(&module, ct_in.size())
247            | GLWECiphertext::keyswitch_scratch_space(
248                &module,
249                ct_out.size(),
250                ct_in.size(),
251                ksk.size(),
252                rank_in,
253                rank_out,
254            ),
255    );
256
257    let mut sk_in: SecretKey<Vec<u8>> = SecretKey::new(&module, rank_in);
258    sk_in.fill_ternary_prob(0.5, &mut source_xs);
259
260    let mut sk_in_dft: SecretKeyFourier<Vec<u8>, FFT64> = SecretKeyFourier::new(&module, rank_in);
261    sk_in_dft.dft(&module, &sk_in);
262
263    let mut sk_out: SecretKey<Vec<u8>> = SecretKey::new(&module, rank_out);
264    sk_out.fill_ternary_prob(0.5, &mut source_xs);
265
266    let mut sk_out_dft: SecretKeyFourier<Vec<u8>, FFT64> = SecretKeyFourier::new(&module, rank_out);
267    sk_out_dft.dft(&module, &sk_out);
268
269    ksk.encrypt_sk(
270        &module,
271        &sk_in,
272        &sk_out_dft,
273        &mut source_xa,
274        &mut source_xe,
275        sigma,
276        scratch.borrow(),
277    );
278
279    ct_in.encrypt_sk(
280        &module,
281        &pt_want,
282        &sk_in_dft,
283        &mut source_xa,
284        &mut source_xe,
285        sigma,
286        scratch.borrow(),
287    );
288
289    ct_out.keyswitch(&module, &ct_in, &ksk, scratch.borrow());
290
291    ct_out.decrypt(&module, &mut pt_have, &sk_out_dft, scratch.borrow());
292
293    module.vec_znx_sub_ab_inplace(&mut pt_have, 0, &pt_want, 0);
294
295    let noise_have: f64 = pt_have.data.std(0, basek).log2();
296    let noise_want: f64 = noise_gglwe_product(
297        module.n() as f64,
298        basek,
299        0.5,
300        0.5,
301        0f64,
302        sigma * sigma,
303        0f64,
304        rank_in as f64,
305        k_ct_in,
306        k_keyswitch,
307    );
308
309    assert!(
310        (noise_have - noise_want).abs() <= 0.1,
311        "{} {}",
312        noise_have,
313        noise_want
314    );
315}
316
317fn test_keyswitch_inplace(log_n: usize, basek: usize, k_ksk: usize, k_ct: usize, rank: usize, sigma: f64) {
318    let module: Module<FFT64> = Module::<FFT64>::new(1 << log_n);
319    let rows: usize = (k_ct + basek - 1) / basek;
320
321    let mut ct_grlwe: GLWESwitchingKey<Vec<u8>, FFT64> = GLWESwitchingKey::new(&module, basek, k_ksk, rows, rank, rank);
322    let mut ct_rlwe: GLWECiphertext<Vec<u8>> = GLWECiphertext::new(&module, basek, k_ct, rank);
323    let mut pt_want: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::new(&module, basek, k_ct);
324    let mut pt_have: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::new(&module, basek, k_ct);
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, 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        k_ct,
391        k_ksk,
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    (1..4).for_each(|rank| {
405        println!("test external_product rank: {}", rank);
406        test_external_product(12, 12, 60, 45, 60, rank, 3.2);
407    });
408}
409
410fn test_external_product(log_n: usize, basek: usize, k_ggsw: usize, k_ct_in: usize, k_ct_out: usize, rank: usize, sigma: f64) {
411    let module: Module<FFT64> = Module::<FFT64>::new(1 << log_n);
412
413    let rows: usize = (k_ct_in + basek - 1) / basek;
414
415    let mut ct_rgsw: GGSWCiphertext<Vec<u8>, FFT64> = GGSWCiphertext::new(&module, basek, k_ggsw, rows, rank);
416    let mut ct_rlwe_in: GLWECiphertext<Vec<u8>> = GLWECiphertext::new(&module, basek, k_ct_in, rank);
417    let mut ct_rlwe_out: GLWECiphertext<Vec<u8>> = GLWECiphertext::new(&module, basek, k_ct_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, k_ct_in);
420    let mut pt_have: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::new(&module, basek, k_ct_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, 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                rank,
447            ),
448    );
449
450    let mut sk: SecretKey<Vec<u8>> = SecretKey::new(&module, rank);
451    sk.fill_ternary_prob(0.5, &mut source_xs);
452
453    let mut sk_dft: SecretKeyFourier<Vec<u8>, FFT64> = SecretKeyFourier::new(&module, rank);
454    sk_dft.dft(&module, &sk);
455
456    ct_rgsw.encrypt_sk(
457        &module,
458        &pt_rgsw,
459        &sk_dft,
460        &mut source_xa,
461        &mut source_xe,
462        sigma,
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        scratch.borrow(),
474    );
475
476    ct_rlwe_out.external_product(&module, &ct_rlwe_in, &ct_rgsw, scratch.borrow());
477
478    ct_rlwe_out.decrypt(&module, &mut pt_have, &sk_dft, scratch.borrow());
479
480    module.vec_znx_rotate_inplace(k as i64, &mut pt_want, 0);
481
482    module.vec_znx_sub_ab_inplace(&mut pt_have, 0, &pt_want, 0);
483
484    let noise_have: f64 = pt_have.data.std(0, basek).log2();
485
486    let var_gct_err_lhs: f64 = sigma * sigma;
487    let var_gct_err_rhs: f64 = 0f64;
488
489    let var_msg: f64 = 1f64 / module.n() as f64; // X^{k}
490    let var_a0_err: f64 = sigma * sigma;
491    let var_a1_err: f64 = 1f64 / 12f64;
492
493    let noise_want: f64 = noise_rgsw_product(
494        module.n() as f64,
495        basek,
496        0.5,
497        var_msg,
498        var_a0_err,
499        var_a1_err,
500        var_gct_err_lhs,
501        var_gct_err_rhs,
502        rank as f64,
503        k_ct_in,
504        k_ggsw,
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    (1..4).for_each(|rank| {
518        println!("test external_product rank: {}", rank);
519        test_external_product_inplace(12, 15, 60, 60, rank, 3.2);
520    });
521}
522
523fn test_external_product_inplace(log_n: usize, basek: usize, k_ggsw: usize, k_ct: usize, rank: usize, sigma: f64) {
524    let module: Module<FFT64> = Module::<FFT64>::new(1 << log_n);
525    let rows: usize = (k_ct + basek - 1) / basek;
526
527    let mut ct_rgsw: GGSWCiphertext<Vec<u8>, FFT64> = GGSWCiphertext::new(&module, basek, k_ggsw, rows, rank);
528    let mut ct_rlwe: GLWECiphertext<Vec<u8>> = GLWECiphertext::new(&module, basek, k_ct, rank);
529    let mut pt_rgsw: ScalarZnx<Vec<u8>> = module.new_scalar_znx(1);
530    let mut pt_want: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::new(&module, basek, k_ct);
531    let mut pt_have: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::new(&module, basek, k_ct);
532
533    let mut source_xs: Source = Source::new([0u8; 32]);
534    let mut source_xe: Source = Source::new([0u8; 32]);
535    let mut source_xa: Source = Source::new([0u8; 32]);
536
537    // Random input plaintext
538    pt_want
539        .data
540        .fill_uniform(basek, 0, pt_want.size(), &mut source_xa);
541
542    pt_want.to_mut().at_mut(0, 0)[1] = 1;
543
544    let k: usize = 1;
545
546    pt_rgsw.raw_mut()[k] = 1; // X^{k}
547
548    let mut scratch: ScratchOwned = ScratchOwned::new(
549        GGSWCiphertext::encrypt_sk_scratch_space(&module, rank, ct_rgsw.size())
550            | GLWECiphertext::decrypt_scratch_space(&module, ct_rlwe.size())
551            | GLWECiphertext::encrypt_sk_scratch_space(&module, ct_rlwe.size())
552            | GLWECiphertext::external_product_inplace_scratch_space(&module, ct_rlwe.size(), ct_rgsw.size(), rank),
553    );
554
555    let mut sk: SecretKey<Vec<u8>> = SecretKey::new(&module, rank);
556    sk.fill_ternary_prob(0.5, &mut source_xs);
557
558    let mut sk_dft: SecretKeyFourier<Vec<u8>, FFT64> = SecretKeyFourier::new(&module, rank);
559    sk_dft.dft(&module, &sk);
560
561    ct_rgsw.encrypt_sk(
562        &module,
563        &pt_rgsw,
564        &sk_dft,
565        &mut source_xa,
566        &mut source_xe,
567        sigma,
568        scratch.borrow(),
569    );
570
571    ct_rlwe.encrypt_sk(
572        &module,
573        &pt_want,
574        &sk_dft,
575        &mut source_xa,
576        &mut source_xe,
577        sigma,
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, basek).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        basek,
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        rank as f64,
608        k_ct,
609        k_ggsw,
610    );
611
612    assert!(
613        (noise_have - noise_want).abs() <= 0.1,
614        "{} {}",
615        noise_have,
616        noise_want
617    );
618}