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