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}