poulpy/core/src/test_fft64/gglwe.rs
core/src/test_fft64/gglwe.rsRaw
1use backend::{FFT64, Module, ScalarZnx, ScalarZnxAlloc, ScalarZnxToMut, ScratchOwned, Stats, VecZnxOps, ZnxViewMut};
2use sampling::source::Source;
3
4use crate::{
5 GGSWCiphertext, GLWECiphertextFourier, GLWEPlaintext, GLWESecret, GLWESwitchingKey, GetRow, Infos,
6 test_fft64::ggsw::noise_ggsw_product,
7};
8
9#[test]
10fn encrypt_sk() {
11 (1..4).for_each(|rank_in| {
12 (1..4).for_each(|rank_out| {
13 println!("test encrypt_sk rank_in rank_out: {} {}", rank_in, rank_out);
14 test_encrypt_sk(12, 8, 54, 3.2, rank_in, rank_out);
15 });
16 });
17}
18
19#[test]
20fn key_switch() {
21 (1..4).for_each(|rank_in_s0s1| {
22 (1..4).for_each(|rank_out_s0s1| {
23 (1..4).for_each(|rank_out_s1s2| {
24 println!(
25 "test key_switch : ({},{},{})",
26 rank_in_s0s1, rank_out_s0s1, rank_out_s1s2
27 );
28 test_key_switch(12, 15, 60, 3.2, rank_in_s0s1, rank_out_s0s1, rank_out_s1s2);
29 })
30 });
31 });
32}
33
34#[test]
35fn key_switch_inplace() {
36 (1..4).for_each(|rank_in_s0s1| {
37 (1..4).for_each(|rank_out_s0s1| {
38 println!(
39 "test key_switch_inplace : ({},{})",
40 rank_in_s0s1, rank_out_s0s1
41 );
42 test_key_switch_inplace(12, 15, 60, 3.2, rank_in_s0s1, rank_out_s0s1);
43 });
44 });
45}
46
47#[test]
48fn external_product() {
49 (1..4).for_each(|rank_in| {
50 (1..4).for_each(|rank_out| {
51 println!("test external_product rank: {} {}", rank_in, rank_out);
52 test_external_product(12, 12, 60, 3.2, rank_in, rank_out);
53 });
54 });
55}
56
57#[test]
58fn external_product_inplace() {
59 (1..4).for_each(|rank_in| {
60 (1..4).for_each(|rank_out| {
61 println!(
62 "test external_product_inplace rank: {} {}",
63 rank_in, rank_out
64 );
65 test_external_product_inplace(12, 12, 60, 3.2, rank_in, rank_out);
66 });
67 });
68}
69
70fn test_encrypt_sk(log_n: usize, basek: usize, k_ksk: usize, sigma: f64, rank_in: usize, rank_out: usize) {
71 let module: Module<FFT64> = Module::<FFT64>::new(1 << log_n);
72 let rows = (k_ksk + basek - 1) / basek;
73
74 let mut ksk: GLWESwitchingKey<Vec<u8>, FFT64> = GLWESwitchingKey::alloc(&module, basek, k_ksk, rows, rank_in, rank_out);
75 let mut pt: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::alloc(&module, basek, k_ksk);
76
77 let mut source_xs: Source = Source::new([0u8; 32]);
78 let mut source_xe: Source = Source::new([0u8; 32]);
79 let mut source_xa: Source = Source::new([0u8; 32]);
80
81 let mut scratch: ScratchOwned = ScratchOwned::new(
82 GLWESwitchingKey::encrypt_sk_scratch_space(&module, basek, k_ksk, rank_out)
83 | GLWECiphertextFourier::decrypt_scratch_space(&module, basek, k_ksk),
84 );
85
86 let mut sk_in: GLWESecret<Vec<u8>, FFT64> = GLWESecret::alloc(&module, rank_in);
87 sk_in.fill_ternary_prob(&module, 0.5, &mut source_xs);
88
89 let mut sk_out: GLWESecret<Vec<u8>, FFT64> = GLWESecret::alloc(&module, rank_out);
90 sk_out.fill_ternary_prob(&module, 0.5, &mut source_xs);
91
92 ksk.generate_from_sk(
93 &module,
94 &sk_in,
95 &sk_out,
96 &mut source_xa,
97 &mut source_xe,
98 sigma,
99 scratch.borrow(),
100 );
101
102 let mut ct_glwe_fourier: GLWECiphertextFourier<Vec<u8>, FFT64> =
103 GLWECiphertextFourier::alloc(&module, basek, k_ksk, rank_out);
104
105 (0..ksk.rank_in()).for_each(|col_i| {
106 (0..ksk.rows()).for_each(|row_i| {
107 ksk.get_row(&module, row_i, col_i, &mut ct_glwe_fourier);
108 ct_glwe_fourier.decrypt(&module, &mut pt, &sk_out, scratch.borrow());
109 module.vec_znx_sub_scalar_inplace(&mut pt.data, 0, row_i, &sk_in.data, col_i);
110 let std_pt: f64 = pt.data.std(0, basek) * (k_ksk as f64).exp2();
111 assert!((sigma - std_pt).abs() <= 0.2, "{} {}", sigma, std_pt);
112 });
113 });
114}
115
116fn test_key_switch(
117 log_n: usize,
118 basek: usize,
119 k_ksk: usize,
120 sigma: f64,
121 rank_in_s0s1: usize,
122 rank_out_s0s1: usize,
123 rank_out_s1s2: usize,
124) {
125 let module: Module<FFT64> = Module::<FFT64>::new(1 << log_n);
126 let rows = (k_ksk + basek - 1) / basek;
127
128 let mut ct_gglwe_s0s1: GLWESwitchingKey<Vec<u8>, FFT64> =
129 GLWESwitchingKey::alloc(&module, basek, k_ksk, rows, rank_in_s0s1, rank_out_s0s1);
130 let mut ct_gglwe_s1s2: GLWESwitchingKey<Vec<u8>, FFT64> =
131 GLWESwitchingKey::alloc(&module, basek, k_ksk, rows, rank_out_s0s1, rank_out_s1s2);
132 let mut ct_gglwe_s0s2: GLWESwitchingKey<Vec<u8>, FFT64> =
133 GLWESwitchingKey::alloc(&module, basek, k_ksk, rows, rank_in_s0s1, rank_out_s1s2);
134
135 let mut source_xs: Source = Source::new([0u8; 32]);
136 let mut source_xe: Source = Source::new([0u8; 32]);
137 let mut source_xa: Source = Source::new([0u8; 32]);
138
139 let mut scratch: ScratchOwned = ScratchOwned::new(
140 GLWESwitchingKey::encrypt_sk_scratch_space(&module, basek, k_ksk, rank_in_s0s1 | rank_out_s0s1)
141 | GLWECiphertextFourier::decrypt_scratch_space(&module, basek, k_ksk)
142 | GLWESwitchingKey::keyswitch_scratch_space(
143 &module,
144 basek,
145 ct_gglwe_s0s2.k(),
146 ct_gglwe_s0s2.rank(),
147 ct_gglwe_s0s1.k(),
148 ct_gglwe_s0s1.rank(),
149 ct_gglwe_s1s2.k(),
150 ),
151 );
152
153 let mut sk0: GLWESecret<Vec<u8>, FFT64> = GLWESecret::alloc(&module, rank_in_s0s1);
154 sk0.fill_ternary_prob(&module, 0.5, &mut source_xs);
155
156 let mut sk1: GLWESecret<Vec<u8>, FFT64> = GLWESecret::alloc(&module, rank_out_s0s1);
157 sk1.fill_ternary_prob(&module, 0.5, &mut source_xs);
158
159 let mut sk2: GLWESecret<Vec<u8>, FFT64> = GLWESecret::alloc(&module, rank_out_s1s2);
160 sk2.fill_ternary_prob(&module, 0.5, &mut source_xs);
161
162 // gglwe_{s1}(s0) = s0 -> s1
163 ct_gglwe_s0s1.generate_from_sk(
164 &module,
165 &sk0,
166 &sk1,
167 &mut source_xa,
168 &mut source_xe,
169 sigma,
170 scratch.borrow(),
171 );
172
173 // gglwe_{s2}(s1) -> s1 -> s2
174 ct_gglwe_s1s2.generate_from_sk(
175 &module,
176 &sk1,
177 &sk2,
178 &mut source_xa,
179 &mut source_xe,
180 sigma,
181 scratch.borrow(),
182 );
183
184 // gglwe_{s1}(s0) (x) gglwe_{s2}(s1) = gglwe_{s2}(s0)
185 ct_gglwe_s0s2.keyswitch(&module, &ct_gglwe_s0s1, &ct_gglwe_s1s2, scratch.borrow());
186
187 let mut ct_glwe_dft: GLWECiphertextFourier<Vec<u8>, FFT64> =
188 GLWECiphertextFourier::alloc(&module, basek, k_ksk, rank_out_s1s2);
189 let mut pt: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::alloc(&module, basek, k_ksk);
190
191 (0..ct_gglwe_s0s2.rank_in()).for_each(|col_i| {
192 (0..ct_gglwe_s0s2.rows()).for_each(|row_i| {
193 ct_gglwe_s0s2.get_row(&module, row_i, col_i, &mut ct_glwe_dft);
194 ct_glwe_dft.decrypt(&module, &mut pt, &sk2, scratch.borrow());
195 module.vec_znx_sub_scalar_inplace(&mut pt.data, 0, row_i, &sk0.data, col_i);
196
197 let noise_have: f64 = pt.data.std(0, basek).log2();
198 let noise_want: f64 = log2_std_noise_gglwe_product(
199 module.n() as f64,
200 basek,
201 0.5,
202 0.5,
203 0f64,
204 sigma * sigma,
205 0f64,
206 rank_out_s0s1 as f64,
207 k_ksk,
208 k_ksk,
209 );
210
211 assert!(
212 (noise_have - noise_want).abs() <= 0.1,
213 "{} {}",
214 noise_have,
215 noise_want
216 );
217 });
218 });
219}
220
221fn test_key_switch_inplace(log_n: usize, basek: usize, k_ksk: usize, sigma: f64, rank_in_s0s1: usize, rank_out_s0s1: usize) {
222 let module: Module<FFT64> = Module::<FFT64>::new(1 << log_n);
223 let rows: usize = (k_ksk + basek - 1) / basek;
224
225 let mut ct_gglwe_s0s1: GLWESwitchingKey<Vec<u8>, FFT64> =
226 GLWESwitchingKey::alloc(&module, basek, k_ksk, rows, rank_in_s0s1, rank_out_s0s1);
227 let mut ct_gglwe_s1s2: GLWESwitchingKey<Vec<u8>, FFT64> =
228 GLWESwitchingKey::alloc(&module, basek, k_ksk, rows, rank_out_s0s1, rank_out_s0s1);
229
230 let mut source_xs: Source = Source::new([0u8; 32]);
231 let mut source_xe: Source = Source::new([0u8; 32]);
232 let mut source_xa: Source = Source::new([0u8; 32]);
233
234 let mut scratch: ScratchOwned = ScratchOwned::new(
235 GLWESwitchingKey::encrypt_sk_scratch_space(&module, basek, k_ksk, rank_out_s0s1)
236 | GLWECiphertextFourier::decrypt_scratch_space(&module, basek, k_ksk)
237 | GLWESwitchingKey::keyswitch_inplace_scratch_space(
238 &module,
239 basek,
240 ct_gglwe_s0s1.k(),
241 ct_gglwe_s0s1.rank(),
242 ct_gglwe_s1s2.k(),
243 ),
244 );
245
246 let mut sk0: GLWESecret<Vec<u8>, FFT64> = GLWESecret::alloc(&module, rank_in_s0s1);
247 sk0.fill_ternary_prob(&module, 0.5, &mut source_xs);
248
249 let mut sk1: GLWESecret<Vec<u8>, FFT64> = GLWESecret::alloc(&module, rank_out_s0s1);
250 sk1.fill_ternary_prob(&module, 0.5, &mut source_xs);
251
252 let mut sk2: GLWESecret<Vec<u8>, FFT64> = GLWESecret::alloc(&module, rank_out_s0s1);
253 sk2.fill_ternary_prob(&module, 0.5, &mut source_xs);
254
255 // gglwe_{s1}(s0) = s0 -> s1
256 ct_gglwe_s0s1.generate_from_sk(
257 &module,
258 &sk0,
259 &sk1,
260 &mut source_xa,
261 &mut source_xe,
262 sigma,
263 scratch.borrow(),
264 );
265
266 // gglwe_{s2}(s1) -> s1 -> s2
267 ct_gglwe_s1s2.generate_from_sk(
268 &module,
269 &sk1,
270 &sk2,
271 &mut source_xa,
272 &mut source_xe,
273 sigma,
274 scratch.borrow(),
275 );
276
277 // gglwe_{s1}(s0) (x) gglwe_{s2}(s1) = gglwe_{s2}(s0)
278 ct_gglwe_s0s1.keyswitch_inplace(&module, &ct_gglwe_s1s2, scratch.borrow());
279
280 let ct_gglwe_s0s2: GLWESwitchingKey<Vec<u8>, FFT64> = ct_gglwe_s0s1;
281
282 let mut ct_glwe_dft: GLWECiphertextFourier<Vec<u8>, FFT64> =
283 GLWECiphertextFourier::alloc(&module, basek, k_ksk, rank_out_s0s1);
284 let mut pt: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::alloc(&module, basek, k_ksk);
285
286 (0..ct_gglwe_s0s2.rank_in()).for_each(|col_i| {
287 (0..ct_gglwe_s0s2.rows()).for_each(|row_i| {
288 ct_gglwe_s0s2.get_row(&module, row_i, col_i, &mut ct_glwe_dft);
289 ct_glwe_dft.decrypt(&module, &mut pt, &sk2, scratch.borrow());
290 module.vec_znx_sub_scalar_inplace(&mut pt.data, 0, row_i, &sk0.data, col_i);
291
292 let noise_have: f64 = pt.data.std(0, basek).log2();
293 let noise_want: f64 = log2_std_noise_gglwe_product(
294 module.n() as f64,
295 basek,
296 0.5,
297 0.5,
298 0f64,
299 sigma * sigma,
300 0f64,
301 rank_out_s0s1 as f64,
302 k_ksk,
303 k_ksk,
304 );
305
306 assert!(
307 (noise_have - noise_want).abs() <= 0.1,
308 "{} {}",
309 noise_have,
310 noise_want
311 );
312 });
313 });
314}
315
316fn test_external_product(log_n: usize, basek: usize, k: usize, sigma: f64, rank_in: usize, rank_out: usize) {
317 let module: Module<FFT64> = Module::<FFT64>::new(1 << log_n);
318
319 let rows: usize = (k + basek - 1) / basek;
320
321 let mut ct_gglwe_in: GLWESwitchingKey<Vec<u8>, FFT64> = GLWESwitchingKey::alloc(&module, basek, k, rows, rank_in, rank_out);
322 let mut ct_gglwe_out: GLWESwitchingKey<Vec<u8>, FFT64> = GLWESwitchingKey::alloc(&module, basek, k, rows, rank_in, rank_out);
323 let mut ct_rgsw: GGSWCiphertext<Vec<u8>, FFT64> = GGSWCiphertext::alloc(&module, basek, k, rows, rank_out);
324
325 let mut pt_rgsw: ScalarZnx<Vec<u8>> = module.new_scalar_znx(1);
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 let mut scratch: ScratchOwned = ScratchOwned::new(
332 GLWESwitchingKey::encrypt_sk_scratch_space(&module, basek, k, rank_out)
333 | GLWECiphertextFourier::decrypt_scratch_space(&module, basek, k)
334 | GLWESwitchingKey::external_product_scratch_space(
335 &module,
336 basek,
337 ct_gglwe_out.k(),
338 ct_gglwe_in.k(),
339 ct_rgsw.k(),
340 rank_out,
341 )
342 | GGSWCiphertext::encrypt_sk_scratch_space(&module, basek, k, rank_out),
343 );
344
345 let r: usize = 1;
346
347 pt_rgsw.to_mut().raw_mut()[r] = 1; // X^{r}
348
349 let mut sk_in: GLWESecret<Vec<u8>, FFT64> = GLWESecret::alloc(&module, rank_in);
350 sk_in.fill_ternary_prob(&module, 0.5, &mut source_xs);
351
352 let mut sk_out: GLWESecret<Vec<u8>, FFT64> = GLWESecret::alloc(&module, rank_out);
353 sk_out.fill_ternary_prob(&module, 0.5, &mut source_xs);
354
355 // gglwe_{s1}(s0) = s0 -> s1
356 ct_gglwe_in.generate_from_sk(
357 &module,
358 &sk_in,
359 &sk_out,
360 &mut source_xa,
361 &mut source_xe,
362 sigma,
363 scratch.borrow(),
364 );
365
366 ct_rgsw.encrypt_sk(
367 &module,
368 &pt_rgsw,
369 &sk_out,
370 &mut source_xa,
371 &mut source_xe,
372 sigma,
373 scratch.borrow(),
374 );
375
376 // gglwe_(m) (x) RGSW_(X^k) = gglwe_(m * X^k)
377 ct_gglwe_out.external_product(&module, &ct_gglwe_in, &ct_rgsw, scratch.borrow());
378
379 scratch = ScratchOwned::new(
380 GLWESwitchingKey::encrypt_sk_scratch_space(&module, basek, k, rank_out)
381 | GLWECiphertextFourier::decrypt_scratch_space(&module, basek, k)
382 | GLWESwitchingKey::external_product_scratch_space(
383 &module,
384 basek,
385 ct_gglwe_out.k(),
386 ct_gglwe_in.k(),
387 ct_rgsw.k(),
388 rank_out,
389 )
390 | GGSWCiphertext::encrypt_sk_scratch_space(&module, basek, k, rank_out),
391 );
392
393 let mut ct_glwe_dft: GLWECiphertextFourier<Vec<u8>, FFT64> = GLWECiphertextFourier::alloc(&module, basek, k, rank_out);
394 let mut pt: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::alloc(&module, basek, k);
395
396 (0..rank_in).for_each(|i| {
397 module.vec_znx_rotate_inplace(r as i64, &mut sk_in.data, i); // * X^{r}
398 });
399
400 (0..rank_in).for_each(|col_i| {
401 (0..ct_gglwe_out.rows()).for_each(|row_i| {
402 ct_gglwe_out.get_row(&module, row_i, col_i, &mut ct_glwe_dft);
403 ct_glwe_dft.decrypt(&module, &mut pt, &sk_out, scratch.borrow());
404 module.vec_znx_sub_scalar_inplace(&mut pt.data, 0, row_i, &sk_in.data, col_i);
405
406 let noise_have: f64 = pt.data.std(0, basek).log2();
407
408 let var_gct_err_lhs: f64 = sigma * sigma;
409 let var_gct_err_rhs: f64 = 0f64;
410
411 let var_msg: f64 = 1f64 / module.n() as f64; // X^{k}
412 let var_a0_err: f64 = sigma * sigma;
413 let var_a1_err: f64 = 1f64 / 12f64;
414
415 let noise_want: f64 = noise_ggsw_product(
416 module.n() as f64,
417 basek,
418 0.5,
419 var_msg,
420 var_a0_err,
421 var_a1_err,
422 var_gct_err_lhs,
423 var_gct_err_rhs,
424 rank_out as f64,
425 k,
426 k,
427 );
428
429 assert!(
430 (noise_have - noise_want).abs() <= 0.1,
431 "{} {}",
432 noise_have,
433 noise_want
434 );
435 });
436 });
437}
438
439fn test_external_product_inplace(log_n: usize, basek: usize, k: usize, sigma: f64, rank_in: usize, rank_out: usize) {
440 let module: Module<FFT64> = Module::<FFT64>::new(1 << log_n);
441
442 let rows: usize = (k + basek - 1) / basek;
443
444 let mut ct_gglwe: GLWESwitchingKey<Vec<u8>, FFT64> = GLWESwitchingKey::alloc(&module, basek, k, rows, rank_in, rank_out);
445 let mut ct_rgsw: GGSWCiphertext<Vec<u8>, FFT64> = GGSWCiphertext::alloc(&module, basek, k, rows, rank_out);
446
447 let mut pt_rgsw: ScalarZnx<Vec<u8>> = module.new_scalar_znx(1);
448
449 let mut source_xs: Source = Source::new([0u8; 32]);
450 let mut source_xe: Source = Source::new([0u8; 32]);
451 let mut source_xa: Source = Source::new([0u8; 32]);
452
453 let mut scratch: ScratchOwned = ScratchOwned::new(
454 GLWESwitchingKey::encrypt_sk_scratch_space(&module, basek, k, rank_out)
455 | GLWECiphertextFourier::decrypt_scratch_space(&module, basek, k)
456 | GLWESwitchingKey::external_product_inplace_scratch_space(&module, basek, k, k, rank_out)
457 | GGSWCiphertext::encrypt_sk_scratch_space(&module, basek, k, rank_out),
458 );
459
460 let r: usize = 1;
461
462 pt_rgsw.to_mut().raw_mut()[r] = 1; // X^{r}
463
464 let mut sk_in: GLWESecret<Vec<u8>, FFT64> = GLWESecret::alloc(&module, rank_in);
465 sk_in.fill_ternary_prob(&module, 0.5, &mut source_xs);
466
467 let mut sk_out: GLWESecret<Vec<u8>, FFT64> = GLWESecret::alloc(&module, rank_out);
468 sk_out.fill_ternary_prob(&module, 0.5, &mut source_xs);
469
470 // gglwe_{s1}(s0) = s0 -> s1
471 ct_gglwe.generate_from_sk(
472 &module,
473 &sk_in,
474 &sk_out,
475 &mut source_xa,
476 &mut source_xe,
477 sigma,
478 scratch.borrow(),
479 );
480
481 ct_rgsw.encrypt_sk(
482 &module,
483 &pt_rgsw,
484 &sk_out,
485 &mut source_xa,
486 &mut source_xe,
487 sigma,
488 scratch.borrow(),
489 );
490
491 // gglwe_(m) (x) RGSW_(X^k) = gglwe_(m * X^k)
492 ct_gglwe.external_product_inplace(&module, &ct_rgsw, scratch.borrow());
493
494 let mut ct_glwe_dft: GLWECiphertextFourier<Vec<u8>, FFT64> = GLWECiphertextFourier::alloc(&module, basek, k, rank_out);
495 let mut pt: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::alloc(&module, basek, k);
496
497 (0..rank_in).for_each(|i| {
498 module.vec_znx_rotate_inplace(r as i64, &mut sk_in.data, i); // * X^{r}
499 });
500
501 (0..rank_in).for_each(|col_i| {
502 (0..ct_gglwe.rows()).for_each(|row_i| {
503 ct_gglwe.get_row(&module, row_i, col_i, &mut ct_glwe_dft);
504 ct_glwe_dft.decrypt(&module, &mut pt, &sk_out, scratch.borrow());
505 module.vec_znx_sub_scalar_inplace(&mut pt.data, 0, row_i, &sk_in.data, col_i);
506
507 let noise_have: f64 = pt.data.std(0, basek).log2();
508
509 let var_gct_err_lhs: f64 = sigma * sigma;
510 let var_gct_err_rhs: f64 = 0f64;
511
512 let var_msg: f64 = 1f64 / module.n() as f64; // X^{k}
513 let var_a0_err: f64 = sigma * sigma;
514 let var_a1_err: f64 = 1f64 / 12f64;
515
516 let noise_want: f64 = noise_ggsw_product(
517 module.n() as f64,
518 basek,
519 0.5,
520 var_msg,
521 var_a0_err,
522 var_a1_err,
523 var_gct_err_lhs,
524 var_gct_err_rhs,
525 rank_out as f64,
526 k,
527 k,
528 );
529
530 assert!(
531 (noise_have - noise_want).abs() <= 0.1,
532 "{} {}",
533 noise_have,
534 noise_want
535 );
536 });
537 });
538}
539
540pub(crate) fn var_noise_gglwe_product(
541 n: f64,
542 basek: usize,
543 var_xs: f64,
544 var_msg: f64,
545 var_a_err: f64,
546 var_gct_err_lhs: f64,
547 var_gct_err_rhs: f64,
548 rank_in: f64,
549 a_logq: usize,
550 b_logq: usize,
551) -> f64 {
552 let a_logq: usize = a_logq.min(b_logq);
553 let a_cols: usize = (a_logq + basek - 1) / basek;
554
555 let b_scale = 2.0f64.powi(b_logq as i32);
556 let a_scale: f64 = 2.0f64.powi((b_logq - a_logq) as i32);
557
558 let base: f64 = (1 << (basek)) as f64;
559 let var_base: f64 = base * base / 12f64;
560
561 // lhs = a_cols * n * (var_base * var_gct_err_lhs + var_e_a * var_msg * p^2)
562 // rhs = a_cols * n * var_base * var_gct_err_rhs * var_xs
563 let mut noise: f64 = (a_cols as f64) * n * var_base * (var_gct_err_lhs + var_xs * var_gct_err_rhs);
564 noise += var_msg * var_a_err * a_scale * a_scale * n;
565 noise *= rank_in;
566 noise /= b_scale * b_scale;
567 noise
568}
569
570pub(crate) fn log2_std_noise_gglwe_product(
571 n: f64,
572 basek: usize,
573 var_xs: f64,
574 var_msg: f64,
575 var_a_err: f64,
576 var_gct_err_lhs: f64,
577 var_gct_err_rhs: f64,
578 rank_in: f64,
579 a_logq: usize,
580 b_logq: usize,
581) -> f64 {
582 let mut noise: f64 = var_noise_gglwe_product(
583 n,
584 basek,
585 var_xs,
586 var_msg,
587 var_a_err,
588 var_gct_err_lhs,
589 var_gct_err_rhs,
590 rank_in,
591 a_logq,
592 b_logq,
593 );
594 noise = noise.sqrt();
595 noise.log2().min(-1.0) // max noise is [-2^{-1}, 2^{-1}]
596}