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}