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