poulpy/core/src/test_fft64/ggsw.rs
core/src/test_fft64/ggsw.rsRaw
1use base2k::{
2 FFT64, Module, ScalarZnx, ScalarZnxAlloc, ScalarZnxDftOps, ScratchOwned, Stats, VecZnxBig, VecZnxBigAlloc, VecZnxBigOps,
3 VecZnxDft, VecZnxDftAlloc, VecZnxDftOps, VecZnxOps, VecZnxToMut, ZnxViewMut, ZnxZero,
4};
5use sampling::source::Source;
6
7use crate::{
8 automorphism::AutomorphismKey,
9 elem::{GetRow, Infos},
10 ggsw_ciphertext::GGSWCiphertext,
11 glwe_ciphertext_fourier::GLWECiphertextFourier,
12 glwe_plaintext::GLWEPlaintext,
13 keys::{SecretKey, SecretKeyFourier},
14 keyswitch_key::GLWESwitchingKey,
15 tensor_key::TensorKey,
16};
17
18use super::gglwe::var_noise_gglwe_product;
19
20#[test]
21fn encrypt_sk() {
22 (1..4).for_each(|rank| {
23 println!("test encrypt_sk rank: {}", rank);
24 test_encrypt_sk(11, 8, 54, 3.2, rank);
25 });
26}
27
28#[test]
29fn external_product() {
30 (1..4).for_each(|rank| {
31 println!("test external_product rank: {}", rank);
32 test_external_product(12, 12, 60, rank, 3.2);
33 });
34}
35
36#[test]
37fn external_product_inplace() {
38 (1..4).for_each(|rank| {
39 println!("test external_product rank: {}", rank);
40 test_external_product_inplace(12, 15, 60, rank, 3.2);
41 });
42}
43
44fn test_encrypt_sk(log_n: usize, basek: usize, k_ggsw: usize, sigma: f64, rank: usize) {
45 let module: Module<FFT64> = Module::<FFT64>::new(1 << log_n);
46
47 let rows: usize = (k_ggsw + basek - 1) / basek;
48
49 let mut ct: GGSWCiphertext<Vec<u8>, FFT64> = GGSWCiphertext::new(&module, basek, k_ggsw, rows, rank);
50 let mut pt_have: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::new(&module, basek, k_ggsw);
51 let mut pt_want: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::new(&module, basek, k_ggsw);
52 let mut pt_scalar: ScalarZnx<Vec<u8>> = module.new_scalar_znx(1);
53
54 let mut source_xs: Source = Source::new([0u8; 32]);
55 let mut source_xe: Source = Source::new([0u8; 32]);
56 let mut source_xa: Source = Source::new([0u8; 32]);
57
58 pt_scalar.fill_ternary_hw(0, module.n(), &mut source_xs);
59
60 let mut scratch: ScratchOwned = ScratchOwned::new(
61 GGSWCiphertext::encrypt_sk_scratch_space(&module, rank, ct.size())
62 | GLWECiphertextFourier::decrypt_scratch_space(&module, ct.size()),
63 );
64
65 let mut sk: SecretKey<Vec<u8>> = SecretKey::new(&module, rank);
66 sk.fill_ternary_prob(0.5, &mut source_xs);
67
68 let mut sk_dft: SecretKeyFourier<Vec<u8>, FFT64> = SecretKeyFourier::new(&module, rank);
69 sk_dft.dft(&module, &sk);
70
71 ct.encrypt_sk(
72 &module,
73 &pt_scalar,
74 &sk_dft,
75 &mut source_xa,
76 &mut source_xe,
77 sigma,
78 scratch.borrow(),
79 );
80
81 let mut ct_glwe_fourier: GLWECiphertextFourier<Vec<u8>, FFT64> = GLWECiphertextFourier::new(&module, basek, k_ggsw, rank);
82 let mut pt_dft: VecZnxDft<Vec<u8>, FFT64> = module.new_vec_znx_dft(1, ct.size());
83 let mut pt_big: VecZnxBig<Vec<u8>, FFT64> = module.new_vec_znx_big(1, ct.size());
84
85 (0..ct.rank() + 1).for_each(|col_j| {
86 (0..ct.rows()).for_each(|row_i| {
87 module.vec_znx_add_scalar_inplace(&mut pt_want, 0, row_i, &pt_scalar, 0);
88
89 // mul with sk[col_j-1]
90 if col_j > 0 {
91 module.vec_znx_dft(&mut pt_dft, 0, &pt_want, 0);
92 module.svp_apply_inplace(&mut pt_dft, 0, &sk_dft, col_j - 1);
93 module.vec_znx_idft_tmp_a(&mut pt_big, 0, &mut pt_dft, 0);
94 module.vec_znx_big_normalize(basek, &mut pt_want, 0, &pt_big, 0, scratch.borrow());
95 }
96
97 ct.get_row(&module, row_i, col_j, &mut ct_glwe_fourier);
98
99 ct_glwe_fourier.decrypt(&module, &mut pt_have, &sk_dft, scratch.borrow());
100
101 module.vec_znx_sub_ab_inplace(&mut pt_have, 0, &pt_want, 0);
102
103 let std_pt: f64 = pt_have.data.std(0, basek) * (k_ggsw as f64).exp2();
104 assert!((sigma - std_pt).abs() <= 0.2, "{} {}", sigma, std_pt);
105
106 pt_want.data.zero();
107 });
108 });
109}
110
111#[test]
112fn keyswitch() {
113 (1..4).for_each(|rank| {
114 println!("test keyswitch rank: {}", rank);
115 test_keyswitch(12, 15, 60, rank, 3.2);
116 });
117}
118
119fn test_keyswitch(log_n: usize, basek: usize, k: usize, rank: usize, sigma: f64) {
120 let module: Module<FFT64> = Module::<FFT64>::new(1 << log_n);
121 let rows: usize = (k + basek - 1) / basek;
122
123 let mut ct_in: GGSWCiphertext<Vec<u8>, FFT64> = GGSWCiphertext::new(&module, basek, k, rows, rank);
124 let mut ct_out: GGSWCiphertext<Vec<u8>, FFT64> = GGSWCiphertext::new(&module, basek, k, rows, rank);
125 let mut tsk: TensorKey<Vec<u8>, FFT64> = TensorKey::new(&module, basek, k, rows, rank);
126 let mut ksk: GLWESwitchingKey<Vec<u8>, FFT64> = GLWESwitchingKey::new(&module, basek, k, rows, rank, rank);
127 let mut pt_have: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::new(&module, basek, k);
128 let mut pt_want: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::new(&module, basek, k);
129 let mut pt_scalar: ScalarZnx<Vec<u8>> = module.new_scalar_znx(1);
130
131 let mut source_xs: Source = Source::new([0u8; 32]);
132 let mut source_xe: Source = Source::new([0u8; 32]);
133 let mut source_xa: Source = Source::new([0u8; 32]);
134
135 let mut scratch: ScratchOwned = ScratchOwned::new(
136 GGSWCiphertext::encrypt_sk_scratch_space(&module, rank, ct_in.size())
137 | GLWECiphertextFourier::decrypt_scratch_space(&module, ct_out.size())
138 | GLWESwitchingKey::encrypt_sk_scratch_space(&module, rank, ksk.size())
139 | TensorKey::encrypt_sk_scratch_space(&module, rank, ksk.size())
140 | GGSWCiphertext::keyswitch_scratch_space(
141 &module,
142 ct_out.size(),
143 ct_in.size(),
144 ksk.size(),
145 tsk.size(),
146 rank,
147 ),
148 );
149
150 let var_xs: f64 = 0.5;
151
152 let mut sk_in: SecretKey<Vec<u8>> = SecretKey::new(&module, rank);
153 sk_in.fill_ternary_prob(var_xs, &mut source_xs);
154
155 let mut sk_in_dft: SecretKeyFourier<Vec<u8>, FFT64> = SecretKeyFourier::new(&module, rank);
156 sk_in_dft.dft(&module, &sk_in);
157
158 let mut sk_out: SecretKey<Vec<u8>> = SecretKey::new(&module, rank);
159 sk_out.fill_ternary_prob(var_xs, &mut source_xs);
160
161 let mut sk_out_dft: SecretKeyFourier<Vec<u8>, FFT64> = SecretKeyFourier::new(&module, rank);
162 sk_out_dft.dft(&module, &sk_out);
163
164 ksk.encrypt_sk(
165 &module,
166 &sk_in,
167 &sk_out_dft,
168 &mut source_xa,
169 &mut source_xe,
170 sigma,
171 scratch.borrow(),
172 );
173 tsk.encrypt_sk(
174 &module,
175 &sk_out_dft,
176 &mut source_xa,
177 &mut source_xe,
178 sigma,
179 scratch.borrow(),
180 );
181
182 pt_scalar.fill_ternary_hw(0, module.n(), &mut source_xs);
183
184 ct_in.encrypt_sk(
185 &module,
186 &pt_scalar,
187 &sk_in_dft,
188 &mut source_xa,
189 &mut source_xe,
190 sigma,
191 scratch.borrow(),
192 );
193
194 ct_out.keyswitch(&module, &ct_in, &ksk, &tsk, scratch.borrow());
195
196 let mut ct_glwe_fourier: GLWECiphertextFourier<Vec<u8>, FFT64> = GLWECiphertextFourier::new(&module, basek, k, rank);
197 let mut pt_dft: VecZnxDft<Vec<u8>, FFT64> = module.new_vec_znx_dft(1, ct_out.size());
198 let mut pt_big: VecZnxBig<Vec<u8>, FFT64> = module.new_vec_znx_big(1, ct_out.size());
199
200 (0..ct_out.rank() + 1).for_each(|col_j| {
201 (0..ct_out.rows()).for_each(|row_i| {
202 module.vec_znx_add_scalar_inplace(&mut pt_want, 0, row_i, &pt_scalar, 0);
203
204 // mul with sk[col_j-1]
205 if col_j > 0 {
206 module.vec_znx_dft(&mut pt_dft, 0, &pt_want, 0);
207 module.svp_apply_inplace(&mut pt_dft, 0, &sk_out_dft, col_j - 1);
208 module.vec_znx_idft_tmp_a(&mut pt_big, 0, &mut pt_dft, 0);
209 module.vec_znx_big_normalize(basek, &mut pt_want, 0, &pt_big, 0, scratch.borrow());
210 }
211
212 ct_out.get_row(&module, row_i, col_j, &mut ct_glwe_fourier);
213
214 ct_glwe_fourier.decrypt(&module, &mut pt_have, &sk_out_dft, scratch.borrow());
215
216 module.vec_znx_sub_ab_inplace(&mut pt_have, 0, &pt_want, 0);
217
218 let noise_have: f64 = pt_have.data.std(0, basek).log2();
219 let noise_want: f64 = noise_ggsw_keyswitch(
220 module.n() as f64,
221 basek,
222 col_j,
223 var_xs,
224 0f64,
225 sigma * sigma,
226 0f64,
227 rank as f64,
228 k,
229 k,
230 );
231
232 println!("{} {}", noise_have, noise_want);
233
234 assert!(
235 (noise_have - noise_want).abs() <= 0.1,
236 "{} {}",
237 noise_have,
238 noise_want
239 );
240
241 pt_want.data.zero();
242 });
243 });
244}
245
246pub(crate) fn noise_ggsw_keyswitch(
247 n: f64,
248 basek: usize,
249 col: usize,
250 var_xs: f64,
251 var_a_err: f64,
252 var_gct_err_lhs: f64,
253 var_gct_err_rhs: f64,
254 rank: f64,
255 a_logq: usize,
256 b_logq: usize,
257) -> f64 {
258 let var_si_x_sj: f64 = n * var_xs * var_xs;
259
260 // Initial KS for col = 0
261 let mut noise: f64 = var_noise_gglwe_product(
262 n,
263 basek,
264 var_xs,
265 var_xs,
266 var_a_err,
267 var_gct_err_lhs,
268 var_gct_err_rhs,
269 rank,
270 a_logq,
271 b_logq,
272 );
273
274 // Other GGSW reconstruction for col > 0
275 if col > 0 {
276 noise += var_noise_gglwe_product(
277 n,
278 basek,
279 var_xs,
280 var_si_x_sj,
281 var_a_err + 1f64 / 12.0,
282 var_gct_err_lhs,
283 var_gct_err_rhs,
284 rank,
285 a_logq,
286 b_logq,
287 );
288 noise += n * noise * var_xs * 0.5;
289 }
290
291 noise = noise.sqrt();
292 noise.log2().min(-1.0) // max noise is [-2^{-1}, 2^{-1}]
293}
294
295fn test_automorphism(p: i64, log_n: usize, basek: usize, k: usize, rank: usize, sigma: f64) {
296 let module: Module<FFT64> = Module::<FFT64>::new(1 << log_n);
297 let rows: usize = (k + basek - 1) / basek;
298
299 let mut ct_in: GGSWCiphertext<Vec<u8>, FFT64> = GGSWCiphertext::new(&module, basek, k, rows, rank);
300 let mut ct_out: GGSWCiphertext<Vec<u8>, FFT64> = GGSWCiphertext::new(&module, basek, k, rows, rank);
301 let mut tsk: TensorKey<Vec<u8>, FFT64> = TensorKey::new(&module, basek, k, rows, rank);
302 let mut ksk: GLWESwitchingKey<Vec<u8>, FFT64> = GLWESwitchingKey::new(&module, basek, k, rows, rank, rank);
303 let mut pt_have: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::new(&module, basek, k);
304 let mut pt_want: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::new(&module, basek, k);
305 let mut pt_scalar: ScalarZnx<Vec<u8>> = module.new_scalar_znx(1);
306
307 let mut source_xs: Source = Source::new([0u8; 32]);
308 let mut source_xe: Source = Source::new([0u8; 32]);
309 let mut source_xa: Source = Source::new([0u8; 32]);
310
311 let mut scratch: ScratchOwned = ScratchOwned::new(
312 GGSWCiphertext::encrypt_sk_scratch_space(&module, rank, ct_in.size())
313 | GLWECiphertextFourier::decrypt_scratch_space(&module, ct_out.size())
314 | GLWESwitchingKey::encrypt_sk_scratch_space(&module, rank, ksk.size())
315 | TensorKey::encrypt_sk_scratch_space(&module, rank, ksk.size())
316 | GGSWCiphertext::keyswitch_scratch_space(
317 &module,
318 ct_out.size(),
319 ct_in.size(),
320 ksk.size(),
321 tsk.size(),
322 rank,
323 ),
324 );
325
326 let var_xs: f64 = 0.5;
327
328 let mut sk_in: SecretKey<Vec<u8>> = SecretKey::new(&module, rank);
329 sk_in.fill_ternary_prob(var_xs, &mut source_xs);
330
331 let mut sk_in_dft: SecretKeyFourier<Vec<u8>, FFT64> = SecretKeyFourier::new(&module, rank);
332 sk_in_dft.dft(&module, &sk_in);
333
334 let mut sk_out: SecretKey<Vec<u8>> = SecretKey::new(&module, rank);
335 sk_out.fill_ternary_prob(var_xs, &mut source_xs);
336
337 let mut sk_out_dft: SecretKeyFourier<Vec<u8>, FFT64> = SecretKeyFourier::new(&module, rank);
338 sk_out_dft.dft(&module, &sk_out);
339
340 ksk.encrypt_sk(
341 &module,
342 &sk_in,
343 &sk_out_dft,
344 &mut source_xa,
345 &mut source_xe,
346 sigma,
347 scratch.borrow(),
348 );
349 tsk.encrypt_sk(
350 &module,
351 &sk_out_dft,
352 &mut source_xa,
353 &mut source_xe,
354 sigma,
355 scratch.borrow(),
356 );
357
358 pt_scalar.fill_ternary_hw(0, module.n(), &mut source_xs);
359
360 ct_in.encrypt_sk(
361 &module,
362 &pt_scalar,
363 &sk_in_dft,
364 &mut source_xa,
365 &mut source_xe,
366 sigma,
367 scratch.borrow(),
368 );
369
370 ct_out.keyswitch(&module, &ct_in, &ksk, &tsk, scratch.borrow());
371
372 let mut ct_glwe_fourier: GLWECiphertextFourier<Vec<u8>, FFT64> = GLWECiphertextFourier::new(&module, basek, k, rank);
373 let mut pt_dft: VecZnxDft<Vec<u8>, FFT64> = module.new_vec_znx_dft(1, ct_out.size());
374 let mut pt_big: VecZnxBig<Vec<u8>, FFT64> = module.new_vec_znx_big(1, ct_out.size());
375
376 (0..ct_out.rank() + 1).for_each(|col_j| {
377 (0..ct_out.rows()).for_each(|row_i| {
378 module.vec_znx_add_scalar_inplace(&mut pt_want, 0, row_i, &pt_scalar, 0);
379
380 // mul with sk[col_j-1]
381 if col_j > 0 {
382 module.vec_znx_dft(&mut pt_dft, 0, &pt_want, 0);
383 module.svp_apply_inplace(&mut pt_dft, 0, &sk_out_dft, col_j - 1);
384 module.vec_znx_idft_tmp_a(&mut pt_big, 0, &mut pt_dft, 0);
385 module.vec_znx_big_normalize(basek, &mut pt_want, 0, &pt_big, 0, scratch.borrow());
386 }
387
388 ct_out.get_row(&module, row_i, col_j, &mut ct_glwe_fourier);
389
390 ct_glwe_fourier.decrypt(&module, &mut pt_have, &sk_out_dft, scratch.borrow());
391
392 module.vec_znx_sub_ab_inplace(&mut pt_have, 0, &pt_want, 0);
393
394 let noise_have: f64 = pt_have.data.std(0, basek).log2();
395 let noise_want: f64 = noise_ggsw_keyswitch(
396 module.n() as f64,
397 basek,
398 col_j,
399 var_xs,
400 0f64,
401 sigma * sigma,
402 0f64,
403 rank as f64,
404 k,
405 k,
406 );
407
408 assert!(
409 (noise_have - noise_want).abs() <= 0.1,
410 "{} {}",
411 noise_have,
412 noise_want
413 );
414
415 pt_want.data.zero();
416 });
417 });
418}
419
420fn test_external_product(log_n: usize, basek: usize, k_ggsw: usize, rank: usize, sigma: f64) {
421 let module: Module<FFT64> = Module::<FFT64>::new(1 << log_n);
422
423 let rows: usize = (k_ggsw + basek - 1) / basek;
424
425 let mut ct_ggsw_rhs: GGSWCiphertext<Vec<u8>, FFT64> = GGSWCiphertext::new(&module, basek, k_ggsw, rows, rank);
426 let mut ct_ggsw_lhs_in: GGSWCiphertext<Vec<u8>, FFT64> = GGSWCiphertext::new(&module, basek, k_ggsw, rows, rank);
427 let mut ct_ggsw_lhs_out: GGSWCiphertext<Vec<u8>, FFT64> = GGSWCiphertext::new(&module, basek, k_ggsw, rows, rank);
428 let mut pt_ggsw_lhs: ScalarZnx<Vec<u8>> = module.new_scalar_znx(1);
429 let mut pt_ggsw_rhs: ScalarZnx<Vec<u8>> = module.new_scalar_znx(1);
430
431 let mut source_xs: Source = Source::new([0u8; 32]);
432 let mut source_xe: Source = Source::new([0u8; 32]);
433 let mut source_xa: Source = Source::new([0u8; 32]);
434
435 pt_ggsw_lhs.fill_ternary_prob(0, 0.5, &mut source_xs);
436
437 let k: usize = 1;
438
439 pt_ggsw_rhs.to_mut().raw_mut()[k] = 1; //X^{k}
440
441 let mut scratch: ScratchOwned = ScratchOwned::new(
442 GLWECiphertextFourier::decrypt_scratch_space(&module, ct_ggsw_lhs_out.size())
443 | GGSWCiphertext::encrypt_sk_scratch_space(&module, rank, ct_ggsw_lhs_in.size())
444 | GGSWCiphertext::external_product_scratch_space(
445 &module,
446 ct_ggsw_lhs_out.size(),
447 ct_ggsw_lhs_in.size(),
448 ct_ggsw_rhs.size(),
449 rank,
450 ),
451 );
452
453 let mut sk: SecretKey<Vec<u8>> = SecretKey::new(&module, rank);
454 sk.fill_ternary_prob(0.5, &mut source_xs);
455
456 let mut sk_dft: SecretKeyFourier<Vec<u8>, FFT64> = SecretKeyFourier::new(&module, rank);
457 sk_dft.dft(&module, &sk);
458
459 ct_ggsw_rhs.encrypt_sk(
460 &module,
461 &pt_ggsw_rhs,
462 &sk_dft,
463 &mut source_xa,
464 &mut source_xe,
465 sigma,
466 scratch.borrow(),
467 );
468
469 ct_ggsw_lhs_in.encrypt_sk(
470 &module,
471 &pt_ggsw_lhs,
472 &sk_dft,
473 &mut source_xa,
474 &mut source_xe,
475 sigma,
476 scratch.borrow(),
477 );
478
479 ct_ggsw_lhs_out.external_product(&module, &ct_ggsw_lhs_in, &ct_ggsw_rhs, scratch.borrow());
480
481 let mut ct_glwe_fourier: GLWECiphertextFourier<Vec<u8>, FFT64> = GLWECiphertextFourier::new(&module, basek, k_ggsw, rank);
482 let mut pt: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::new(&module, basek, k_ggsw);
483 let mut pt_dft: VecZnxDft<Vec<u8>, FFT64> = module.new_vec_znx_dft(1, ct_ggsw_lhs_out.size());
484 let mut pt_big: VecZnxBig<Vec<u8>, FFT64> = module.new_vec_znx_big(1, ct_ggsw_lhs_out.size());
485 let mut pt_want: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::new(&module, basek, k_ggsw);
486
487 module.vec_znx_rotate_inplace(k as i64, &mut pt_ggsw_lhs, 0);
488
489 (0..ct_ggsw_lhs_out.rank() + 1).for_each(|col_j| {
490 (0..ct_ggsw_lhs_out.rows()).for_each(|row_i| {
491 module.vec_znx_add_scalar_inplace(&mut pt_want, 0, row_i, &pt_ggsw_lhs, 0);
492
493 if col_j > 0 {
494 module.vec_znx_dft(&mut pt_dft, 0, &pt_want, 0);
495 module.svp_apply_inplace(&mut pt_dft, 0, &sk_dft, col_j - 1);
496 module.vec_znx_idft_tmp_a(&mut pt_big, 0, &mut pt_dft, 0);
497 module.vec_znx_big_normalize(basek, &mut pt_want, 0, &pt_big, 0, scratch.borrow());
498 }
499
500 ct_ggsw_lhs_out.get_row(&module, row_i, col_j, &mut ct_glwe_fourier);
501 ct_glwe_fourier.decrypt(&module, &mut pt, &sk_dft, scratch.borrow());
502
503 module.vec_znx_sub_ab_inplace(&mut pt, 0, &pt_want, 0);
504
505 let noise_have: f64 = pt.data.std(0, basek).log2();
506
507 let var_gct_err_lhs: f64 = sigma * sigma;
508 let var_gct_err_rhs: f64 = 0f64;
509
510 let var_msg: f64 = 1f64 / module.n() as f64; // X^{k}
511 let var_a0_err: f64 = sigma * sigma;
512 let var_a1_err: f64 = 1f64 / 12f64;
513
514 let noise_want: f64 = noise_ggsw_product(
515 module.n() as f64,
516 basek,
517 0.5,
518 var_msg,
519 var_a0_err,
520 var_a1_err,
521 var_gct_err_lhs,
522 var_gct_err_rhs,
523 rank as f64,
524 k_ggsw,
525 k_ggsw,
526 );
527
528 assert!(
529 (noise_have - noise_want).abs() <= 0.1,
530 "have: {} want: {}",
531 noise_have,
532 noise_want
533 );
534
535 pt_want.data.zero();
536 });
537 });
538}
539
540fn test_external_product_inplace(log_n: usize, basek: usize, k_ggsw: usize, rank: usize, sigma: f64) {
541 let module: Module<FFT64> = Module::<FFT64>::new(1 << log_n);
542 let rows: usize = (k_ggsw + basek - 1) / basek;
543
544 let mut ct_ggsw_rhs: GGSWCiphertext<Vec<u8>, FFT64> = GGSWCiphertext::new(&module, basek, k_ggsw, rows, rank);
545 let mut ct_ggsw_lhs: GGSWCiphertext<Vec<u8>, FFT64> = GGSWCiphertext::new(&module, basek, k_ggsw, rows, rank);
546 let mut pt_ggsw_lhs: ScalarZnx<Vec<u8>> = module.new_scalar_znx(1);
547 let mut pt_ggsw_rhs: ScalarZnx<Vec<u8>> = module.new_scalar_znx(1);
548
549 let mut source_xs: Source = Source::new([0u8; 32]);
550 let mut source_xe: Source = Source::new([0u8; 32]);
551 let mut source_xa: Source = Source::new([0u8; 32]);
552
553 pt_ggsw_lhs.fill_ternary_prob(0, 0.5, &mut source_xs);
554
555 let k: usize = 1;
556
557 pt_ggsw_rhs.to_mut().raw_mut()[k] = 1; //X^{k}
558
559 let mut scratch: ScratchOwned = ScratchOwned::new(
560 GLWESwitchingKey::encrypt_sk_scratch_space(&module, rank, ct_ggsw_rhs.size())
561 | GLWECiphertextFourier::decrypt_scratch_space(&module, ct_ggsw_lhs.size())
562 | GGSWCiphertext::encrypt_sk_scratch_space(&module, rank, ct_ggsw_lhs.size())
563 | GGSWCiphertext::external_product_inplace_scratch_space(&module, ct_ggsw_lhs.size(), ct_ggsw_rhs.size(), rank),
564 );
565
566 let mut sk: SecretKey<Vec<u8>> = SecretKey::new(&module, rank);
567 sk.fill_ternary_prob(0.5, &mut source_xs);
568
569 let mut sk_dft: SecretKeyFourier<Vec<u8>, FFT64> = SecretKeyFourier::new(&module, rank);
570 sk_dft.dft(&module, &sk);
571
572 ct_ggsw_rhs.encrypt_sk(
573 &module,
574 &pt_ggsw_rhs,
575 &sk_dft,
576 &mut source_xa,
577 &mut source_xe,
578 sigma,
579 scratch.borrow(),
580 );
581
582 ct_ggsw_lhs.encrypt_sk(
583 &module,
584 &pt_ggsw_lhs,
585 &sk_dft,
586 &mut source_xa,
587 &mut source_xe,
588 sigma,
589 scratch.borrow(),
590 );
591
592 ct_ggsw_lhs.external_product_inplace(&module, &ct_ggsw_rhs, scratch.borrow());
593
594 let mut ct_glwe_fourier: GLWECiphertextFourier<Vec<u8>, FFT64> = GLWECiphertextFourier::new(&module, basek, k_ggsw, rank);
595 let mut pt: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::new(&module, basek, k_ggsw);
596 let mut pt_dft: VecZnxDft<Vec<u8>, FFT64> = module.new_vec_znx_dft(1, ct_ggsw_lhs.size());
597 let mut pt_big: VecZnxBig<Vec<u8>, FFT64> = module.new_vec_znx_big(1, ct_ggsw_lhs.size());
598 let mut pt_want: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::new(&module, basek, k_ggsw);
599
600 module.vec_znx_rotate_inplace(k as i64, &mut pt_ggsw_lhs, 0);
601
602 (0..ct_ggsw_lhs.rank() + 1).for_each(|col_j| {
603 (0..ct_ggsw_lhs.rows()).for_each(|row_i| {
604 module.vec_znx_add_scalar_inplace(&mut pt_want, 0, row_i, &pt_ggsw_lhs, 0);
605
606 if col_j > 0 {
607 module.vec_znx_dft(&mut pt_dft, 0, &pt_want, 0);
608 module.svp_apply_inplace(&mut pt_dft, 0, &sk_dft, col_j - 1);
609 module.vec_znx_idft_tmp_a(&mut pt_big, 0, &mut pt_dft, 0);
610 module.vec_znx_big_normalize(basek, &mut pt_want, 0, &pt_big, 0, scratch.borrow());
611 }
612
613 ct_ggsw_lhs.get_row(&module, row_i, col_j, &mut ct_glwe_fourier);
614 ct_glwe_fourier.decrypt(&module, &mut pt, &sk_dft, scratch.borrow());
615
616 module.vec_znx_sub_ab_inplace(&mut pt, 0, &pt_want, 0);
617
618 let noise_have: f64 = pt.data.std(0, basek).log2();
619
620 let var_gct_err_lhs: f64 = sigma * sigma;
621 let var_gct_err_rhs: f64 = 0f64;
622
623 let var_msg: f64 = 1f64 / module.n() as f64; // X^{k}
624 let var_a0_err: f64 = sigma * sigma;
625 let var_a1_err: f64 = 1f64 / 12f64;
626
627 let noise_want: f64 = noise_ggsw_product(
628 module.n() as f64,
629 basek,
630 0.5,
631 var_msg,
632 var_a0_err,
633 var_a1_err,
634 var_gct_err_lhs,
635 var_gct_err_rhs,
636 rank as f64,
637 k_ggsw,
638 k_ggsw,
639 );
640
641 assert!(
642 (noise_have - noise_want).abs() <= 0.1,
643 "have: {} want: {}",
644 noise_have,
645 noise_want
646 );
647
648 pt_want.data.zero();
649 });
650 });
651}
652pub(crate) fn noise_ggsw_product(
653 n: f64,
654 basek: usize,
655 var_xs: f64,
656 var_msg: f64,
657 var_a0_err: f64,
658 var_a1_err: f64,
659 var_gct_err_lhs: f64,
660 var_gct_err_rhs: f64,
661 rank: f64,
662 a_logq: usize,
663 b_logq: usize,
664) -> f64 {
665 let a_logq: usize = a_logq.min(b_logq);
666 let a_cols: usize = (a_logq + basek - 1) / basek;
667
668 let b_scale = 2.0f64.powi(b_logq as i32);
669 let a_scale: f64 = 2.0f64.powi((b_logq - a_logq) as i32);
670
671 let base: f64 = (1 << (basek)) as f64;
672 let var_base: f64 = base * base / 12f64;
673
674 // lhs = a_cols * n * (var_base * var_gct_err_lhs + var_e_a * var_msg * p^2)
675 // rhs = a_cols * n * var_base * var_gct_err_rhs * var_xs
676 let mut noise: f64 = (rank + 1.0) * (a_cols as f64) * n * var_base * (var_gct_err_lhs + var_xs * var_gct_err_rhs);
677 noise += var_msg * var_a0_err * a_scale * a_scale * n;
678 noise += var_msg * var_a1_err * a_scale * a_scale * n * var_xs * rank;
679 noise = noise.sqrt();
680 noise /= b_scale;
681 noise.log2().min(-1.0) // max noise is [-2^{-1}, 2^{-1}]
682}