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