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