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