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}