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 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::alloc(&module, basek, k_ksk, rows, rank_in, rank_out);
80 let mut pt: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::alloc(&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::alloc(&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::alloc(&module, rank_in);
95 sk_in_dft.dft(&module, &sk_in);
96
97 let mut sk_out: SecretKey<Vec<u8>> = SecretKey::alloc(&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::alloc(&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> =
114 GLWECiphertextFourier::alloc(&module, basek, k_ksk, rank_out);
115
116 (0..ksk.rank_in()).for_each(|col_i| {
117 (0..ksk.rows()).for_each(|row_i| {
118 ksk.get_row(&module, row_i, col_i, &mut ct_glwe_fourier);
119 ct_glwe_fourier.decrypt(&module, &mut pt, &sk_out_dft, scratch.borrow());
120 module.vec_znx_sub_scalar_inplace(&mut pt, 0, row_i, &sk_in, col_i);
121 let std_pt: f64 = pt.data.std(0, basek) * (k_ksk as f64).exp2();
122 assert!((sigma - std_pt).abs() <= 0.2, "{} {}", sigma, std_pt);
123 });
124 });
125}
126
127fn test_key_switch(
128 log_n: usize,
129 basek: usize,
130 k_ksk: usize,
131 sigma: f64,
132 rank_in_s0s1: usize,
133 rank_out_s0s1: usize,
134 rank_out_s1s2: usize,
135) {
136 let module: Module<FFT64> = Module::<FFT64>::new(1 << log_n);
137 let rows = (k_ksk + basek - 1) / basek;
138
139 let mut ct_gglwe_s0s1: GLWESwitchingKey<Vec<u8>, FFT64> =
140 GLWESwitchingKey::alloc(&module, basek, k_ksk, rows, rank_in_s0s1, rank_out_s0s1);
141 let mut ct_gglwe_s1s2: GLWESwitchingKey<Vec<u8>, FFT64> =
142 GLWESwitchingKey::alloc(&module, basek, k_ksk, rows, rank_out_s0s1, rank_out_s1s2);
143 let mut ct_gglwe_s0s2: GLWESwitchingKey<Vec<u8>, FFT64> =
144 GLWESwitchingKey::alloc(&module, basek, k_ksk, rows, rank_in_s0s1, rank_out_s1s2);
145
146 let mut source_xs: Source = Source::new([0u8; 32]);
147 let mut source_xe: Source = Source::new([0u8; 32]);
148 let mut source_xa: Source = Source::new([0u8; 32]);
149
150 let mut scratch: ScratchOwned = ScratchOwned::new(
151 GLWESwitchingKey::encrypt_sk_scratch_space(&module, rank_in_s0s1 | rank_out_s0s1, ct_gglwe_s0s1.size())
152 | GLWECiphertextFourier::decrypt_scratch_space(&module, ct_gglwe_s0s2.size())
153 | GLWESwitchingKey::keyswitch_scratch_space(
154 &module,
155 ct_gglwe_s0s2.size(),
156 ct_gglwe_s0s2.rank(),
157 ct_gglwe_s0s1.size(),
158 ct_gglwe_s0s1.rank(),
159 ct_gglwe_s1s2.size(),
160 ),
161 );
162
163 let mut sk0: SecretKey<Vec<u8>> = SecretKey::alloc(&module, rank_in_s0s1);
164 sk0.fill_ternary_prob(0.5, &mut source_xs);
165
166 let mut sk0_dft: SecretKeyFourier<Vec<u8>, FFT64> = SecretKeyFourier::alloc(&module, rank_in_s0s1);
167 sk0_dft.dft(&module, &sk0);
168
169 let mut sk1: SecretKey<Vec<u8>> = SecretKey::alloc(&module, rank_out_s0s1);
170 sk1.fill_ternary_prob(0.5, &mut source_xs);
171
172 let mut sk1_dft: SecretKeyFourier<Vec<u8>, FFT64> = SecretKeyFourier::alloc(&module, rank_out_s0s1);
173 sk1_dft.dft(&module, &sk1);
174
175 let mut sk2: SecretKey<Vec<u8>> = SecretKey::alloc(&module, rank_out_s1s2);
176 sk2.fill_ternary_prob(0.5, &mut source_xs);
177
178 let mut sk2_dft: SecretKeyFourier<Vec<u8>, FFT64> = SecretKeyFourier::alloc(&module, rank_out_s1s2);
179 sk2_dft.dft(&module, &sk2);
180
181 // gglwe_{s1}(s0) = s0 -> s1
182 ct_gglwe_s0s1.encrypt_sk(
183 &module,
184 &sk0,
185 &sk1_dft,
186 &mut source_xa,
187 &mut source_xe,
188 sigma,
189 scratch.borrow(),
190 );
191
192 // gglwe_{s2}(s1) -> s1 -> s2
193 ct_gglwe_s1s2.encrypt_sk(
194 &module,
195 &sk1,
196 &sk2_dft,
197 &mut source_xa,
198 &mut source_xe,
199 sigma,
200 scratch.borrow(),
201 );
202
203 // gglwe_{s1}(s0) (x) gglwe_{s2}(s1) = gglwe_{s2}(s0)
204 ct_gglwe_s0s2.keyswitch(&module, &ct_gglwe_s0s1, &ct_gglwe_s1s2, scratch.borrow());
205
206 let mut ct_glwe_dft: GLWECiphertextFourier<Vec<u8>, FFT64> =
207 GLWECiphertextFourier::alloc(&module, basek, k_ksk, rank_out_s1s2);
208 let mut pt: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::alloc(&module, basek, k_ksk);
209
210 (0..ct_gglwe_s0s2.rank_in()).for_each(|col_i| {
211 (0..ct_gglwe_s0s2.rows()).for_each(|row_i| {
212 ct_gglwe_s0s2.get_row(&module, row_i, col_i, &mut ct_glwe_dft);
213 ct_glwe_dft.decrypt(&module, &mut pt, &sk2_dft, scratch.borrow());
214 module.vec_znx_sub_scalar_inplace(&mut pt, 0, row_i, &sk0, col_i);
215
216 let noise_have: f64 = pt.data.std(0, basek).log2();
217 let noise_want: f64 = log2_std_noise_gglwe_product(
218 module.n() as f64,
219 basek,
220 0.5,
221 0.5,
222 0f64,
223 sigma * sigma,
224 0f64,
225 rank_out_s0s1 as f64,
226 k_ksk,
227 k_ksk,
228 );
229
230 assert!(
231 (noise_have - noise_want).abs() <= 0.1,
232 "{} {}",
233 noise_have,
234 noise_want
235 );
236 });
237 });
238}
239
240fn test_key_switch_inplace(log_n: usize, basek: usize, k_ksk: usize, sigma: f64, rank_in_s0s1: usize, rank_out_s0s1: usize) {
241 let module: Module<FFT64> = Module::<FFT64>::new(1 << log_n);
242 let rows: usize = (k_ksk + basek - 1) / basek;
243
244 let mut ct_gglwe_s0s1: GLWESwitchingKey<Vec<u8>, FFT64> =
245 GLWESwitchingKey::alloc(&module, basek, k_ksk, rows, rank_in_s0s1, rank_out_s0s1);
246 let mut ct_gglwe_s1s2: GLWESwitchingKey<Vec<u8>, FFT64> =
247 GLWESwitchingKey::alloc(&module, basek, k_ksk, rows, rank_out_s0s1, rank_out_s0s1);
248
249 let mut source_xs: Source = Source::new([0u8; 32]);
250 let mut source_xe: Source = Source::new([0u8; 32]);
251 let mut source_xa: Source = Source::new([0u8; 32]);
252
253 let mut scratch: ScratchOwned = ScratchOwned::new(
254 GLWESwitchingKey::encrypt_sk_scratch_space(&module, rank_out_s0s1, ct_gglwe_s0s1.size())
255 | GLWECiphertextFourier::decrypt_scratch_space(&module, ct_gglwe_s0s1.size())
256 | GLWESwitchingKey::keyswitch_inplace_scratch_space(
257 &module,
258 ct_gglwe_s0s1.size(),
259 ct_gglwe_s0s1.rank(),
260 ct_gglwe_s1s2.size(),
261 ),
262 );
263
264 let mut sk0: SecretKey<Vec<u8>> = SecretKey::alloc(&module, rank_in_s0s1);
265 sk0.fill_ternary_prob(0.5, &mut source_xs);
266
267 let mut sk0_dft: SecretKeyFourier<Vec<u8>, FFT64> = SecretKeyFourier::alloc(&module, rank_in_s0s1);
268 sk0_dft.dft(&module, &sk0);
269
270 let mut sk1: SecretKey<Vec<u8>> = SecretKey::alloc(&module, rank_out_s0s1);
271 sk1.fill_ternary_prob(0.5, &mut source_xs);
272
273 let mut sk1_dft: SecretKeyFourier<Vec<u8>, FFT64> = SecretKeyFourier::alloc(&module, rank_out_s0s1);
274 sk1_dft.dft(&module, &sk1);
275
276 let mut sk2: SecretKey<Vec<u8>> = SecretKey::alloc(&module, rank_out_s0s1);
277 sk2.fill_ternary_prob(0.5, &mut source_xs);
278
279 let mut sk2_dft: SecretKeyFourier<Vec<u8>, FFT64> = SecretKeyFourier::alloc(&module, rank_out_s0s1);
280 sk2_dft.dft(&module, &sk2);
281
282 // gglwe_{s1}(s0) = s0 -> s1
283 ct_gglwe_s0s1.encrypt_sk(
284 &module,
285 &sk0,
286 &sk1_dft,
287 &mut source_xa,
288 &mut source_xe,
289 sigma,
290 scratch.borrow(),
291 );
292
293 // gglwe_{s2}(s1) -> s1 -> s2
294 ct_gglwe_s1s2.encrypt_sk(
295 &module,
296 &sk1,
297 &sk2_dft,
298 &mut source_xa,
299 &mut source_xe,
300 sigma,
301 scratch.borrow(),
302 );
303
304 // gglwe_{s1}(s0) (x) gglwe_{s2}(s1) = gglwe_{s2}(s0)
305 ct_gglwe_s0s1.keyswitch_inplace(&module, &ct_gglwe_s1s2, scratch.borrow());
306
307 let ct_gglwe_s0s2: GLWESwitchingKey<Vec<u8>, FFT64> = ct_gglwe_s0s1;
308
309 let mut ct_glwe_dft: GLWECiphertextFourier<Vec<u8>, FFT64> =
310 GLWECiphertextFourier::alloc(&module, basek, k_ksk, rank_out_s0s1);
311 let mut pt: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::alloc(&module, basek, k_ksk);
312
313 (0..ct_gglwe_s0s2.rank_in()).for_each(|col_i| {
314 (0..ct_gglwe_s0s2.rows()).for_each(|row_i| {
315 ct_gglwe_s0s2.get_row(&module, row_i, col_i, &mut ct_glwe_dft);
316 ct_glwe_dft.decrypt(&module, &mut pt, &sk2_dft, scratch.borrow());
317 module.vec_znx_sub_scalar_inplace(&mut pt, 0, row_i, &sk0, col_i);
318
319 let noise_have: f64 = pt.data.std(0, basek).log2();
320 let noise_want: f64 = log2_std_noise_gglwe_product(
321 module.n() as f64,
322 basek,
323 0.5,
324 0.5,
325 0f64,
326 sigma * sigma,
327 0f64,
328 rank_out_s0s1 as f64,
329 k_ksk,
330 k_ksk,
331 );
332
333 assert!(
334 (noise_have - noise_want).abs() <= 0.1,
335 "{} {}",
336 noise_have,
337 noise_want
338 );
339 });
340 });
341}
342
343fn test_external_product(log_n: usize, basek: usize, k: usize, sigma: f64, rank_in: usize, rank_out: usize) {
344 let module: Module<FFT64> = Module::<FFT64>::new(1 << log_n);
345
346 let rows: usize = (k + basek - 1) / basek;
347
348 let mut ct_gglwe_in: GLWESwitchingKey<Vec<u8>, FFT64> = GLWESwitchingKey::alloc(&module, basek, k, rows, rank_in, rank_out);
349 let mut ct_gglwe_out: GLWESwitchingKey<Vec<u8>, FFT64> = GLWESwitchingKey::alloc(&module, basek, k, rows, rank_in, rank_out);
350 let mut ct_rgsw: GGSWCiphertext<Vec<u8>, FFT64> = GGSWCiphertext::alloc(&module, basek, k, rows, rank_out);
351
352 let mut pt_rgsw: ScalarZnx<Vec<u8>> = module.new_scalar_znx(1);
353
354 let mut source_xs: Source = Source::new([0u8; 32]);
355 let mut source_xe: Source = Source::new([0u8; 32]);
356 let mut source_xa: Source = Source::new([0u8; 32]);
357
358 let mut scratch: ScratchOwned = ScratchOwned::new(
359 GLWESwitchingKey::encrypt_sk_scratch_space(&module, rank_out, ct_gglwe_in.size())
360 | GLWECiphertextFourier::decrypt_scratch_space(&module, ct_gglwe_out.size())
361 | GLWESwitchingKey::external_product_scratch_space(
362 &module,
363 ct_gglwe_out.size(),
364 ct_gglwe_in.size(),
365 ct_rgsw.size(),
366 rank_out,
367 )
368 | GGSWCiphertext::encrypt_sk_scratch_space(&module, rank_out, ct_rgsw.size()),
369 );
370
371 let r: usize = 1;
372
373 pt_rgsw.to_mut().raw_mut()[r] = 1; // X^{r}
374
375 let mut sk_in: SecretKey<Vec<u8>> = SecretKey::alloc(&module, rank_in);
376 sk_in.fill_ternary_prob(0.5, &mut source_xs);
377
378 let mut sk_in_dft: SecretKeyFourier<Vec<u8>, FFT64> = SecretKeyFourier::alloc(&module, rank_in);
379 sk_in_dft.dft(&module, &sk_in);
380
381 let mut sk_out: SecretKey<Vec<u8>> = SecretKey::alloc(&module, rank_out);
382 sk_out.fill_ternary_prob(0.5, &mut source_xs);
383
384 let mut sk_out_dft: SecretKeyFourier<Vec<u8>, FFT64> = SecretKeyFourier::alloc(&module, rank_out);
385 sk_out_dft.dft(&module, &sk_out);
386
387 // gglwe_{s1}(s0) = s0 -> s1
388 ct_gglwe_in.encrypt_sk(
389 &module,
390 &sk_in,
391 &sk_out_dft,
392 &mut source_xa,
393 &mut source_xe,
394 sigma,
395 scratch.borrow(),
396 );
397
398 ct_rgsw.encrypt_sk(
399 &module,
400 &pt_rgsw,
401 &sk_out_dft,
402 &mut source_xa,
403 &mut source_xe,
404 sigma,
405 scratch.borrow(),
406 );
407
408 // gglwe_(m) (x) RGSW_(X^k) = gglwe_(m * X^k)
409 ct_gglwe_out.external_product(&module, &ct_gglwe_in, &ct_rgsw, scratch.borrow());
410
411 scratch = ScratchOwned::new(
412 GLWESwitchingKey::encrypt_sk_scratch_space(&module, rank_out, ct_gglwe_in.size())
413 | GLWECiphertextFourier::decrypt_scratch_space(&module, ct_gglwe_out.size())
414 | GLWESwitchingKey::external_product_scratch_space(
415 &module,
416 ct_gglwe_out.size(),
417 ct_gglwe_in.size(),
418 ct_rgsw.size(),
419 rank_out,
420 )
421 | GGSWCiphertext::encrypt_sk_scratch_space(&module, rank_out, ct_rgsw.size()),
422 );
423
424 let mut ct_glwe_dft: GLWECiphertextFourier<Vec<u8>, FFT64> = GLWECiphertextFourier::alloc(&module, basek, k, rank_out);
425 let mut pt: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::alloc(&module, basek, k);
426
427 (0..rank_in).for_each(|i| {
428 module.vec_znx_rotate_inplace(r as i64, &mut sk_in.data, i); // * X^{r}
429 });
430
431 (0..rank_in).for_each(|col_i| {
432 (0..ct_gglwe_out.rows()).for_each(|row_i| {
433 ct_gglwe_out.get_row(&module, row_i, col_i, &mut ct_glwe_dft);
434 ct_glwe_dft.decrypt(&module, &mut pt, &sk_out_dft, scratch.borrow());
435 module.vec_znx_sub_scalar_inplace(&mut pt, 0, row_i, &sk_in, col_i);
436
437 let noise_have: f64 = pt.data.std(0, basek).log2();
438
439 let var_gct_err_lhs: f64 = sigma * sigma;
440 let var_gct_err_rhs: f64 = 0f64;
441
442 let var_msg: f64 = 1f64 / module.n() as f64; // X^{k}
443 let var_a0_err: f64 = sigma * sigma;
444 let var_a1_err: f64 = 1f64 / 12f64;
445
446 let noise_want: f64 = noise_ggsw_product(
447 module.n() as f64,
448 basek,
449 0.5,
450 var_msg,
451 var_a0_err,
452 var_a1_err,
453 var_gct_err_lhs,
454 var_gct_err_rhs,
455 rank_out as f64,
456 k,
457 k,
458 );
459
460 assert!(
461 (noise_have - noise_want).abs() <= 0.1,
462 "{} {}",
463 noise_have,
464 noise_want
465 );
466 });
467 });
468}
469
470fn test_external_product_inplace(log_n: usize, basek: usize, k: usize, sigma: f64, rank_in: usize, rank_out: usize) {
471 let module: Module<FFT64> = Module::<FFT64>::new(1 << log_n);
472
473 let rows: usize = (k + basek - 1) / basek;
474
475 let mut ct_gglwe: GLWESwitchingKey<Vec<u8>, FFT64> = GLWESwitchingKey::alloc(&module, basek, k, rows, rank_in, rank_out);
476 let mut ct_rgsw: GGSWCiphertext<Vec<u8>, FFT64> = GGSWCiphertext::alloc(&module, basek, k, rows, rank_out);
477
478 let mut pt_rgsw: ScalarZnx<Vec<u8>> = module.new_scalar_znx(1);
479
480 let mut source_xs: Source = Source::new([0u8; 32]);
481 let mut source_xe: Source = Source::new([0u8; 32]);
482 let mut source_xa: Source = Source::new([0u8; 32]);
483
484 let mut scratch: ScratchOwned = ScratchOwned::new(
485 GLWESwitchingKey::encrypt_sk_scratch_space(&module, rank_out, ct_gglwe.size())
486 | GLWECiphertextFourier::decrypt_scratch_space(&module, ct_gglwe.size())
487 | GLWESwitchingKey::external_product_inplace_scratch_space(&module, ct_gglwe.size(), ct_rgsw.size(), rank_out)
488 | GGSWCiphertext::encrypt_sk_scratch_space(&module, rank_out, ct_rgsw.size()),
489 );
490
491 let r: usize = 1;
492
493 pt_rgsw.to_mut().raw_mut()[r] = 1; // X^{r}
494
495 let mut sk_in: SecretKey<Vec<u8>> = SecretKey::alloc(&module, rank_in);
496 sk_in.fill_ternary_prob(0.5, &mut source_xs);
497
498 let mut sk_in_dft: SecretKeyFourier<Vec<u8>, FFT64> = SecretKeyFourier::alloc(&module, rank_in);
499 sk_in_dft.dft(&module, &sk_in);
500
501 let mut sk_out: SecretKey<Vec<u8>> = SecretKey::alloc(&module, rank_out);
502 sk_out.fill_ternary_prob(0.5, &mut source_xs);
503
504 let mut sk_out_dft: SecretKeyFourier<Vec<u8>, FFT64> = SecretKeyFourier::alloc(&module, rank_out);
505 sk_out_dft.dft(&module, &sk_out);
506
507 // gglwe_{s1}(s0) = s0 -> s1
508 ct_gglwe.encrypt_sk(
509 &module,
510 &sk_in,
511 &sk_out_dft,
512 &mut source_xa,
513 &mut source_xe,
514 sigma,
515 scratch.borrow(),
516 );
517
518 ct_rgsw.encrypt_sk(
519 &module,
520 &pt_rgsw,
521 &sk_out_dft,
522 &mut source_xa,
523 &mut source_xe,
524 sigma,
525 scratch.borrow(),
526 );
527
528 // gglwe_(m) (x) RGSW_(X^k) = gglwe_(m * X^k)
529 ct_gglwe.external_product_inplace(&module, &ct_rgsw, scratch.borrow());
530
531 let mut ct_glwe_dft: GLWECiphertextFourier<Vec<u8>, FFT64> = GLWECiphertextFourier::alloc(&module, basek, k, rank_out);
532 let mut pt: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::alloc(&module, basek, k);
533
534 (0..rank_in).for_each(|i| {
535 module.vec_znx_rotate_inplace(r as i64, &mut sk_in.data, i); // * X^{r}
536 });
537
538 (0..rank_in).for_each(|col_i| {
539 (0..ct_gglwe.rows()).for_each(|row_i| {
540 ct_gglwe.get_row(&module, row_i, col_i, &mut ct_glwe_dft);
541 ct_glwe_dft.decrypt(&module, &mut pt, &sk_out_dft, scratch.borrow());
542 module.vec_znx_sub_scalar_inplace(&mut pt, 0, row_i, &sk_in, col_i);
543
544 let noise_have: f64 = pt.data.std(0, basek).log2();
545
546 let var_gct_err_lhs: f64 = sigma * sigma;
547 let var_gct_err_rhs: f64 = 0f64;
548
549 let var_msg: f64 = 1f64 / module.n() as f64; // X^{k}
550 let var_a0_err: f64 = sigma * sigma;
551 let var_a1_err: f64 = 1f64 / 12f64;
552
553 let noise_want: f64 = noise_ggsw_product(
554 module.n() as f64,
555 basek,
556 0.5,
557 var_msg,
558 var_a0_err,
559 var_a1_err,
560 var_gct_err_lhs,
561 var_gct_err_rhs,
562 rank_out as f64,
563 k,
564 k,
565 );
566
567 assert!(
568 (noise_have - noise_want).abs() <= 0.1,
569 "{} {}",
570 noise_have,
571 noise_want
572 );
573 });
574 });
575}
576
577pub(crate) fn var_noise_gglwe_product(
578 n: f64,
579 basek: usize,
580 var_xs: f64,
581 var_msg: f64,
582 var_a_err: f64,
583 var_gct_err_lhs: f64,
584 var_gct_err_rhs: f64,
585 rank_in: f64,
586 a_logq: usize,
587 b_logq: usize,
588) -> f64 {
589 let a_logq: usize = a_logq.min(b_logq);
590 let a_cols: usize = (a_logq + basek - 1) / basek;
591
592 let b_scale = 2.0f64.powi(b_logq as i32);
593 let a_scale: f64 = 2.0f64.powi((b_logq - a_logq) as i32);
594
595 let base: f64 = (1 << (basek)) as f64;
596 let var_base: f64 = base * base / 12f64;
597
598 // lhs = a_cols * n * (var_base * var_gct_err_lhs + var_e_a * var_msg * p^2)
599 // rhs = a_cols * n * var_base * var_gct_err_rhs * var_xs
600 let mut noise: f64 = (a_cols as f64) * n * var_base * (var_gct_err_lhs + var_xs * var_gct_err_rhs);
601 noise += var_msg * var_a_err * a_scale * a_scale * n;
602 noise *= rank_in;
603 noise /= b_scale * b_scale;
604 noise
605}
606
607pub(crate) fn log2_std_noise_gglwe_product(
608 n: f64,
609 basek: usize,
610 var_xs: f64,
611 var_msg: f64,
612 var_a_err: f64,
613 var_gct_err_lhs: f64,
614 var_gct_err_rhs: f64,
615 rank_in: f64,
616 a_logq: usize,
617 b_logq: usize,
618) -> f64 {
619 let mut noise: f64 = var_noise_gglwe_product(
620 n,
621 basek,
622 var_xs,
623 var_msg,
624 var_a_err,
625 var_gct_err_lhs,
626 var_gct_err_rhs,
627 rank_in,
628 a_logq,
629 b_logq,
630 );
631 noise = noise.sqrt();
632 noise.log2().min(-1.0) // max noise is [-2^{-1}, 2^{-1}]
633}