poulpy/core/src/test_fft64/glwe.rs
core/src/test_fft64/glwe.rsRaw
1use backend::{
2 Decoding, Encoding, FFT64, FillUniform, Module, ScalarZnx, ScalarZnxAlloc, ScratchOwned, Stats, VecZnxOps, ZnxViewMut,
3 ZnxZero,
4};
5use itertools::izip;
6use sampling::source::Source;
7
8use crate::{
9 GGSWCiphertext, GLWECiphertext, GLWECiphertextFourier, GLWEPlaintext, GLWEPublicKey, GLWESecret, Infos,
10 automorphism::AutomorphismKey,
11 div_ceil,
12 keyswitch_key::GLWESwitchingKey,
13 test_fft64::{log2_std_noise_gglwe_product, noise_ggsw_product},
14};
15
16#[test]
17fn encrypt_sk() {
18 let log_n: usize = 8;
19 (1..4).for_each(|rank| {
20 println!("test encrypt_sk rank: {}", rank);
21 test_encrypt_sk(log_n, 8, 54, 30, 3.2, rank);
22 });
23}
24
25#[test]
26fn encrypt_zero_sk() {
27 let log_n: usize = 8;
28 (1..4).for_each(|rank| {
29 println!("test encrypt_zero_sk rank: {}", rank);
30 test_encrypt_zero_sk(log_n, 8, 64, 3.2, rank);
31 });
32}
33
34#[test]
35fn encrypt_pk() {
36 let log_n: usize = 8;
37 (1..4).for_each(|rank| {
38 println!("test encrypt_pk rank: {}", rank);
39 test_encrypt_pk(log_n, 8, 64, 64, 3.2, rank)
40 });
41}
42
43#[test]
44fn keyswitch() {
45 let log_n: usize = 8;
46 let basek: usize = 12;
47 let k_in: usize = 45;
48 let digits: usize = div_ceil(k_in, basek);
49 (1..4).for_each(|rank_in| {
50 (1..4).for_each(|rank_out| {
51 (1..digits + 1).for_each(|di| {
52 let k_ksk: usize = k_in + basek * di;
53 let k_out: usize = k_ksk; // better capture noise
54 println!(
55 "test keyswitch digits: {} rank_in: {} rank_out: {}",
56 di, rank_in, rank_out
57 );
58 test_keyswitch(log_n, basek, k_out, k_in, k_ksk, di, rank_in, rank_out, 3.2);
59 })
60 });
61 });
62}
63
64#[test]
65fn keyswitch_inplace() {
66 let log_n: usize = 8;
67 let basek: usize = 12;
68 let k_ct: usize = 45;
69 let digits: usize = div_ceil(k_ct, basek);
70 (1..4).for_each(|rank| {
71 (1..digits + 1).for_each(|di| {
72 let k_ksk: usize = k_ct + basek * di;
73 println!("test keyswitch_inplace digits: {} rank: {}", di, rank);
74 test_keyswitch_inplace(log_n, basek, k_ct, k_ksk, di, rank, 3.2);
75 });
76 });
77}
78
79#[test]
80fn external_product() {
81 let log_n: usize = 8;
82 let basek: usize = 12;
83 let k_in: usize = 45;
84 let digits: usize = div_ceil(k_in, basek);
85 (1..4).for_each(|rank| {
86 (1..digits + 1).for_each(|di| {
87 let k_ggsw: usize = k_in + basek * di;
88 let k_out: usize = k_ggsw; // Better capture noise
89 println!("test external_product digits: {} rank: {}", di, rank);
90 test_external_product(log_n, basek, k_out, k_in, k_ggsw, di, rank, 3.2);
91 });
92 });
93}
94
95#[test]
96fn external_product_inplace() {
97 let log_n: usize = 8;
98 let basek: usize = 12;
99 let k_ct: usize = 60;
100 let digits: usize = div_ceil(k_ct, basek);
101 (1..4).for_each(|rank| {
102 (1..digits + 1).for_each(|di| {
103 let k_ggsw: usize = k_ct + basek * di;
104 println!("test external_product digits: {} rank: {}", di, rank);
105 test_external_product_inplace(log_n, basek, k_ct, k_ggsw, di, rank, 3.2);
106 });
107 });
108}
109
110#[test]
111fn automorphism_inplace() {
112 let log_n: usize = 8;
113 let basek: usize = 12;
114 let k_ct: usize = 60;
115 let digits: usize = div_ceil(k_ct, basek);
116 (1..4).for_each(|rank| {
117 (1..digits + 1).for_each(|di| {
118 let k_ksk: usize = k_ct + basek * di;
119 println!("test automorphism_inplace digits: {} rank: {}", di, rank);
120 test_automorphism_inplace(log_n, basek, -5, k_ct, k_ksk, di, rank, 3.2);
121 });
122 });
123}
124
125#[test]
126fn automorphism() {
127 let log_n: usize = 8;
128 let basek: usize = 12;
129 let k_in: usize = 60;
130 let digits: usize = div_ceil(k_in, basek);
131 (1..4).for_each(|rank| {
132 (1..digits + 1).for_each(|di| {
133 let k_ksk: usize = k_in + basek * di;
134 let k_out: usize = k_ksk; // Better capture noise.
135 println!("test automorphism digits: {} rank: {}", di, rank);
136 test_automorphism(log_n, basek, -5, k_out, k_in, k_ksk, di, rank, 3.2);
137 })
138 });
139}
140
141fn test_encrypt_sk(log_n: usize, basek: usize, k_ct: usize, k_pt: usize, sigma: f64, rank: usize) {
142 let module: Module<FFT64> = Module::<FFT64>::new(1 << log_n);
143
144 let mut ct: GLWECiphertext<Vec<u8>> = GLWECiphertext::alloc(&module, basek, k_ct, rank);
145 let mut pt: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::alloc(&module, basek, k_pt);
146
147 let mut source_xs: Source = Source::new([0u8; 32]);
148 let mut source_xe: Source = Source::new([0u8; 32]);
149 let mut source_xa: Source = Source::new([0u8; 32]);
150
151 let mut scratch: ScratchOwned = ScratchOwned::new(
152 GLWECiphertext::encrypt_sk_scratch_space(&module, basek, ct.k())
153 | GLWECiphertext::decrypt_scratch_space(&module, basek, ct.k()),
154 );
155
156 let mut sk: GLWESecret<Vec<u8>, FFT64> = GLWESecret::alloc(&module, rank);
157 sk.fill_ternary_prob(&module, 0.5, &mut source_xs);
158
159 let mut data_want: Vec<i64> = vec![0i64; module.n()];
160
161 data_want
162 .iter_mut()
163 .for_each(|x| *x = source_xa.next_i64() & 0xFF);
164
165 pt.data.encode_vec_i64(0, basek, k_pt, &data_want, 10);
166
167 ct.encrypt_sk(
168 &module,
169 &pt,
170 &sk,
171 &mut source_xa,
172 &mut source_xe,
173 sigma,
174 scratch.borrow(),
175 );
176
177 pt.data.zero();
178
179 ct.decrypt(&module, &mut pt, &sk, scratch.borrow());
180
181 let mut data_have: Vec<i64> = vec![0i64; module.n()];
182
183 pt.data
184 .decode_vec_i64(0, basek, pt.size() * basek, &mut data_have);
185
186 // TODO: properly assert the decryption noise through std(dec(ct) - pt)
187 let scale: f64 = (1 << (pt.size() * basek - k_pt)) as f64;
188 izip!(data_want.iter(), data_have.iter()).for_each(|(a, b)| {
189 let b_scaled = (*b as f64) / scale;
190 assert!(
191 (*a as f64 - b_scaled).abs() < 0.1,
192 "{} {}",
193 *a as f64,
194 b_scaled
195 )
196 });
197}
198
199fn test_encrypt_zero_sk(log_n: usize, basek: usize, k_ct: usize, sigma: f64, rank: usize) {
200 let module: Module<FFT64> = Module::<FFT64>::new(1 << log_n);
201
202 let mut pt: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::alloc(&module, basek, k_ct);
203
204 let mut source_xs: Source = Source::new([0u8; 32]);
205 let mut source_xe: Source = Source::new([1u8; 32]);
206 let mut source_xa: Source = Source::new([0u8; 32]);
207
208 let mut sk: GLWESecret<Vec<u8>, FFT64> = GLWESecret::alloc(&module, rank);
209 sk.fill_ternary_prob(&module, 0.5, &mut source_xs);
210
211 let mut ct_dft: GLWECiphertextFourier<Vec<u8>, FFT64> = GLWECiphertextFourier::alloc(&module, basek, k_ct, rank);
212
213 let mut scratch: ScratchOwned = ScratchOwned::new(
214 GLWECiphertextFourier::decrypt_scratch_space(&module, basek, k_ct)
215 | GLWECiphertextFourier::encrypt_sk_scratch_space(&module, basek, k_ct, rank),
216 );
217
218 ct_dft.encrypt_zero_sk(
219 &module,
220 &sk,
221 &mut source_xa,
222 &mut source_xe,
223 sigma,
224 scratch.borrow(),
225 );
226 ct_dft.decrypt(&module, &mut pt, &sk, scratch.borrow());
227
228 assert!((sigma - pt.data.std(0, basek) * (k_ct as f64).exp2()) <= 0.2);
229}
230
231fn test_encrypt_pk(log_n: usize, basek: usize, k_ct: usize, k_pk: usize, sigma: f64, rank: usize) {
232 let module: Module<FFT64> = Module::<FFT64>::new(1 << log_n);
233
234 let mut ct: GLWECiphertext<Vec<u8>> = GLWECiphertext::alloc(&module, basek, k_ct, rank);
235 let mut pt_want: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::alloc(&module, basek, k_ct);
236
237 let mut source_xs: Source = Source::new([0u8; 32]);
238 let mut source_xe: Source = Source::new([0u8; 32]);
239 let mut source_xa: Source = Source::new([0u8; 32]);
240 let mut source_xu: Source = Source::new([0u8; 32]);
241
242 let mut sk: GLWESecret<Vec<u8>, FFT64> = GLWESecret::alloc(&module, rank);
243 sk.fill_ternary_prob(&module, 0.5, &mut source_xs);
244
245 let mut pk: GLWEPublicKey<Vec<u8>, FFT64> = GLWEPublicKey::alloc(&module, basek, k_pk, rank);
246 pk.generate_from_sk(&module, &sk, &mut source_xa, &mut source_xe, sigma);
247
248 let mut scratch: ScratchOwned = ScratchOwned::new(
249 GLWECiphertext::encrypt_sk_scratch_space(&module, basek, ct.k())
250 | GLWECiphertext::decrypt_scratch_space(&module, basek, ct.k())
251 | GLWECiphertext::encrypt_pk_scratch_space(&module, basek, pk.k()),
252 );
253
254 let mut data_want: Vec<i64> = vec![0i64; module.n()];
255
256 data_want
257 .iter_mut()
258 .for_each(|x| *x = source_xa.next_i64() & 0);
259
260 pt_want.data.encode_vec_i64(0, basek, k_ct, &data_want, 10);
261
262 ct.encrypt_pk(
263 &module,
264 &pt_want,
265 &pk,
266 &mut source_xu,
267 &mut source_xe,
268 sigma,
269 scratch.borrow(),
270 );
271
272 let mut pt_have: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::alloc(&module, basek, k_ct);
273
274 ct.decrypt(&module, &mut pt_have, &sk, scratch.borrow());
275
276 module.vec_znx_sub_ab_inplace(&mut pt_want.data, 0, &pt_have.data, 0);
277
278 let noise_have: f64 = pt_want.data.std(0, basek).log2();
279 let noise_want: f64 = ((((rank as f64) + 1.0) * module.n() as f64 * 0.5 * sigma * sigma).sqrt()).log2() - (k_ct as f64);
280
281 assert!(
282 (noise_have - noise_want).abs() < 0.2,
283 "{} {}",
284 noise_have,
285 noise_want
286 );
287}
288
289fn test_keyswitch(
290 log_n: usize,
291 basek: usize,
292 k_out: usize,
293 k_in: usize,
294 k_ksk: usize,
295 digits: usize,
296 rank_in: usize,
297 rank_out: usize,
298 sigma: f64,
299) {
300 let module: Module<FFT64> = Module::<FFT64>::new(1 << log_n);
301
302 let rows: usize = div_ceil(k_in, basek * digits);
303
304 let mut ksk: GLWESwitchingKey<Vec<u8>, FFT64> =
305 GLWESwitchingKey::alloc(&module, basek, k_ksk, rows, digits, rank_in, rank_out);
306 let mut ct_in: GLWECiphertext<Vec<u8>> = GLWECiphertext::alloc(&module, basek, k_in, rank_in);
307 let mut ct_out: GLWECiphertext<Vec<u8>> = GLWECiphertext::alloc(&module, basek, k_out, rank_out);
308 let mut pt_want: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::alloc(&module, basek, k_in);
309 let mut pt_have: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::alloc(&module, basek, k_out);
310
311 let mut source_xs: Source = Source::new([0u8; 32]);
312 let mut source_xe: Source = Source::new([0u8; 32]);
313 let mut source_xa: Source = Source::new([0u8; 32]);
314
315 pt_want
316 .data
317 .fill_uniform(basek, 0, pt_want.size(), &mut source_xa);
318
319 let mut scratch: ScratchOwned = ScratchOwned::new(
320 GLWESwitchingKey::encrypt_sk_scratch_space(&module, basek, ksk.k(), rank_out)
321 | GLWECiphertext::decrypt_scratch_space(&module, basek, ct_out.k())
322 | GLWECiphertext::encrypt_sk_scratch_space(&module, basek, ct_in.k())
323 | GLWECiphertext::keyswitch_scratch_space(
324 &module,
325 basek,
326 ct_out.k(),
327 ct_in.k(),
328 ksk.k(),
329 digits,
330 rank_in,
331 rank_out,
332 ),
333 );
334
335 let mut sk_in: GLWESecret<Vec<u8>, FFT64> = GLWESecret::alloc(&module, rank_in);
336 sk_in.fill_ternary_prob(&module, 0.5, &mut source_xs);
337
338 let mut sk_out: GLWESecret<Vec<u8>, FFT64> = GLWESecret::alloc(&module, rank_out);
339 sk_out.fill_ternary_prob(&module, 0.5, &mut source_xs);
340
341 ksk.generate_from_sk(
342 &module,
343 &sk_in,
344 &sk_out,
345 &mut source_xa,
346 &mut source_xe,
347 sigma,
348 scratch.borrow(),
349 );
350
351 ct_in.encrypt_sk(
352 &module,
353 &pt_want,
354 &sk_in,
355 &mut source_xa,
356 &mut source_xe,
357 sigma,
358 scratch.borrow(),
359 );
360
361 ct_out.keyswitch(&module, &ct_in, &ksk, scratch.borrow());
362 ct_out.decrypt(&module, &mut pt_have, &sk_out, scratch.borrow());
363
364 module.vec_znx_sub_ab_inplace(&mut pt_have.data, 0, &pt_want.data, 0);
365
366 let noise_have: f64 = pt_have.data.std(0, basek).log2();
367 let noise_want: f64 = log2_std_noise_gglwe_product(
368 module.n() as f64,
369 basek * digits,
370 0.5,
371 0.5,
372 0f64,
373 sigma * sigma,
374 0f64,
375 rank_in as f64,
376 k_in,
377 k_ksk,
378 );
379
380 println!("{} vs. {}", noise_have, noise_want);
381
382 assert!(
383 (noise_have - noise_want).abs() <= 0.5,
384 "{} {}",
385 noise_have,
386 noise_want
387 );
388}
389
390fn test_keyswitch_inplace(log_n: usize, basek: usize, k_ct: usize, k_ksk: usize, digits: usize, rank: usize, sigma: f64) {
391 let module: Module<FFT64> = Module::<FFT64>::new(1 << log_n);
392
393 let rows: usize = div_ceil(k_ct, basek * digits);
394
395 let mut ct_grlwe: GLWESwitchingKey<Vec<u8>, FFT64> = GLWESwitchingKey::alloc(&module, basek, k_ksk, rows, digits, rank, rank);
396 let mut ct_glwe: GLWECiphertext<Vec<u8>> = GLWECiphertext::alloc(&module, basek, k_ct, rank);
397 let mut pt_want: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::alloc(&module, basek, k_ct);
398 let mut pt_have: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::alloc(&module, basek, k_ct);
399
400 let mut source_xs: Source = Source::new([0u8; 32]);
401 let mut source_xe: Source = Source::new([0u8; 32]);
402 let mut source_xa: Source = Source::new([0u8; 32]);
403
404 pt_want
405 .data
406 .fill_uniform(basek, 0, pt_want.size(), &mut source_xa);
407
408 let mut scratch: ScratchOwned = ScratchOwned::new(
409 GLWESwitchingKey::encrypt_sk_scratch_space(&module, basek, ct_grlwe.k(), rank)
410 | GLWECiphertext::decrypt_scratch_space(&module, basek, ct_glwe.k())
411 | GLWECiphertext::encrypt_sk_scratch_space(&module, basek, ct_glwe.k())
412 | GLWECiphertext::keyswitch_inplace_scratch_space(&module, basek, ct_glwe.k(), ct_grlwe.k(), digits, rank),
413 );
414
415 let mut sk0: GLWESecret<Vec<u8>, FFT64> = GLWESecret::alloc(&module, rank);
416 sk0.fill_ternary_prob(&module, 0.5, &mut source_xs);
417
418 let mut sk1: GLWESecret<Vec<u8>, FFT64> = GLWESecret::alloc(&module, rank);
419 sk1.fill_ternary_prob(&module, 0.5, &mut source_xs);
420
421 ct_grlwe.generate_from_sk(
422 &module,
423 &sk0,
424 &sk1,
425 &mut source_xa,
426 &mut source_xe,
427 sigma,
428 scratch.borrow(),
429 );
430
431 ct_glwe.encrypt_sk(
432 &module,
433 &pt_want,
434 &sk0,
435 &mut source_xa,
436 &mut source_xe,
437 sigma,
438 scratch.borrow(),
439 );
440
441 ct_glwe.keyswitch_inplace(&module, &ct_grlwe, scratch.borrow());
442
443 ct_glwe.decrypt(&module, &mut pt_have, &sk1, scratch.borrow());
444
445 module.vec_znx_sub_ab_inplace(&mut pt_have.data, 0, &pt_want.data, 0);
446
447 let noise_have: f64 = pt_have.data.std(0, basek).log2();
448 let noise_want: f64 = log2_std_noise_gglwe_product(
449 module.n() as f64,
450 basek * digits,
451 0.5,
452 0.5,
453 0f64,
454 sigma * sigma,
455 0f64,
456 rank as f64,
457 k_ct,
458 k_ksk,
459 );
460
461 assert!(
462 (noise_have - noise_want).abs() <= 0.5,
463 "{} {}",
464 noise_have,
465 noise_want
466 );
467}
468
469fn test_automorphism(
470 log_n: usize,
471 basek: usize,
472 p: i64,
473 k_out: usize,
474 k_in: usize,
475 k_ksk: usize,
476 digits: usize,
477 rank: usize,
478 sigma: f64,
479) {
480 let module: Module<FFT64> = Module::<FFT64>::new(1 << log_n);
481
482 let rows: usize = div_ceil(k_in, basek * digits);
483
484 let mut autokey: AutomorphismKey<Vec<u8>, FFT64> = AutomorphismKey::alloc(&module, basek, k_ksk, rows, digits, rank);
485 let mut ct_in: GLWECiphertext<Vec<u8>> = GLWECiphertext::alloc(&module, basek, k_in, rank);
486 let mut ct_out: GLWECiphertext<Vec<u8>> = GLWECiphertext::alloc(&module, basek, k_out, rank);
487 let mut pt_want: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::alloc(&module, basek, k_in);
488 let mut pt_have: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::alloc(&module, basek, k_out);
489
490 let mut source_xs: Source = Source::new([0u8; 32]);
491 let mut source_xe: Source = Source::new([0u8; 32]);
492 let mut source_xa: Source = Source::new([0u8; 32]);
493
494 pt_want
495 .data
496 .fill_uniform(basek, 0, pt_want.size(), &mut source_xa);
497
498 let mut scratch: ScratchOwned = ScratchOwned::new(
499 AutomorphismKey::generate_from_sk_scratch_space(&module, basek, autokey.k(), rank)
500 | GLWECiphertext::decrypt_scratch_space(&module, basek, ct_out.k())
501 | GLWECiphertext::encrypt_sk_scratch_space(&module, basek, ct_in.k())
502 | GLWECiphertext::automorphism_scratch_space(
503 &module,
504 basek,
505 ct_out.k(),
506 ct_in.k(),
507 autokey.k(),
508 digits,
509 rank,
510 ),
511 );
512
513 let mut sk: GLWESecret<Vec<u8>, FFT64> = GLWESecret::alloc(&module, rank);
514 sk.fill_ternary_prob(&module, 0.5, &mut source_xs);
515
516 autokey.generate_from_sk(
517 &module,
518 p,
519 &sk,
520 &mut source_xa,
521 &mut source_xe,
522 sigma,
523 scratch.borrow(),
524 );
525
526 ct_in.encrypt_sk(
527 &module,
528 &pt_want,
529 &sk,
530 &mut source_xa,
531 &mut source_xe,
532 sigma,
533 scratch.borrow(),
534 );
535
536 ct_out.automorphism(&module, &ct_in, &autokey, scratch.borrow());
537 ct_out.decrypt(&module, &mut pt_have, &sk, scratch.borrow());
538 module.vec_znx_automorphism_inplace(p, &mut pt_want.data, 0);
539 module.vec_znx_sub_ab_inplace(&mut pt_have.data, 0, &pt_want.data, 0);
540 module.vec_znx_normalize_inplace(basek, &mut pt_have.data, 0, scratch.borrow());
541
542 let noise_have: f64 = pt_have.data.std(0, basek).log2();
543
544 println!("{}", noise_have);
545
546 let noise_want: f64 = log2_std_noise_gglwe_product(
547 module.n() as f64,
548 basek * digits,
549 0.5,
550 0.5,
551 0f64,
552 sigma * sigma,
553 0f64,
554 rank as f64,
555 k_in,
556 k_ksk,
557 );
558
559 assert!(
560 noise_have <= noise_want + 1.0,
561 "{} {}",
562 noise_have,
563 noise_want
564 );
565}
566
567fn test_automorphism_inplace(
568 log_n: usize,
569 basek: usize,
570 p: i64,
571 k_ct: usize,
572 k_ksk: usize,
573 digits: usize,
574 rank: usize,
575 sigma: f64,
576) {
577 let module: Module<FFT64> = Module::<FFT64>::new(1 << log_n);
578
579 let rows: usize = div_ceil(k_ct, basek * digits);
580
581 let mut autokey: AutomorphismKey<Vec<u8>, FFT64> = AutomorphismKey::alloc(&module, basek, k_ksk, rows, digits, rank);
582 let mut ct: GLWECiphertext<Vec<u8>> = GLWECiphertext::alloc(&module, basek, k_ct, rank);
583 let mut pt_want: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::alloc(&module, basek, k_ct);
584 let mut pt_have: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::alloc(&module, basek, k_ct);
585
586 let mut source_xs: Source = Source::new([0u8; 32]);
587 let mut source_xe: Source = Source::new([0u8; 32]);
588 let mut source_xa: Source = Source::new([0u8; 32]);
589
590 pt_want
591 .data
592 .fill_uniform(basek, 0, pt_want.size(), &mut source_xa);
593
594 let mut scratch: ScratchOwned = ScratchOwned::new(
595 AutomorphismKey::generate_from_sk_scratch_space(&module, basek, autokey.k(), rank)
596 | GLWECiphertext::decrypt_scratch_space(&module, basek, ct.k())
597 | GLWECiphertext::encrypt_sk_scratch_space(&module, basek, ct.k())
598 | GLWECiphertext::automorphism_inplace_scratch_space(&module, basek, ct.k(), autokey.k(), digits, rank),
599 );
600
601 let mut sk: GLWESecret<Vec<u8>, FFT64> = GLWESecret::alloc(&module, rank);
602 sk.fill_ternary_prob(&module, 0.5, &mut source_xs);
603
604 autokey.generate_from_sk(
605 &module,
606 p,
607 &sk,
608 &mut source_xa,
609 &mut source_xe,
610 sigma,
611 scratch.borrow(),
612 );
613
614 ct.encrypt_sk(
615 &module,
616 &pt_want,
617 &sk,
618 &mut source_xa,
619 &mut source_xe,
620 sigma,
621 scratch.borrow(),
622 );
623
624 ct.automorphism_inplace(&module, &autokey, scratch.borrow());
625 ct.decrypt(&module, &mut pt_have, &sk, scratch.borrow());
626 module.vec_znx_automorphism_inplace(p, &mut pt_want.data, 0);
627 module.vec_znx_sub_ab_inplace(&mut pt_have.data, 0, &pt_want.data, 0);
628 module.vec_znx_normalize_inplace(basek, &mut pt_have.data, 0, scratch.borrow());
629
630 let noise_have: f64 = pt_have.data.std(0, basek).log2();
631 let noise_want: f64 = log2_std_noise_gglwe_product(
632 module.n() as f64,
633 basek * digits,
634 0.5,
635 0.5,
636 0f64,
637 sigma * sigma,
638 0f64,
639 rank as f64,
640 k_ct,
641 k_ksk,
642 );
643
644 assert!(
645 (noise_have - noise_want).abs() <= 0.5,
646 "{} {}",
647 noise_have,
648 noise_want
649 );
650}
651
652fn test_external_product(
653 log_n: usize,
654 basek: usize,
655 k_out: usize,
656 k_in: usize,
657 k_ggsw: usize,
658 digits: usize,
659 rank: usize,
660 sigma: f64,
661) {
662 let module: Module<FFT64> = Module::<FFT64>::new(1 << log_n);
663
664 let rows: usize = div_ceil(k_in, digits * basek);
665
666 let mut ct_ggsw: GGSWCiphertext<Vec<u8>, FFT64> = GGSWCiphertext::alloc(&module, basek, k_ggsw, rows, digits, rank);
667 let mut ct_glwe_in: GLWECiphertext<Vec<u8>> = GLWECiphertext::alloc(&module, basek, k_in, rank);
668 let mut ct_glwe_out: GLWECiphertext<Vec<u8>> = GLWECiphertext::alloc(&module, basek, k_out, rank);
669 let mut pt_rgsw: ScalarZnx<Vec<u8>> = module.new_scalar_znx(1);
670 let mut pt_want: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::alloc(&module, basek, k_in);
671 let mut pt_have: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::alloc(&module, basek, k_out);
672
673 let mut source_xs: Source = Source::new([0u8; 32]);
674 let mut source_xe: Source = Source::new([0u8; 32]);
675 let mut source_xa: Source = Source::new([0u8; 32]);
676
677 // Random input plaintext
678 pt_want
679 .data
680 .fill_uniform(basek, 0, pt_want.size(), &mut source_xa);
681
682 pt_want.data.at_mut(0, 0)[1] = 1;
683
684 let k: usize = 1;
685
686 pt_rgsw.raw_mut()[k] = 1; // X^{k}
687
688 let mut scratch: ScratchOwned = ScratchOwned::new(
689 GGSWCiphertext::encrypt_sk_scratch_space(&module, basek, ct_ggsw.k(), rank)
690 | GLWECiphertext::decrypt_scratch_space(&module, basek, ct_glwe_out.k())
691 | GLWECiphertext::encrypt_sk_scratch_space(&module, basek, ct_glwe_in.k())
692 | GLWECiphertext::external_product_scratch_space(
693 &module,
694 basek,
695 ct_glwe_out.k(),
696 ct_glwe_in.k(),
697 ct_ggsw.k(),
698 digits,
699 rank,
700 ),
701 );
702
703 let mut sk: GLWESecret<Vec<u8>, FFT64> = GLWESecret::alloc(&module, rank);
704 sk.fill_ternary_prob(&module, 0.5, &mut source_xs);
705
706 ct_ggsw.encrypt_sk(
707 &module,
708 &pt_rgsw,
709 &sk,
710 &mut source_xa,
711 &mut source_xe,
712 sigma,
713 scratch.borrow(),
714 );
715
716 ct_glwe_in.encrypt_sk(
717 &module,
718 &pt_want,
719 &sk,
720 &mut source_xa,
721 &mut source_xe,
722 sigma,
723 scratch.borrow(),
724 );
725
726 ct_glwe_out.external_product(&module, &ct_glwe_in, &ct_ggsw, scratch.borrow());
727
728 ct_glwe_out.decrypt(&module, &mut pt_have, &sk, scratch.borrow());
729
730 module.vec_znx_rotate_inplace(k as i64, &mut pt_want.data, 0);
731
732 module.vec_znx_sub_ab_inplace(&mut pt_have.data, 0, &pt_want.data, 0);
733
734 let noise_have: f64 = pt_have.data.std(0, basek).log2();
735
736 let var_gct_err_lhs: f64 = sigma * sigma;
737 let var_gct_err_rhs: f64 = 0f64;
738
739 let var_msg: f64 = 1f64 / module.n() as f64; // X^{k}
740 let var_a0_err: f64 = sigma * sigma;
741 let var_a1_err: f64 = 1f64 / 12f64;
742
743 let noise_want: f64 = noise_ggsw_product(
744 module.n() as f64,
745 basek * digits,
746 0.5,
747 var_msg,
748 var_a0_err,
749 var_a1_err,
750 var_gct_err_lhs,
751 var_gct_err_rhs,
752 rank as f64,
753 k_in,
754 k_ggsw,
755 );
756
757 assert!(
758 (noise_have - noise_want).abs() <= 0.5,
759 "{} {}",
760 noise_have,
761 noise_want
762 );
763}
764
765fn test_external_product_inplace(log_n: usize, basek: usize, k_ct: usize, k_ggsw: usize, digits: usize, rank: usize, sigma: f64) {
766 let module: Module<FFT64> = Module::<FFT64>::new(1 << log_n);
767 let rows: usize = div_ceil(k_ct, digits * basek);
768
769 let mut ct_ggsw: GGSWCiphertext<Vec<u8>, FFT64> = GGSWCiphertext::alloc(&module, basek, k_ggsw, rows, digits, rank);
770 let mut ct_glwe: GLWECiphertext<Vec<u8>> = GLWECiphertext::alloc(&module, basek, k_ct, rank);
771 let mut pt_rgsw: ScalarZnx<Vec<u8>> = module.new_scalar_znx(1);
772 let mut pt_want: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::alloc(&module, basek, k_ct);
773 let mut pt_have: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::alloc(&module, basek, k_ct);
774
775 let mut source_xs: Source = Source::new([0u8; 32]);
776 let mut source_xe: Source = Source::new([0u8; 32]);
777 let mut source_xa: Source = Source::new([0u8; 32]);
778
779 // Random input plaintext
780 pt_want
781 .data
782 .fill_uniform(basek, 0, pt_want.size(), &mut source_xa);
783
784 pt_want.data.at_mut(0, 0)[1] = 1;
785
786 let k: usize = 1;
787
788 pt_rgsw.raw_mut()[k] = 1; // X^{k}
789
790 let mut scratch: ScratchOwned = ScratchOwned::new(
791 GGSWCiphertext::encrypt_sk_scratch_space(&module, basek, ct_ggsw.k(), rank)
792 | GLWECiphertext::decrypt_scratch_space(&module, basek, ct_glwe.k())
793 | GLWECiphertext::encrypt_sk_scratch_space(&module, basek, ct_glwe.k())
794 | GLWECiphertext::external_product_inplace_scratch_space(&module, basek, ct_glwe.k(), ct_ggsw.k(), digits, rank),
795 );
796
797 let mut sk: GLWESecret<Vec<u8>, FFT64> = GLWESecret::alloc(&module, rank);
798 sk.fill_ternary_prob(&module, 0.5, &mut source_xs);
799
800 ct_ggsw.encrypt_sk(
801 &module,
802 &pt_rgsw,
803 &sk,
804 &mut source_xa,
805 &mut source_xe,
806 sigma,
807 scratch.borrow(),
808 );
809
810 ct_glwe.encrypt_sk(
811 &module,
812 &pt_want,
813 &sk,
814 &mut source_xa,
815 &mut source_xe,
816 sigma,
817 scratch.borrow(),
818 );
819
820 ct_glwe.external_product_inplace(&module, &ct_ggsw, scratch.borrow());
821
822 ct_glwe.decrypt(&module, &mut pt_have, &sk, scratch.borrow());
823
824 module.vec_znx_rotate_inplace(k as i64, &mut pt_want.data, 0);
825
826 module.vec_znx_sub_ab_inplace(&mut pt_have.data, 0, &pt_want.data, 0);
827
828 let noise_have: f64 = pt_have.data.std(0, basek).log2();
829
830 let var_gct_err_lhs: f64 = sigma * sigma;
831 let var_gct_err_rhs: f64 = 0f64;
832
833 let var_msg: f64 = 1f64 / module.n() as f64; // X^{k}
834 let var_a0_err: f64 = sigma * sigma;
835 let var_a1_err: f64 = 1f64 / 12f64;
836
837 let noise_want: f64 = noise_ggsw_product(
838 module.n() as f64,
839 basek * digits,
840 0.5,
841 var_msg,
842 var_a0_err,
843 var_a1_err,
844 var_gct_err_lhs,
845 var_gct_err_rhs,
846 rank as f64,
847 k_ct,
848 k_ggsw,
849 );
850
851 assert!(
852 (noise_have - noise_want).abs() <= 0.5,
853 "{} {}",
854 noise_have,
855 noise_want
856 );
857}