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> = GLWECiphertextFourier::alloc(&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::alloc(&module, basek, k_ksk, rows, rank_in_s0s1, rank_out_s0s1);
140 let mut ct_gglwe_s1s2: GLWESwitchingKey<Vec<u8>, FFT64> =
141 GLWESwitchingKey::alloc(&module, basek, k_ksk, rows, rank_out_s0s1, rank_out_s1s2);
142 let mut ct_gglwe_s0s2: GLWESwitchingKey<Vec<u8>, FFT64> =
143 GLWESwitchingKey::alloc(&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::alloc(&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::alloc(&module, rank_in_s0s1);
166 sk0_dft.dft(&module, &sk0);
167
168 let mut sk1: SecretKey<Vec<u8>> = SecretKey::alloc(&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::alloc(&module, rank_out_s0s1);
172 sk1_dft.dft(&module, &sk1);
173
174 let mut sk2: SecretKey<Vec<u8>> = SecretKey::alloc(&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::alloc(&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::alloc(&module, basek, k_ksk, rank_out_s1s2);
206 let mut pt: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::alloc(&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 = log2_std_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::alloc(&module, basek, k_ksk, rows, rank_in_s0s1, rank_out_s0s1);
244 let mut ct_gglwe_s1s2: GLWESwitchingKey<Vec<u8>, FFT64> =
245 GLWESwitchingKey::alloc(&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::alloc(&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::alloc(&module, rank_in_s0s1);
266 sk0_dft.dft(&module, &sk0);
267
268 let mut sk1: SecretKey<Vec<u8>> = SecretKey::alloc(&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::alloc(&module, rank_out_s0s1);
272 sk1_dft.dft(&module, &sk1);
273
274 let mut sk2: SecretKey<Vec<u8>> = SecretKey::alloc(&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::alloc(&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::alloc(&module, basek, k_ksk, rank_out_s0s1);
308 let mut pt: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::alloc(&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 = log2_std_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::alloc(&module, basek, k, rows, rank_in, rank_out);
346 let mut ct_gglwe_out: GLWESwitchingKey<Vec<u8>, FFT64> = GLWESwitchingKey::alloc(&module, basek, k, rows, rank_in, rank_out);
347 let mut ct_rgsw: GGSWCiphertext<Vec<u8>, FFT64> = GGSWCiphertext::alloc(&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::alloc(&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::alloc(&module, rank_in);
376 sk_in_dft.dft(&module, &sk_in);
377
378 let mut sk_out: SecretKey<Vec<u8>> = SecretKey::alloc(&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::alloc(&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 scratch = ScratchOwned::new(
409 GLWESwitchingKey::encrypt_sk_scratch_space(&module, rank_out, ct_gglwe_in.size())
410 | GLWECiphertextFourier::decrypt_scratch_space(&module, ct_gglwe_out.size())
411 | GLWESwitchingKey::external_product_scratch_space(
412 &module,
413 ct_gglwe_out.size(),
414 ct_gglwe_in.size(),
415 ct_rgsw.size(),
416 rank_out,
417 )
418 | GGSWCiphertext::encrypt_sk_scratch_space(&module, rank_out, ct_rgsw.size()),
419 );
420
421 let mut ct_glwe_dft: GLWECiphertextFourier<Vec<u8>, FFT64> = GLWECiphertextFourier::alloc(&module, basek, k, rank_out);
422 let mut pt: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::alloc(&module, basek, k);
423
424 (0..rank_in).for_each(|i| {
425 module.vec_znx_rotate_inplace(r as i64, &mut sk_in.data, i); // * X^{r}
426 });
427
428 (0..rank_in).for_each(|col_i| {
429 (0..ct_gglwe_out.rows()).for_each(|row_i| {
430 ct_gglwe_out.get_row(&module, row_i, col_i, &mut ct_glwe_dft);
431 ct_glwe_dft.decrypt(&module, &mut pt, &sk_out_dft, scratch.borrow());
432 module.vec_znx_sub_scalar_inplace(&mut pt, 0, row_i, &sk_in, col_i);
433
434 let noise_have: f64 = pt.data.std(0, basek).log2();
435
436 let var_gct_err_lhs: f64 = sigma * sigma;
437 let var_gct_err_rhs: f64 = 0f64;
438
439 let var_msg: f64 = 1f64 / module.n() as f64; // X^{k}
440 let var_a0_err: f64 = sigma * sigma;
441 let var_a1_err: f64 = 1f64 / 12f64;
442
443 let noise_want: f64 = noise_ggsw_product(
444 module.n() as f64,
445 basek,
446 0.5,
447 var_msg,
448 var_a0_err,
449 var_a1_err,
450 var_gct_err_lhs,
451 var_gct_err_rhs,
452 rank_out as f64,
453 k,
454 k,
455 );
456
457 assert!(
458 (noise_have - noise_want).abs() <= 0.1,
459 "{} {}",
460 noise_have,
461 noise_want
462 );
463 });
464 });
465}
466
467fn test_external_product_inplace(log_n: usize, basek: usize, k: usize, sigma: f64, rank_in: usize, rank_out: usize) {
468 let module: Module<FFT64> = Module::<FFT64>::new(1 << log_n);
469
470 let rows: usize = (k + basek - 1) / basek;
471
472 let mut ct_gglwe: GLWESwitchingKey<Vec<u8>, FFT64> = GLWESwitchingKey::alloc(&module, basek, k, rows, rank_in, rank_out);
473 let mut ct_rgsw: GGSWCiphertext<Vec<u8>, FFT64> = GGSWCiphertext::alloc(&module, basek, k, rows, rank_out);
474
475 let mut pt_rgsw: ScalarZnx<Vec<u8>> = module.new_scalar_znx(1);
476
477 let mut source_xs: Source = Source::new([0u8; 32]);
478 let mut source_xe: Source = Source::new([0u8; 32]);
479 let mut source_xa: Source = Source::new([0u8; 32]);
480
481 let mut scratch: ScratchOwned = ScratchOwned::new(
482 GLWESwitchingKey::encrypt_sk_scratch_space(&module, rank_out, ct_gglwe.size())
483 | GLWECiphertextFourier::decrypt_scratch_space(&module, ct_gglwe.size())
484 | GLWESwitchingKey::external_product_inplace_scratch_space(&module, ct_gglwe.size(), ct_rgsw.size(), rank_out)
485 | GGSWCiphertext::encrypt_sk_scratch_space(&module, rank_out, ct_rgsw.size()),
486 );
487
488 let r: usize = 1;
489
490 pt_rgsw.to_mut().raw_mut()[r] = 1; // X^{r}
491
492 let mut sk_in: SecretKey<Vec<u8>> = SecretKey::alloc(&module, rank_in);
493 sk_in.fill_ternary_prob(0.5, &mut source_xs);
494
495 let mut sk_in_dft: SecretKeyFourier<Vec<u8>, FFT64> = SecretKeyFourier::alloc(&module, rank_in);
496 sk_in_dft.dft(&module, &sk_in);
497
498 let mut sk_out: SecretKey<Vec<u8>> = SecretKey::alloc(&module, rank_out);
499 sk_out.fill_ternary_prob(0.5, &mut source_xs);
500
501 let mut sk_out_dft: SecretKeyFourier<Vec<u8>, FFT64> = SecretKeyFourier::alloc(&module, rank_out);
502 sk_out_dft.dft(&module, &sk_out);
503
504 // gglwe_{s1}(s0) = s0 -> s1
505 ct_gglwe.encrypt_sk(
506 &module,
507 &sk_in,
508 &sk_out_dft,
509 &mut source_xa,
510 &mut source_xe,
511 sigma,
512 scratch.borrow(),
513 );
514
515 ct_rgsw.encrypt_sk(
516 &module,
517 &pt_rgsw,
518 &sk_out_dft,
519 &mut source_xa,
520 &mut source_xe,
521 sigma,
522 scratch.borrow(),
523 );
524
525 // gglwe_(m) (x) RGSW_(X^k) = gglwe_(m * X^k)
526 ct_gglwe.external_product_inplace(&module, &ct_rgsw, scratch.borrow());
527
528 let mut ct_glwe_dft: GLWECiphertextFourier<Vec<u8>, FFT64> = GLWECiphertextFourier::alloc(&module, basek, k, rank_out);
529 let mut pt: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::alloc(&module, basek, k);
530
531 (0..rank_in).for_each(|i| {
532 module.vec_znx_rotate_inplace(r as i64, &mut sk_in.data, i); // * X^{r}
533 });
534
535 (0..rank_in).for_each(|col_i| {
536 (0..ct_gglwe.rows()).for_each(|row_i| {
537 ct_gglwe.get_row(&module, row_i, col_i, &mut ct_glwe_dft);
538 ct_glwe_dft.decrypt(&module, &mut pt, &sk_out_dft, scratch.borrow());
539 module.vec_znx_sub_scalar_inplace(&mut pt, 0, row_i, &sk_in, col_i);
540
541 let noise_have: f64 = pt.data.std(0, basek).log2();
542
543 let var_gct_err_lhs: f64 = sigma * sigma;
544 let var_gct_err_rhs: f64 = 0f64;
545
546 let var_msg: f64 = 1f64 / module.n() as f64; // X^{k}
547 let var_a0_err: f64 = sigma * sigma;
548 let var_a1_err: f64 = 1f64 / 12f64;
549
550 let noise_want: f64 = noise_ggsw_product(
551 module.n() as f64,
552 basek,
553 0.5,
554 var_msg,
555 var_a0_err,
556 var_a1_err,
557 var_gct_err_lhs,
558 var_gct_err_rhs,
559 rank_out as f64,
560 k,
561 k,
562 );
563
564 assert!(
565 (noise_have - noise_want).abs() <= 0.1,
566 "{} {}",
567 noise_have,
568 noise_want
569 );
570 });
571 });
572}
573
574pub(crate) fn var_noise_gglwe_product(
575 n: f64,
576 basek: usize,
577 var_xs: f64,
578 var_msg: f64,
579 var_a_err: f64,
580 var_gct_err_lhs: f64,
581 var_gct_err_rhs: f64,
582 rank_in: f64,
583 a_logq: usize,
584 b_logq: usize,
585) -> f64 {
586 let a_logq: usize = a_logq.min(b_logq);
587 let a_cols: usize = (a_logq + basek - 1) / basek;
588
589 let b_scale = 2.0f64.powi(b_logq as i32);
590 let a_scale: f64 = 2.0f64.powi((b_logq - a_logq) as i32);
591
592 let base: f64 = (1 << (basek)) as f64;
593 let var_base: f64 = base * base / 12f64;
594
595 // lhs = a_cols * n * (var_base * var_gct_err_lhs + var_e_a * var_msg * p^2)
596 // rhs = a_cols * n * var_base * var_gct_err_rhs * var_xs
597 let mut noise: f64 = (a_cols as f64) * n * var_base * (var_gct_err_lhs + var_xs * var_gct_err_rhs);
598 noise += var_msg * var_a_err * a_scale * a_scale * n;
599 noise *= rank_in;
600 noise /= b_scale * b_scale;
601 noise
602}
603
604pub(crate) fn log2_std_noise_gglwe_product(
605 n: f64,
606 basek: usize,
607 var_xs: f64,
608 var_msg: f64,
609 var_a_err: f64,
610 var_gct_err_lhs: f64,
611 var_gct_err_rhs: f64,
612 rank_in: f64,
613 a_logq: usize,
614 b_logq: usize,
615) -> f64 {
616 let mut noise: f64 = var_noise_gglwe_product(
617 n,
618 basek,
619 var_xs,
620 var_msg,
621 var_a_err,
622 var_gct_err_lhs,
623 var_gct_err_rhs,
624 rank_in,
625 a_logq,
626 b_logq,
627 );
628 noise = noise.sqrt();
629 noise.log2().min(-1.0) // max noise is [-2^{-1}, 2^{-1}]
630}