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