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