poulpy/core/src/test_fft64/ggsw.rs
core/src/test_fft64/ggsw.rsRaw
1use backend::{
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 keyswitch() {
30 (1..4).for_each(|rank| {
31 println!("test keyswitch rank: {}", rank);
32 test_keyswitch(12, 15, 60, rank, 3.2);
33 });
34}
35
36#[test]
37fn keyswitch_inplace() {
38 (1..4).for_each(|rank| {
39 println!("test keyswitch_inplace rank: {}", rank);
40 test_keyswitch_inplace(12, 15, 60, rank, 3.2);
41 });
42}
43
44#[test]
45fn automorphism() {
46 (1..4).for_each(|rank| {
47 println!("test automorphism rank: {}", rank);
48 test_automorphism(-5, 12, 15, 60, rank, 3.2);
49 });
50}
51
52#[test]
53fn automorphism_inplace() {
54 (1..4).for_each(|rank| {
55 println!("test automorphism_inplace rank: {}", rank);
56 test_automorphism_inplace(-5, 12, 15, 60, rank, 3.2);
57 });
58}
59
60#[test]
61fn external_product() {
62 (1..4).for_each(|rank| {
63 println!("test external_product rank: {}", rank);
64 test_external_product(12, 12, 60, rank, 3.2);
65 });
66}
67
68#[test]
69fn external_product_inplace() {
70 (1..4).for_each(|rank| {
71 println!("test external_product rank: {}", rank);
72 test_external_product_inplace(12, 15, 60, rank, 3.2);
73 });
74}
75
76fn test_encrypt_sk(log_n: usize, basek: usize, k: usize, sigma: f64, rank: usize) {
77 let module: Module<FFT64> = Module::<FFT64>::new(1 << log_n);
78
79 let rows: usize = (k + basek - 1) / basek;
80
81 let mut ct: GGSWCiphertext<Vec<u8>, FFT64> = GGSWCiphertext::alloc(&module, basek, k, rows, rank);
82 let mut pt_have: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::alloc(&module, basek, k);
83 let mut pt_want: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::alloc(&module, basek, k);
84 let mut pt_scalar: ScalarZnx<Vec<u8>> = module.new_scalar_znx(1);
85
86 let mut source_xs: Source = Source::new([0u8; 32]);
87 let mut source_xe: Source = Source::new([0u8; 32]);
88 let mut source_xa: Source = Source::new([0u8; 32]);
89
90 pt_scalar.fill_ternary_hw(0, module.n(), &mut source_xs);
91
92 let mut scratch: ScratchOwned = ScratchOwned::new(
93 GGSWCiphertext::encrypt_sk_scratch_space(&module, basek, k, rank)
94 | GLWECiphertextFourier::decrypt_scratch_space(&module, basek, k),
95 );
96
97 let mut sk: SecretKey<Vec<u8>> = SecretKey::alloc(&module, rank);
98 sk.fill_ternary_prob(0.5, &mut source_xs);
99
100 let mut sk_dft: SecretKeyFourier<Vec<u8>, FFT64> = SecretKeyFourier::alloc(&module, rank);
101 sk_dft.dft(&module, &sk);
102
103 ct.encrypt_sk(
104 &module,
105 &pt_scalar,
106 &sk_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> = GLWECiphertextFourier::alloc(&module, basek, k, rank);
114 let mut pt_dft: VecZnxDft<Vec<u8>, FFT64> = module.new_vec_znx_dft(1, ct.size());
115 let mut pt_big: VecZnxBig<Vec<u8>, FFT64> = module.new_vec_znx_big(1, ct.size());
116
117 (0..ct.rank() + 1).for_each(|col_j| {
118 (0..ct.rows()).for_each(|row_i| {
119 module.vec_znx_add_scalar_inplace(&mut pt_want.data, 0, row_i, &pt_scalar, 0);
120
121 // mul with sk[col_j-1]
122 if col_j > 0 {
123 module.vec_znx_dft(&mut pt_dft, 0, &pt_want.data, 0);
124 module.svp_apply_inplace(&mut pt_dft, 0, &sk_dft.data, col_j - 1);
125 module.vec_znx_idft_tmp_a(&mut pt_big, 0, &mut pt_dft, 0);
126 module.vec_znx_big_normalize(basek, &mut pt_want.data, 0, &pt_big, 0, scratch.borrow());
127 }
128
129 ct.get_row(&module, row_i, col_j, &mut ct_glwe_fourier);
130
131 ct_glwe_fourier.decrypt(&module, &mut pt_have, &sk_dft, scratch.borrow());
132
133 module.vec_znx_sub_ab_inplace(&mut pt_have.data, 0, &pt_want.data, 0);
134
135 let std_pt: f64 = pt_have.data.std(0, basek) * (k as f64).exp2();
136 assert!((sigma - std_pt).abs() <= 0.2, "{} {}", sigma, std_pt);
137
138 pt_want.data.zero();
139 });
140 });
141}
142
143fn test_keyswitch(log_n: usize, basek: usize, k: usize, rank: usize, sigma: f64) {
144 let module: Module<FFT64> = Module::<FFT64>::new(1 << log_n);
145 let rows: usize = (k + basek - 1) / basek;
146
147 let mut ct_in: GGSWCiphertext<Vec<u8>, FFT64> = GGSWCiphertext::alloc(&module, basek, k, rows, rank);
148 let mut ct_out: GGSWCiphertext<Vec<u8>, FFT64> = GGSWCiphertext::alloc(&module, basek, k, rows, rank);
149 let mut tsk: TensorKey<Vec<u8>, FFT64> = TensorKey::alloc(&module, basek, k, rows, rank);
150 let mut ksk: GLWESwitchingKey<Vec<u8>, FFT64> = GLWESwitchingKey::alloc(&module, basek, k, rows, rank, rank);
151 let mut pt_have: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::alloc(&module, basek, k);
152 let mut pt_want: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::alloc(&module, basek, k);
153 let mut pt_scalar: ScalarZnx<Vec<u8>> = module.new_scalar_znx(1);
154
155 let mut source_xs: Source = Source::new([0u8; 32]);
156 let mut source_xe: Source = Source::new([0u8; 32]);
157 let mut source_xa: Source = Source::new([0u8; 32]);
158
159 let mut scratch: ScratchOwned = ScratchOwned::new(
160 GGSWCiphertext::encrypt_sk_scratch_space(&module, basek, k, rank)
161 | GLWECiphertextFourier::decrypt_scratch_space(&module, basek, k)
162 | GLWESwitchingKey::encrypt_sk_scratch_space(&module, basek, k, rank)
163 | TensorKey::generate_from_sk_scratch_space(&module, basek, k, rank)
164 | GGSWCiphertext::keyswitch_scratch_space(
165 &module,
166 basek,
167 ct_out.k(),
168 ct_in.k(),
169 ksk.k(),
170 tsk.k(),
171 rank,
172 ),
173 );
174
175 let var_xs: f64 = 0.5;
176
177 let mut sk_in: SecretKey<Vec<u8>> = SecretKey::alloc(&module, rank);
178 sk_in.fill_ternary_prob(var_xs, &mut source_xs);
179
180 let mut sk_in_dft: SecretKeyFourier<Vec<u8>, FFT64> = SecretKeyFourier::alloc(&module, rank);
181 sk_in_dft.dft(&module, &sk_in);
182
183 let mut sk_out: SecretKey<Vec<u8>> = SecretKey::alloc(&module, rank);
184 sk_out.fill_ternary_prob(var_xs, &mut source_xs);
185
186 let mut sk_out_dft: SecretKeyFourier<Vec<u8>, FFT64> = SecretKeyFourier::alloc(&module, rank);
187 sk_out_dft.dft(&module, &sk_out);
188
189 ksk.generate_from_sk(
190 &module,
191 &sk_in,
192 &sk_out_dft,
193 &mut source_xa,
194 &mut source_xe,
195 sigma,
196 scratch.borrow(),
197 );
198 tsk.generate_from_sk(
199 &module,
200 &sk_out_dft,
201 &mut source_xa,
202 &mut source_xe,
203 sigma,
204 scratch.borrow(),
205 );
206
207 pt_scalar.fill_ternary_hw(0, module.n(), &mut source_xs);
208
209 ct_in.encrypt_sk(
210 &module,
211 &pt_scalar,
212 &sk_in_dft,
213 &mut source_xa,
214 &mut source_xe,
215 sigma,
216 scratch.borrow(),
217 );
218
219 ct_out.keyswitch(&module, &ct_in, &ksk, &tsk, scratch.borrow());
220
221 let mut ct_glwe_fourier: GLWECiphertextFourier<Vec<u8>, FFT64> = GLWECiphertextFourier::alloc(&module, basek, k, rank);
222 let mut pt_dft: VecZnxDft<Vec<u8>, FFT64> = module.new_vec_znx_dft(1, ct_out.size());
223 let mut pt_big: VecZnxBig<Vec<u8>, FFT64> = module.new_vec_znx_big(1, ct_out.size());
224
225 (0..ct_out.rank() + 1).for_each(|col_j| {
226 (0..ct_out.rows()).for_each(|row_i| {
227 module.vec_znx_add_scalar_inplace(&mut pt_want.data, 0, row_i, &pt_scalar, 0);
228
229 // mul with sk[col_j-1]
230 if col_j > 0 {
231 module.vec_znx_dft(&mut pt_dft, 0, &pt_want.data, 0);
232 module.svp_apply_inplace(&mut pt_dft, 0, &sk_out_dft.data, col_j - 1);
233 module.vec_znx_idft_tmp_a(&mut pt_big, 0, &mut pt_dft, 0);
234 module.vec_znx_big_normalize(basek, &mut pt_want.data, 0, &pt_big, 0, scratch.borrow());
235 }
236
237 ct_out.get_row(&module, row_i, col_j, &mut ct_glwe_fourier);
238
239 ct_glwe_fourier.decrypt(&module, &mut pt_have, &sk_out_dft, scratch.borrow());
240
241 module.vec_znx_sub_ab_inplace(&mut pt_have.data, 0, &pt_want.data, 0);
242
243 let noise_have: f64 = pt_have.data.std(0, basek).log2();
244 let noise_want: f64 = noise_ggsw_keyswitch(
245 module.n() as f64,
246 basek,
247 col_j,
248 var_xs,
249 0f64,
250 sigma * sigma,
251 0f64,
252 rank as f64,
253 k,
254 k,
255 );
256
257 println!("{} {}", noise_have, noise_want);
258
259 assert!(
260 (noise_have - noise_want).abs() <= 0.1,
261 "{} {}",
262 noise_have,
263 noise_want
264 );
265
266 pt_want.data.zero();
267 });
268 });
269}
270
271fn test_keyswitch_inplace(log_n: usize, basek: usize, k: usize, rank: usize, sigma: f64) {
272 let module: Module<FFT64> = Module::<FFT64>::new(1 << log_n);
273 let rows: usize = (k + basek - 1) / basek;
274
275 let mut ct: GGSWCiphertext<Vec<u8>, FFT64> = GGSWCiphertext::alloc(&module, basek, k, rows, rank);
276 let mut tsk: TensorKey<Vec<u8>, FFT64> = TensorKey::alloc(&module, basek, k, rows, rank);
277 let mut ksk: GLWESwitchingKey<Vec<u8>, FFT64> = GLWESwitchingKey::alloc(&module, basek, k, rows, rank, rank);
278 let mut pt_have: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::alloc(&module, basek, k);
279 let mut pt_want: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::alloc(&module, basek, k);
280 let mut pt_scalar: ScalarZnx<Vec<u8>> = module.new_scalar_znx(1);
281
282 let mut source_xs: Source = Source::new([0u8; 32]);
283 let mut source_xe: Source = Source::new([0u8; 32]);
284 let mut source_xa: Source = Source::new([0u8; 32]);
285
286 let mut scratch: ScratchOwned = ScratchOwned::new(
287 GGSWCiphertext::encrypt_sk_scratch_space(&module, basek, k, rank)
288 | GLWECiphertextFourier::decrypt_scratch_space(&module, basek, k)
289 | GLWESwitchingKey::encrypt_sk_scratch_space(&module, basek, k, rank)
290 | TensorKey::generate_from_sk_scratch_space(&module, basek, k, rank)
291 | GGSWCiphertext::keyswitch_inplace_scratch_space(&module, basek, ct.k(), ksk.k(), tsk.k(), rank),
292 );
293
294 let var_xs: f64 = 0.5;
295
296 let mut sk_in: SecretKey<Vec<u8>> = SecretKey::alloc(&module, rank);
297 sk_in.fill_ternary_prob(var_xs, &mut source_xs);
298
299 let mut sk_in_dft: SecretKeyFourier<Vec<u8>, FFT64> = SecretKeyFourier::alloc(&module, rank);
300 sk_in_dft.dft(&module, &sk_in);
301
302 let mut sk_out: SecretKey<Vec<u8>> = SecretKey::alloc(&module, rank);
303 sk_out.fill_ternary_prob(var_xs, &mut source_xs);
304
305 let mut sk_out_dft: SecretKeyFourier<Vec<u8>, FFT64> = SecretKeyFourier::alloc(&module, rank);
306 sk_out_dft.dft(&module, &sk_out);
307
308 ksk.generate_from_sk(
309 &module,
310 &sk_in,
311 &sk_out_dft,
312 &mut source_xa,
313 &mut source_xe,
314 sigma,
315 scratch.borrow(),
316 );
317 tsk.generate_from_sk(
318 &module,
319 &sk_out_dft,
320 &mut source_xa,
321 &mut source_xe,
322 sigma,
323 scratch.borrow(),
324 );
325
326 pt_scalar.fill_ternary_hw(0, module.n(), &mut source_xs);
327
328 ct.encrypt_sk(
329 &module,
330 &pt_scalar,
331 &sk_in_dft,
332 &mut source_xa,
333 &mut source_xe,
334 sigma,
335 scratch.borrow(),
336 );
337
338 ct.keyswitch_inplace(&module, &ksk, &tsk, scratch.borrow());
339
340 let mut ct_glwe_fourier: GLWECiphertextFourier<Vec<u8>, FFT64> = GLWECiphertextFourier::alloc(&module, basek, k, rank);
341 let mut pt_dft: VecZnxDft<Vec<u8>, FFT64> = module.new_vec_znx_dft(1, ct.size());
342 let mut pt_big: VecZnxBig<Vec<u8>, FFT64> = module.new_vec_znx_big(1, ct.size());
343
344 (0..ct.rank() + 1).for_each(|col_j| {
345 (0..ct.rows()).for_each(|row_i| {
346 module.vec_znx_add_scalar_inplace(&mut pt_want.data, 0, row_i, &pt_scalar, 0);
347
348 // mul with sk[col_j-1]
349 if col_j > 0 {
350 module.vec_znx_dft(&mut pt_dft, 0, &pt_want.data, 0);
351 module.svp_apply_inplace(&mut pt_dft, 0, &sk_out_dft.data, col_j - 1);
352 module.vec_znx_idft_tmp_a(&mut pt_big, 0, &mut pt_dft, 0);
353 module.vec_znx_big_normalize(basek, &mut pt_want.data, 0, &pt_big, 0, scratch.borrow());
354 }
355
356 ct.get_row(&module, row_i, col_j, &mut ct_glwe_fourier);
357
358 ct_glwe_fourier.decrypt(&module, &mut pt_have, &sk_out_dft, scratch.borrow());
359
360 module.vec_znx_sub_ab_inplace(&mut pt_have.data, 0, &pt_want.data, 0);
361
362 let noise_have: f64 = pt_have.data.std(0, basek).log2();
363 let noise_want: f64 = noise_ggsw_keyswitch(
364 module.n() as f64,
365 basek,
366 col_j,
367 var_xs,
368 0f64,
369 sigma * sigma,
370 0f64,
371 rank as f64,
372 k,
373 k,
374 );
375
376 println!("{} {}", noise_have, noise_want);
377
378 assert!(
379 (noise_have - noise_want).abs() <= 0.1,
380 "{} {}",
381 noise_have,
382 noise_want
383 );
384
385 pt_want.data.zero();
386 });
387 });
388}
389
390pub(crate) fn noise_ggsw_keyswitch(
391 n: f64,
392 basek: usize,
393 col: usize,
394 var_xs: f64,
395 var_a_err: f64,
396 var_gct_err_lhs: f64,
397 var_gct_err_rhs: f64,
398 rank: f64,
399 a_logq: usize,
400 b_logq: usize,
401) -> f64 {
402 let var_si_x_sj: f64 = n * var_xs * var_xs;
403
404 // Initial KS for col = 0
405 let mut noise: f64 = var_noise_gglwe_product(
406 n,
407 basek,
408 var_xs,
409 var_xs,
410 var_a_err,
411 var_gct_err_lhs,
412 var_gct_err_rhs,
413 rank,
414 a_logq,
415 b_logq,
416 );
417
418 // Other GGSW reconstruction for col > 0
419 if col > 0 {
420 noise += var_noise_gglwe_product(
421 n,
422 basek,
423 var_xs,
424 var_si_x_sj,
425 var_a_err + 1f64 / 12.0,
426 var_gct_err_lhs,
427 var_gct_err_rhs,
428 rank,
429 a_logq,
430 b_logq,
431 );
432 noise += n * noise * var_xs * 0.5;
433 }
434
435 noise = noise.sqrt();
436 noise.log2().min(-1.0) // max noise is [-2^{-1}, 2^{-1}]
437}
438
439fn test_automorphism(p: i64, log_n: usize, basek: usize, k: usize, rank: usize, sigma: f64) {
440 let module: Module<FFT64> = Module::<FFT64>::new(1 << log_n);
441 let rows: usize = (k + basek - 1) / basek;
442
443 let mut ct_in: GGSWCiphertext<Vec<u8>, FFT64> = GGSWCiphertext::alloc(&module, basek, k, rows, rank);
444 let mut ct_out: GGSWCiphertext<Vec<u8>, FFT64> = GGSWCiphertext::alloc(&module, basek, k, rows, rank);
445 let mut tensor_key: TensorKey<Vec<u8>, FFT64> = TensorKey::alloc(&module, basek, k, rows, rank);
446 let mut auto_key: AutomorphismKey<Vec<u8>, FFT64> = AutomorphismKey::alloc(&module, basek, k, rows, rank);
447 let mut pt_have: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::alloc(&module, basek, k);
448 let mut pt_want: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::alloc(&module, basek, k);
449 let mut pt_scalar: ScalarZnx<Vec<u8>> = module.new_scalar_znx(1);
450
451 let mut source_xs: Source = Source::new([0u8; 32]);
452 let mut source_xe: Source = Source::new([0u8; 32]);
453 let mut source_xa: Source = Source::new([0u8; 32]);
454
455 let mut scratch: ScratchOwned = ScratchOwned::new(
456 GGSWCiphertext::encrypt_sk_scratch_space(&module, basek, k, rank)
457 | GLWECiphertextFourier::decrypt_scratch_space(&module, basek, k)
458 | AutomorphismKey::generate_from_sk_scratch_space(&module, basek, k, rank)
459 | TensorKey::generate_from_sk_scratch_space(&module, basek, k, rank)
460 | GGSWCiphertext::automorphism_scratch_space(
461 &module,
462 basek,
463 ct_out.k(),
464 ct_in.k(),
465 auto_key.k(),
466 tensor_key.k(),
467 rank,
468 ),
469 );
470
471 let var_xs: f64 = 0.5;
472
473 let mut sk: SecretKey<Vec<u8>> = SecretKey::alloc(&module, rank);
474 sk.fill_ternary_prob(var_xs, &mut source_xs);
475
476 let mut sk_dft: SecretKeyFourier<Vec<u8>, FFT64> = SecretKeyFourier::alloc(&module, rank);
477 sk_dft.dft(&module, &sk);
478
479 auto_key.generate_from_sk(
480 &module,
481 p,
482 &sk,
483 &mut source_xa,
484 &mut source_xe,
485 sigma,
486 scratch.borrow(),
487 );
488 tensor_key.generate_from_sk(
489 &module,
490 &sk_dft,
491 &mut source_xa,
492 &mut source_xe,
493 sigma,
494 scratch.borrow(),
495 );
496
497 pt_scalar.fill_ternary_hw(0, module.n(), &mut source_xs);
498
499 ct_in.encrypt_sk(
500 &module,
501 &pt_scalar,
502 &sk_dft,
503 &mut source_xa,
504 &mut source_xe,
505 sigma,
506 scratch.borrow(),
507 );
508
509 ct_out.automorphism(&module, &ct_in, &auto_key, &tensor_key, scratch.borrow());
510
511 module.scalar_znx_automorphism_inplace(p, &mut pt_scalar, 0);
512
513 let mut ct_glwe_fourier: GLWECiphertextFourier<Vec<u8>, FFT64> = GLWECiphertextFourier::alloc(&module, basek, k, rank);
514 let mut pt_dft: VecZnxDft<Vec<u8>, FFT64> = module.new_vec_znx_dft(1, ct_out.size());
515 let mut pt_big: VecZnxBig<Vec<u8>, FFT64> = module.new_vec_znx_big(1, ct_out.size());
516
517 (0..ct_out.rank() + 1).for_each(|col_j| {
518 (0..ct_out.rows()).for_each(|row_i| {
519 module.vec_znx_add_scalar_inplace(&mut pt_want.data, 0, row_i, &pt_scalar, 0);
520
521 // mul with sk[col_j-1]
522 if col_j > 0 {
523 module.vec_znx_dft(&mut pt_dft, 0, &pt_want.data, 0);
524 module.svp_apply_inplace(&mut pt_dft, 0, &sk_dft.data, col_j - 1);
525 module.vec_znx_idft_tmp_a(&mut pt_big, 0, &mut pt_dft, 0);
526 module.vec_znx_big_normalize(basek, &mut pt_want.data, 0, &pt_big, 0, scratch.borrow());
527 }
528
529 ct_out.get_row(&module, row_i, col_j, &mut ct_glwe_fourier);
530
531 ct_glwe_fourier.decrypt(&module, &mut pt_have, &sk_dft, scratch.borrow());
532
533 module.vec_znx_sub_ab_inplace(&mut pt_have.data, 0, &pt_want.data, 0);
534
535 let noise_have: f64 = pt_have.data.std(0, basek).log2();
536 let noise_want: f64 = noise_ggsw_keyswitch(
537 module.n() as f64,
538 basek,
539 col_j,
540 var_xs,
541 0f64,
542 sigma * sigma,
543 0f64,
544 rank as f64,
545 k,
546 k,
547 );
548
549 assert!(
550 (noise_have - noise_want).abs() <= 0.1,
551 "{} {}",
552 noise_have,
553 noise_want
554 );
555
556 pt_want.data.zero();
557 });
558 });
559}
560
561fn test_automorphism_inplace(p: i64, log_n: usize, basek: usize, k: usize, rank: usize, sigma: f64) {
562 let module: Module<FFT64> = Module::<FFT64>::new(1 << log_n);
563 let rows: usize = (k + basek - 1) / basek;
564
565 let mut ct: GGSWCiphertext<Vec<u8>, FFT64> = GGSWCiphertext::alloc(&module, basek, k, rows, rank);
566 let mut tensor_key: TensorKey<Vec<u8>, FFT64> = TensorKey::alloc(&module, basek, k, rows, rank);
567 let mut auto_key: AutomorphismKey<Vec<u8>, FFT64> = AutomorphismKey::alloc(&module, basek, k, rows, rank);
568 let mut pt_have: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::alloc(&module, basek, k);
569 let mut pt_want: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::alloc(&module, basek, k);
570 let mut pt_scalar: ScalarZnx<Vec<u8>> = module.new_scalar_znx(1);
571
572 let mut source_xs: Source = Source::new([0u8; 32]);
573 let mut source_xe: Source = Source::new([0u8; 32]);
574 let mut source_xa: Source = Source::new([0u8; 32]);
575
576 let mut scratch: ScratchOwned = ScratchOwned::new(
577 GGSWCiphertext::encrypt_sk_scratch_space(&module, basek, k, rank)
578 | GLWECiphertextFourier::decrypt_scratch_space(&module, basek, k)
579 | AutomorphismKey::generate_from_sk_scratch_space(&module, basek, k, rank)
580 | TensorKey::generate_from_sk_scratch_space(&module, basek, k, rank)
581 | GGSWCiphertext::automorphism_inplace_scratch_space(&module, basek, ct.k(), auto_key.k(), tensor_key.k(), rank),
582 );
583
584 let var_xs: f64 = 0.5;
585
586 let mut sk: SecretKey<Vec<u8>> = SecretKey::alloc(&module, rank);
587 sk.fill_ternary_prob(var_xs, &mut source_xs);
588
589 let mut sk_dft: SecretKeyFourier<Vec<u8>, FFT64> = SecretKeyFourier::alloc(&module, rank);
590 sk_dft.dft(&module, &sk);
591
592 auto_key.generate_from_sk(
593 &module,
594 p,
595 &sk,
596 &mut source_xa,
597 &mut source_xe,
598 sigma,
599 scratch.borrow(),
600 );
601 tensor_key.generate_from_sk(
602 &module,
603 &sk_dft,
604 &mut source_xa,
605 &mut source_xe,
606 sigma,
607 scratch.borrow(),
608 );
609
610 pt_scalar.fill_ternary_hw(0, module.n(), &mut source_xs);
611
612 ct.encrypt_sk(
613 &module,
614 &pt_scalar,
615 &sk_dft,
616 &mut source_xa,
617 &mut source_xe,
618 sigma,
619 scratch.borrow(),
620 );
621
622 ct.automorphism_inplace(&module, &auto_key, &tensor_key, scratch.borrow());
623
624 module.scalar_znx_automorphism_inplace(p, &mut pt_scalar, 0);
625
626 let mut ct_glwe_fourier: GLWECiphertextFourier<Vec<u8>, FFT64> = GLWECiphertextFourier::alloc(&module, basek, k, rank);
627 let mut pt_dft: VecZnxDft<Vec<u8>, FFT64> = module.new_vec_znx_dft(1, ct.size());
628 let mut pt_big: VecZnxBig<Vec<u8>, FFT64> = module.new_vec_znx_big(1, ct.size());
629
630 (0..ct.rank() + 1).for_each(|col_j| {
631 (0..ct.rows()).for_each(|row_i| {
632 module.vec_znx_add_scalar_inplace(&mut pt_want.data, 0, row_i, &pt_scalar, 0);
633
634 // mul with sk[col_j-1]
635 if col_j > 0 {
636 module.vec_znx_dft(&mut pt_dft, 0, &pt_want.data, 0);
637 module.svp_apply_inplace(&mut pt_dft, 0, &sk_dft.data, col_j - 1);
638 module.vec_znx_idft_tmp_a(&mut pt_big, 0, &mut pt_dft, 0);
639 module.vec_znx_big_normalize(basek, &mut pt_want.data, 0, &pt_big, 0, scratch.borrow());
640 }
641
642 ct.get_row(&module, row_i, col_j, &mut ct_glwe_fourier);
643
644 ct_glwe_fourier.decrypt(&module, &mut pt_have, &sk_dft, scratch.borrow());
645
646 module.vec_znx_sub_ab_inplace(&mut pt_have.data, 0, &pt_want.data, 0);
647
648 let noise_have: f64 = pt_have.data.std(0, basek).log2();
649 let noise_want: f64 = noise_ggsw_keyswitch(
650 module.n() as f64,
651 basek,
652 col_j,
653 var_xs,
654 0f64,
655 sigma * sigma,
656 0f64,
657 rank as f64,
658 k,
659 k,
660 );
661
662 assert!(
663 (noise_have - noise_want).abs() <= 0.1,
664 "{} {}",
665 noise_have,
666 noise_want
667 );
668
669 pt_want.data.zero();
670 });
671 });
672}
673
674fn test_external_product(log_n: usize, basek: usize, k_ggsw: usize, rank: usize, sigma: f64) {
675 let module: Module<FFT64> = Module::<FFT64>::new(1 << log_n);
676
677 let rows: usize = (k_ggsw + basek - 1) / basek;
678
679 let mut ct_ggsw_rhs: GGSWCiphertext<Vec<u8>, FFT64> = GGSWCiphertext::alloc(&module, basek, k_ggsw, rows, rank);
680 let mut ct_ggsw_lhs_in: GGSWCiphertext<Vec<u8>, FFT64> = GGSWCiphertext::alloc(&module, basek, k_ggsw, rows, rank);
681 let mut ct_ggsw_lhs_out: GGSWCiphertext<Vec<u8>, FFT64> = GGSWCiphertext::alloc(&module, basek, k_ggsw, rows, rank);
682 let mut pt_ggsw_lhs: ScalarZnx<Vec<u8>> = module.new_scalar_znx(1);
683 let mut pt_ggsw_rhs: ScalarZnx<Vec<u8>> = module.new_scalar_znx(1);
684
685 let mut source_xs: Source = Source::new([0u8; 32]);
686 let mut source_xe: Source = Source::new([0u8; 32]);
687 let mut source_xa: Source = Source::new([0u8; 32]);
688
689 pt_ggsw_lhs.fill_ternary_prob(0, 0.5, &mut source_xs);
690
691 let k: usize = 1;
692
693 pt_ggsw_rhs.to_mut().raw_mut()[k] = 1; //X^{k}
694
695 let mut scratch: ScratchOwned = ScratchOwned::new(
696 GLWECiphertextFourier::decrypt_scratch_space(&module, basek, k)
697 | GGSWCiphertext::encrypt_sk_scratch_space(&module, basek, k, rank)
698 | GGSWCiphertext::external_product_scratch_space(
699 &module,
700 basek,
701 ct_ggsw_lhs_out.k(),
702 ct_ggsw_lhs_in.k(),
703 ct_ggsw_rhs.k(),
704 rank,
705 ),
706 );
707
708 let mut sk: SecretKey<Vec<u8>> = SecretKey::alloc(&module, rank);
709 sk.fill_ternary_prob(0.5, &mut source_xs);
710
711 let mut sk_dft: SecretKeyFourier<Vec<u8>, FFT64> = SecretKeyFourier::alloc(&module, rank);
712 sk_dft.dft(&module, &sk);
713
714 ct_ggsw_rhs.encrypt_sk(
715 &module,
716 &pt_ggsw_rhs,
717 &sk_dft,
718 &mut source_xa,
719 &mut source_xe,
720 sigma,
721 scratch.borrow(),
722 );
723
724 ct_ggsw_lhs_in.encrypt_sk(
725 &module,
726 &pt_ggsw_lhs,
727 &sk_dft,
728 &mut source_xa,
729 &mut source_xe,
730 sigma,
731 scratch.borrow(),
732 );
733
734 ct_ggsw_lhs_out.external_product(&module, &ct_ggsw_lhs_in, &ct_ggsw_rhs, scratch.borrow());
735
736 let mut ct_glwe_fourier: GLWECiphertextFourier<Vec<u8>, FFT64> = GLWECiphertextFourier::alloc(&module, basek, k_ggsw, rank);
737 let mut pt: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::alloc(&module, basek, k_ggsw);
738 let mut pt_dft: VecZnxDft<Vec<u8>, FFT64> = module.new_vec_znx_dft(1, ct_ggsw_lhs_out.size());
739 let mut pt_big: VecZnxBig<Vec<u8>, FFT64> = module.new_vec_znx_big(1, ct_ggsw_lhs_out.size());
740 let mut pt_want: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::alloc(&module, basek, k_ggsw);
741
742 module.vec_znx_rotate_inplace(k as i64, &mut pt_ggsw_lhs, 0);
743
744 (0..ct_ggsw_lhs_out.rank() + 1).for_each(|col_j| {
745 (0..ct_ggsw_lhs_out.rows()).for_each(|row_i| {
746 module.vec_znx_add_scalar_inplace(&mut pt_want.data, 0, row_i, &pt_ggsw_lhs, 0);
747
748 if col_j > 0 {
749 module.vec_znx_dft(&mut pt_dft, 0, &pt_want.data, 0);
750 module.svp_apply_inplace(&mut pt_dft, 0, &sk_dft.data, col_j - 1);
751 module.vec_znx_idft_tmp_a(&mut pt_big, 0, &mut pt_dft, 0);
752 module.vec_znx_big_normalize(basek, &mut pt_want.data, 0, &pt_big, 0, scratch.borrow());
753 }
754
755 ct_ggsw_lhs_out.get_row(&module, row_i, col_j, &mut ct_glwe_fourier);
756 ct_glwe_fourier.decrypt(&module, &mut pt, &sk_dft, scratch.borrow());
757
758 module.vec_znx_sub_ab_inplace(&mut pt.data, 0, &pt_want.data, 0);
759
760 let noise_have: f64 = pt.data.std(0, basek).log2();
761
762 let var_gct_err_lhs: f64 = sigma * sigma;
763 let var_gct_err_rhs: f64 = 0f64;
764
765 let var_msg: f64 = 1f64 / module.n() as f64; // X^{k}
766 let var_a0_err: f64 = sigma * sigma;
767 let var_a1_err: f64 = 1f64 / 12f64;
768
769 let noise_want: f64 = noise_ggsw_product(
770 module.n() as f64,
771 basek,
772 0.5,
773 var_msg,
774 var_a0_err,
775 var_a1_err,
776 var_gct_err_lhs,
777 var_gct_err_rhs,
778 rank as f64,
779 k_ggsw,
780 k_ggsw,
781 );
782
783 assert!(
784 (noise_have - noise_want).abs() <= 0.1,
785 "have: {} want: {}",
786 noise_have,
787 noise_want
788 );
789
790 pt_want.data.zero();
791 });
792 });
793}
794
795fn test_external_product_inplace(log_n: usize, basek: usize, k_ggsw: usize, rank: usize, sigma: f64) {
796 let module: Module<FFT64> = Module::<FFT64>::new(1 << log_n);
797 let rows: usize = (k_ggsw + basek - 1) / basek;
798
799 let mut ct_ggsw_rhs: GGSWCiphertext<Vec<u8>, FFT64> = GGSWCiphertext::alloc(&module, basek, k_ggsw, rows, rank);
800 let mut ct_ggsw_lhs: GGSWCiphertext<Vec<u8>, FFT64> = GGSWCiphertext::alloc(&module, basek, k_ggsw, rows, rank);
801 let mut pt_ggsw_lhs: ScalarZnx<Vec<u8>> = module.new_scalar_znx(1);
802 let mut pt_ggsw_rhs: ScalarZnx<Vec<u8>> = module.new_scalar_znx(1);
803
804 let mut source_xs: Source = Source::new([0u8; 32]);
805 let mut source_xe: Source = Source::new([0u8; 32]);
806 let mut source_xa: Source = Source::new([0u8; 32]);
807
808 pt_ggsw_lhs.fill_ternary_prob(0, 0.5, &mut source_xs);
809
810 let k: usize = 1;
811
812 pt_ggsw_rhs.to_mut().raw_mut()[k] = 1; //X^{k}
813
814 let mut scratch: ScratchOwned = ScratchOwned::new(
815 GLWESwitchingKey::encrypt_sk_scratch_space(&module, basek, k, rank)
816 | GLWECiphertextFourier::decrypt_scratch_space(&module, basek, k)
817 | GGSWCiphertext::encrypt_sk_scratch_space(&module, basek, k, rank)
818 | GGSWCiphertext::external_product_inplace_scratch_space(&module, basek, ct_ggsw_lhs.k(), ct_ggsw_rhs.k(), rank),
819 );
820
821 let mut sk: SecretKey<Vec<u8>> = SecretKey::alloc(&module, rank);
822 sk.fill_ternary_prob(0.5, &mut source_xs);
823
824 let mut sk_dft: SecretKeyFourier<Vec<u8>, FFT64> = SecretKeyFourier::alloc(&module, rank);
825 sk_dft.dft(&module, &sk);
826
827 ct_ggsw_rhs.encrypt_sk(
828 &module,
829 &pt_ggsw_rhs,
830 &sk_dft,
831 &mut source_xa,
832 &mut source_xe,
833 sigma,
834 scratch.borrow(),
835 );
836
837 ct_ggsw_lhs.encrypt_sk(
838 &module,
839 &pt_ggsw_lhs,
840 &sk_dft,
841 &mut source_xa,
842 &mut source_xe,
843 sigma,
844 scratch.borrow(),
845 );
846
847 ct_ggsw_lhs.external_product_inplace(&module, &ct_ggsw_rhs, scratch.borrow());
848
849 let mut ct_glwe_fourier: GLWECiphertextFourier<Vec<u8>, FFT64> = GLWECiphertextFourier::alloc(&module, basek, k_ggsw, rank);
850 let mut pt: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::alloc(&module, basek, k_ggsw);
851 let mut pt_dft: VecZnxDft<Vec<u8>, FFT64> = module.new_vec_znx_dft(1, ct_ggsw_lhs.size());
852 let mut pt_big: VecZnxBig<Vec<u8>, FFT64> = module.new_vec_znx_big(1, ct_ggsw_lhs.size());
853 let mut pt_want: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::alloc(&module, basek, k_ggsw);
854
855 module.vec_znx_rotate_inplace(k as i64, &mut pt_ggsw_lhs, 0);
856
857 (0..ct_ggsw_lhs.rank() + 1).for_each(|col_j| {
858 (0..ct_ggsw_lhs.rows()).for_each(|row_i| {
859 module.vec_znx_add_scalar_inplace(&mut pt_want.data, 0, row_i, &pt_ggsw_lhs, 0);
860
861 if col_j > 0 {
862 module.vec_znx_dft(&mut pt_dft, 0, &pt_want.data, 0);
863 module.svp_apply_inplace(&mut pt_dft, 0, &sk_dft.data, col_j - 1);
864 module.vec_znx_idft_tmp_a(&mut pt_big, 0, &mut pt_dft, 0);
865 module.vec_znx_big_normalize(basek, &mut pt_want.data, 0, &pt_big, 0, scratch.borrow());
866 }
867
868 ct_ggsw_lhs.get_row(&module, row_i, col_j, &mut ct_glwe_fourier);
869 ct_glwe_fourier.decrypt(&module, &mut pt, &sk_dft, scratch.borrow());
870
871 module.vec_znx_sub_ab_inplace(&mut pt.data, 0, &pt_want.data, 0);
872
873 let noise_have: f64 = pt.data.std(0, basek).log2();
874
875 let var_gct_err_lhs: f64 = sigma * sigma;
876 let var_gct_err_rhs: f64 = 0f64;
877
878 let var_msg: f64 = 1f64 / module.n() as f64; // X^{k}
879 let var_a0_err: f64 = sigma * sigma;
880 let var_a1_err: f64 = 1f64 / 12f64;
881
882 let noise_want: f64 = noise_ggsw_product(
883 module.n() as f64,
884 basek,
885 0.5,
886 var_msg,
887 var_a0_err,
888 var_a1_err,
889 var_gct_err_lhs,
890 var_gct_err_rhs,
891 rank as f64,
892 k_ggsw,
893 k_ggsw,
894 );
895
896 assert!(
897 (noise_have - noise_want).abs() <= 0.1,
898 "have: {} want: {}",
899 noise_have,
900 noise_want
901 );
902
903 pt_want.data.zero();
904 });
905 });
906}
907pub(crate) fn noise_ggsw_product(
908 n: f64,
909 basek: usize,
910 var_xs: f64,
911 var_msg: f64,
912 var_a0_err: f64,
913 var_a1_err: f64,
914 var_gct_err_lhs: f64,
915 var_gct_err_rhs: f64,
916 rank: f64,
917 a_logq: usize,
918 b_logq: usize,
919) -> f64 {
920 let a_logq: usize = a_logq.min(b_logq);
921 let a_cols: usize = (a_logq + basek - 1) / basek;
922
923 let b_scale = 2.0f64.powi(b_logq as i32);
924 let a_scale: f64 = 2.0f64.powi((b_logq - a_logq) as i32);
925
926 let base: f64 = (1 << (basek)) as f64;
927 let var_base: f64 = base * base / 12f64;
928
929 // lhs = a_cols * n * (var_base * var_gct_err_lhs + var_e_a * var_msg * p^2)
930 // rhs = a_cols * n * var_base * var_gct_err_rhs * var_xs
931 let mut noise: f64 = (rank + 1.0) * (a_cols as f64) * n * var_base * (var_gct_err_lhs + var_xs * var_gct_err_rhs);
932 noise += var_msg * var_a0_err * a_scale * a_scale * n;
933 noise += var_msg * var_a1_err * a_scale * a_scale * n * var_xs * rank;
934 noise = noise.sqrt();
935 noise /= b_scale;
936 noise.log2().min(-1.0) // max noise is [-2^{-1}, 2^{-1}]
937}