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