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