poulpy/core/src/test_fft64/ggsw.rs
core/src/test_fft64/ggsw.rsRaw
1use base2k::{
2 FFT64, Module, ScalarZnx, ScalarZnxAlloc, ScalarZnxDftOps, ScalarZnxOps, ScratchOwned, Stats, VecZnxBig, VecZnxBigAlloc,
3 VecZnxBigOps, 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
295#[test]
296fn automorphism() {
297 (1..4).for_each(|rank| {
298 println!("test automorphism rank: {}", rank);
299 test_automorphism(-5, 12, 15, 60, rank, 3.2);
300 });
301}
302
303fn test_automorphism(p: i64, log_n: usize, basek: usize, k: usize, rank: usize, sigma: f64) {
304 let module: Module<FFT64> = Module::<FFT64>::new(1 << log_n);
305 let rows: usize = (k + basek - 1) / basek;
306
307 let mut ct_in: GGSWCiphertext<Vec<u8>, FFT64> = GGSWCiphertext::new(&module, basek, k, rows, rank);
308 let mut ct_out: GGSWCiphertext<Vec<u8>, FFT64> = GGSWCiphertext::new(&module, basek, k, rows, rank);
309 let mut tensor_key: TensorKey<Vec<u8>, FFT64> = TensorKey::new(&module, basek, k, rows, rank);
310 let mut auto_key: AutomorphismKey<Vec<u8>, FFT64> = AutomorphismKey::new(&module, basek, k, rows, rank);
311 let mut pt_have: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::new(&module, basek, k);
312 let mut pt_want: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::new(&module, basek, k);
313 let mut pt_scalar: ScalarZnx<Vec<u8>> = module.new_scalar_znx(1);
314
315 let mut source_xs: Source = Source::new([0u8; 32]);
316 let mut source_xe: Source = Source::new([0u8; 32]);
317 let mut source_xa: Source = Source::new([0u8; 32]);
318
319 let mut scratch: ScratchOwned = ScratchOwned::new(
320 GGSWCiphertext::encrypt_sk_scratch_space(&module, rank, ct_in.size())
321 | GLWECiphertextFourier::decrypt_scratch_space(&module, ct_out.size())
322 | AutomorphismKey::encrypt_sk_scratch_space(&module, rank, auto_key.size())
323 | TensorKey::encrypt_sk_scratch_space(&module, rank, tensor_key.size())
324 | GGSWCiphertext::automorphism_scratch_space(
325 &module,
326 ct_out.size(),
327 ct_in.size(),
328 auto_key.size(),
329 tensor_key.size(),
330 rank,
331 ),
332 );
333
334 let var_xs: f64 = 0.5;
335
336 let mut sk: SecretKey<Vec<u8>> = SecretKey::new(&module, rank);
337 sk.fill_ternary_prob(var_xs, &mut source_xs);
338
339 let mut sk_dft: SecretKeyFourier<Vec<u8>, FFT64> = SecretKeyFourier::new(&module, rank);
340 sk_dft.dft(&module, &sk);
341
342 auto_key.encrypt_sk(
343 &module,
344 p,
345 &sk,
346 &mut source_xa,
347 &mut source_xe,
348 sigma,
349 scratch.borrow(),
350 );
351 tensor_key.encrypt_sk(
352 &module,
353 &sk_dft,
354 &mut source_xa,
355 &mut source_xe,
356 sigma,
357 scratch.borrow(),
358 );
359
360 pt_scalar.fill_ternary_hw(0, module.n(), &mut source_xs);
361
362 ct_in.encrypt_sk(
363 &module,
364 &pt_scalar,
365 &sk_dft,
366 &mut source_xa,
367 &mut source_xe,
368 sigma,
369 scratch.borrow(),
370 );
371
372 ct_out.automorphism(&module, &ct_in, &auto_key, &tensor_key, scratch.borrow());
373
374 module.scalar_znx_automorphism_inplace(p, &mut pt_scalar, 0);
375
376 let mut ct_glwe_fourier: GLWECiphertextFourier<Vec<u8>, FFT64> = GLWECiphertextFourier::new(&module, basek, k, rank);
377 let mut pt_dft: VecZnxDft<Vec<u8>, FFT64> = module.new_vec_znx_dft(1, ct_out.size());
378 let mut pt_big: VecZnxBig<Vec<u8>, FFT64> = module.new_vec_znx_big(1, ct_out.size());
379
380 (0..ct_out.rank() + 1).for_each(|col_j| {
381 (0..ct_out.rows()).for_each(|row_i| {
382 module.vec_znx_add_scalar_inplace(&mut pt_want, 0, row_i, &pt_scalar, 0);
383
384 // mul with sk[col_j-1]
385 if col_j > 0 {
386 module.vec_znx_dft(&mut pt_dft, 0, &pt_want, 0);
387 module.svp_apply_inplace(&mut pt_dft, 0, &sk_dft, col_j - 1);
388 module.vec_znx_idft_tmp_a(&mut pt_big, 0, &mut pt_dft, 0);
389 module.vec_znx_big_normalize(basek, &mut pt_want, 0, &pt_big, 0, scratch.borrow());
390 }
391
392 ct_out.get_row(&module, row_i, col_j, &mut ct_glwe_fourier);
393
394 ct_glwe_fourier.decrypt(&module, &mut pt_have, &sk_dft, scratch.borrow());
395
396 module.vec_znx_sub_ab_inplace(&mut pt_have, 0, &pt_want, 0);
397
398 let noise_have: f64 = pt_have.data.std(0, basek).log2();
399 let noise_want: f64 = noise_ggsw_keyswitch(
400 module.n() as f64,
401 basek,
402 col_j,
403 var_xs,
404 0f64,
405 sigma * sigma,
406 0f64,
407 rank as f64,
408 k,
409 k,
410 );
411
412 assert!(
413 (noise_have - noise_want).abs() <= 0.1,
414 "{} {}",
415 noise_have,
416 noise_want
417 );
418
419 pt_want.data.zero();
420 });
421 });
422}
423
424fn test_external_product(log_n: usize, basek: usize, k_ggsw: usize, rank: usize, sigma: f64) {
425 let module: Module<FFT64> = Module::<FFT64>::new(1 << log_n);
426
427 let rows: usize = (k_ggsw + basek - 1) / basek;
428
429 let mut ct_ggsw_rhs: GGSWCiphertext<Vec<u8>, FFT64> = GGSWCiphertext::new(&module, basek, k_ggsw, rows, rank);
430 let mut ct_ggsw_lhs_in: GGSWCiphertext<Vec<u8>, FFT64> = GGSWCiphertext::new(&module, basek, k_ggsw, rows, rank);
431 let mut ct_ggsw_lhs_out: GGSWCiphertext<Vec<u8>, FFT64> = GGSWCiphertext::new(&module, basek, k_ggsw, rows, rank);
432 let mut pt_ggsw_lhs: ScalarZnx<Vec<u8>> = module.new_scalar_znx(1);
433 let mut pt_ggsw_rhs: ScalarZnx<Vec<u8>> = module.new_scalar_znx(1);
434
435 let mut source_xs: Source = Source::new([0u8; 32]);
436 let mut source_xe: Source = Source::new([0u8; 32]);
437 let mut source_xa: Source = Source::new([0u8; 32]);
438
439 pt_ggsw_lhs.fill_ternary_prob(0, 0.5, &mut source_xs);
440
441 let k: usize = 1;
442
443 pt_ggsw_rhs.to_mut().raw_mut()[k] = 1; //X^{k}
444
445 let mut scratch: ScratchOwned = ScratchOwned::new(
446 GLWECiphertextFourier::decrypt_scratch_space(&module, ct_ggsw_lhs_out.size())
447 | GGSWCiphertext::encrypt_sk_scratch_space(&module, rank, ct_ggsw_lhs_in.size())
448 | GGSWCiphertext::external_product_scratch_space(
449 &module,
450 ct_ggsw_lhs_out.size(),
451 ct_ggsw_lhs_in.size(),
452 ct_ggsw_rhs.size(),
453 rank,
454 ),
455 );
456
457 let mut sk: SecretKey<Vec<u8>> = SecretKey::new(&module, rank);
458 sk.fill_ternary_prob(0.5, &mut source_xs);
459
460 let mut sk_dft: SecretKeyFourier<Vec<u8>, FFT64> = SecretKeyFourier::new(&module, rank);
461 sk_dft.dft(&module, &sk);
462
463 ct_ggsw_rhs.encrypt_sk(
464 &module,
465 &pt_ggsw_rhs,
466 &sk_dft,
467 &mut source_xa,
468 &mut source_xe,
469 sigma,
470 scratch.borrow(),
471 );
472
473 ct_ggsw_lhs_in.encrypt_sk(
474 &module,
475 &pt_ggsw_lhs,
476 &sk_dft,
477 &mut source_xa,
478 &mut source_xe,
479 sigma,
480 scratch.borrow(),
481 );
482
483 ct_ggsw_lhs_out.external_product(&module, &ct_ggsw_lhs_in, &ct_ggsw_rhs, scratch.borrow());
484
485 let mut ct_glwe_fourier: GLWECiphertextFourier<Vec<u8>, FFT64> = GLWECiphertextFourier::new(&module, basek, k_ggsw, rank);
486 let mut pt: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::new(&module, basek, k_ggsw);
487 let mut pt_dft: VecZnxDft<Vec<u8>, FFT64> = module.new_vec_znx_dft(1, ct_ggsw_lhs_out.size());
488 let mut pt_big: VecZnxBig<Vec<u8>, FFT64> = module.new_vec_znx_big(1, ct_ggsw_lhs_out.size());
489 let mut pt_want: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::new(&module, basek, k_ggsw);
490
491 module.vec_znx_rotate_inplace(k as i64, &mut pt_ggsw_lhs, 0);
492
493 (0..ct_ggsw_lhs_out.rank() + 1).for_each(|col_j| {
494 (0..ct_ggsw_lhs_out.rows()).for_each(|row_i| {
495 module.vec_znx_add_scalar_inplace(&mut pt_want, 0, row_i, &pt_ggsw_lhs, 0);
496
497 if col_j > 0 {
498 module.vec_znx_dft(&mut pt_dft, 0, &pt_want, 0);
499 module.svp_apply_inplace(&mut pt_dft, 0, &sk_dft, col_j - 1);
500 module.vec_znx_idft_tmp_a(&mut pt_big, 0, &mut pt_dft, 0);
501 module.vec_znx_big_normalize(basek, &mut pt_want, 0, &pt_big, 0, scratch.borrow());
502 }
503
504 ct_ggsw_lhs_out.get_row(&module, row_i, col_j, &mut ct_glwe_fourier);
505 ct_glwe_fourier.decrypt(&module, &mut pt, &sk_dft, scratch.borrow());
506
507 module.vec_znx_sub_ab_inplace(&mut pt, 0, &pt_want, 0);
508
509 let noise_have: f64 = pt.data.std(0, basek).log2();
510
511 let var_gct_err_lhs: f64 = sigma * sigma;
512 let var_gct_err_rhs: f64 = 0f64;
513
514 let var_msg: f64 = 1f64 / module.n() as f64; // X^{k}
515 let var_a0_err: f64 = sigma * sigma;
516 let var_a1_err: f64 = 1f64 / 12f64;
517
518 let noise_want: f64 = noise_ggsw_product(
519 module.n() as f64,
520 basek,
521 0.5,
522 var_msg,
523 var_a0_err,
524 var_a1_err,
525 var_gct_err_lhs,
526 var_gct_err_rhs,
527 rank as f64,
528 k_ggsw,
529 k_ggsw,
530 );
531
532 assert!(
533 (noise_have - noise_want).abs() <= 0.1,
534 "have: {} want: {}",
535 noise_have,
536 noise_want
537 );
538
539 pt_want.data.zero();
540 });
541 });
542}
543
544fn test_external_product_inplace(log_n: usize, basek: usize, k_ggsw: usize, rank: usize, sigma: f64) {
545 let module: Module<FFT64> = Module::<FFT64>::new(1 << log_n);
546 let rows: usize = (k_ggsw + basek - 1) / basek;
547
548 let mut ct_ggsw_rhs: GGSWCiphertext<Vec<u8>, FFT64> = GGSWCiphertext::new(&module, basek, k_ggsw, rows, rank);
549 let mut ct_ggsw_lhs: GGSWCiphertext<Vec<u8>, FFT64> = GGSWCiphertext::new(&module, basek, k_ggsw, rows, rank);
550 let mut pt_ggsw_lhs: ScalarZnx<Vec<u8>> = module.new_scalar_znx(1);
551 let mut pt_ggsw_rhs: ScalarZnx<Vec<u8>> = module.new_scalar_znx(1);
552
553 let mut source_xs: Source = Source::new([0u8; 32]);
554 let mut source_xe: Source = Source::new([0u8; 32]);
555 let mut source_xa: Source = Source::new([0u8; 32]);
556
557 pt_ggsw_lhs.fill_ternary_prob(0, 0.5, &mut source_xs);
558
559 let k: usize = 1;
560
561 pt_ggsw_rhs.to_mut().raw_mut()[k] = 1; //X^{k}
562
563 let mut scratch: ScratchOwned = ScratchOwned::new(
564 GLWESwitchingKey::encrypt_sk_scratch_space(&module, rank, ct_ggsw_rhs.size())
565 | GLWECiphertextFourier::decrypt_scratch_space(&module, ct_ggsw_lhs.size())
566 | GGSWCiphertext::encrypt_sk_scratch_space(&module, rank, ct_ggsw_lhs.size())
567 | GGSWCiphertext::external_product_inplace_scratch_space(&module, ct_ggsw_lhs.size(), ct_ggsw_rhs.size(), rank),
568 );
569
570 let mut sk: SecretKey<Vec<u8>> = SecretKey::new(&module, rank);
571 sk.fill_ternary_prob(0.5, &mut source_xs);
572
573 let mut sk_dft: SecretKeyFourier<Vec<u8>, FFT64> = SecretKeyFourier::new(&module, rank);
574 sk_dft.dft(&module, &sk);
575
576 ct_ggsw_rhs.encrypt_sk(
577 &module,
578 &pt_ggsw_rhs,
579 &sk_dft,
580 &mut source_xa,
581 &mut source_xe,
582 sigma,
583 scratch.borrow(),
584 );
585
586 ct_ggsw_lhs.encrypt_sk(
587 &module,
588 &pt_ggsw_lhs,
589 &sk_dft,
590 &mut source_xa,
591 &mut source_xe,
592 sigma,
593 scratch.borrow(),
594 );
595
596 ct_ggsw_lhs.external_product_inplace(&module, &ct_ggsw_rhs, scratch.borrow());
597
598 let mut ct_glwe_fourier: GLWECiphertextFourier<Vec<u8>, FFT64> = GLWECiphertextFourier::new(&module, basek, k_ggsw, rank);
599 let mut pt: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::new(&module, basek, k_ggsw);
600 let mut pt_dft: VecZnxDft<Vec<u8>, FFT64> = module.new_vec_znx_dft(1, ct_ggsw_lhs.size());
601 let mut pt_big: VecZnxBig<Vec<u8>, FFT64> = module.new_vec_znx_big(1, ct_ggsw_lhs.size());
602 let mut pt_want: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::new(&module, basek, k_ggsw);
603
604 module.vec_znx_rotate_inplace(k as i64, &mut pt_ggsw_lhs, 0);
605
606 (0..ct_ggsw_lhs.rank() + 1).for_each(|col_j| {
607 (0..ct_ggsw_lhs.rows()).for_each(|row_i| {
608 module.vec_znx_add_scalar_inplace(&mut pt_want, 0, row_i, &pt_ggsw_lhs, 0);
609
610 if col_j > 0 {
611 module.vec_znx_dft(&mut pt_dft, 0, &pt_want, 0);
612 module.svp_apply_inplace(&mut pt_dft, 0, &sk_dft, col_j - 1);
613 module.vec_znx_idft_tmp_a(&mut pt_big, 0, &mut pt_dft, 0);
614 module.vec_znx_big_normalize(basek, &mut pt_want, 0, &pt_big, 0, scratch.borrow());
615 }
616
617 ct_ggsw_lhs.get_row(&module, row_i, col_j, &mut ct_glwe_fourier);
618 ct_glwe_fourier.decrypt(&module, &mut pt, &sk_dft, scratch.borrow());
619
620 module.vec_znx_sub_ab_inplace(&mut pt, 0, &pt_want, 0);
621
622 let noise_have: f64 = pt.data.std(0, basek).log2();
623
624 let var_gct_err_lhs: f64 = sigma * sigma;
625 let var_gct_err_rhs: f64 = 0f64;
626
627 let var_msg: f64 = 1f64 / module.n() as f64; // X^{k}
628 let var_a0_err: f64 = sigma * sigma;
629 let var_a1_err: f64 = 1f64 / 12f64;
630
631 let noise_want: f64 = noise_ggsw_product(
632 module.n() as f64,
633 basek,
634 0.5,
635 var_msg,
636 var_a0_err,
637 var_a1_err,
638 var_gct_err_lhs,
639 var_gct_err_rhs,
640 rank as f64,
641 k_ggsw,
642 k_ggsw,
643 );
644
645 assert!(
646 (noise_have - noise_want).abs() <= 0.1,
647 "have: {} want: {}",
648 noise_have,
649 noise_want
650 );
651
652 pt_want.data.zero();
653 });
654 });
655}
656pub(crate) fn noise_ggsw_product(
657 n: f64,
658 basek: usize,
659 var_xs: f64,
660 var_msg: f64,
661 var_a0_err: f64,
662 var_a1_err: f64,
663 var_gct_err_lhs: f64,
664 var_gct_err_rhs: f64,
665 rank: f64,
666 a_logq: usize,
667 b_logq: usize,
668) -> f64 {
669 let a_logq: usize = a_logq.min(b_logq);
670 let a_cols: usize = (a_logq + basek - 1) / basek;
671
672 let b_scale = 2.0f64.powi(b_logq as i32);
673 let a_scale: f64 = 2.0f64.powi((b_logq - a_logq) as i32);
674
675 let base: f64 = (1 << (basek)) as f64;
676 let var_base: f64 = base * base / 12f64;
677
678 // lhs = a_cols * n * (var_base * var_gct_err_lhs + var_e_a * var_msg * p^2)
679 // rhs = a_cols * n * var_base * var_gct_err_rhs * var_xs
680 let mut noise: f64 = (rank + 1.0) * (a_cols as f64) * n * var_base * (var_gct_err_lhs + var_xs * var_gct_err_rhs);
681 noise += var_msg * var_a0_err * a_scale * a_scale * n;
682 noise += var_msg * var_a1_err * a_scale * a_scale * n * var_xs * rank;
683 noise = noise.sqrt();
684 noise /= b_scale;
685 noise.log2().min(-1.0) // max noise is [-2^{-1}, 2^{-1}]
686}