arnaucube
arnaucube/poulpyPublic Self-hosted repository
poulpy/core/src/test_fft64/glwe.rs
core/src/test_fft64/glwe.rsRaw
  1use base2k::{
  2    Decoding, Encoding, FFT64, FillUniform, Module, ScalarZnx, ScalarZnxAlloc, ScratchOwned, Stats, VecZnxOps, VecZnxToMut,
  3    ZnxViewMut, 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::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(|rank_in| {
 47        (1..4).for_each(|rank_out| {
 48            println!("test keyswitch rank_in: {} rank_out: {}", rank_in, rank_out);
 49            test_keyswitch(12, 12, 60, 45, 60, rank_in, rank_out, 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
 78fn test_encrypt_sk(log_n: usize, basek: usize, k_ct: usize, k_pt: usize, sigma: f64, rank: usize) {
 79    let module: Module<FFT64> = Module::<FFT64>::new(1 << log_n);
 80
 81    let mut ct: GLWECiphertext<Vec<u8>> = GLWECiphertext::new(&module, basek, k_ct, rank);
 82    let mut pt: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::new(&module, basek, k_pt);
 83
 84    let mut source_xs: Source = Source::new([0u8; 32]);
 85    let mut source_xe: Source = Source::new([0u8; 32]);
 86    let mut source_xa: Source = Source::new([0u8; 32]);
 87
 88    let mut scratch: ScratchOwned = ScratchOwned::new(
 89        GLWECiphertext::encrypt_sk_scratch_space(&module, ct.size()) | GLWECiphertext::decrypt_scratch_space(&module, ct.size()),
 90    );
 91
 92    let mut sk: SecretKey<Vec<u8>> = SecretKey::new(&module, rank);
 93    sk.fill_ternary_prob(0.5, &mut source_xs);
 94
 95    let mut sk_dft: SecretKeyFourier<Vec<u8>, FFT64> = SecretKeyFourier::new(&module, rank);
 96    sk_dft.dft(&module, &sk);
 97
 98    let mut data_want: Vec<i64> = vec![0i64; module.n()];
 99
100    data_want
101        .iter_mut()
102        .for_each(|x| *x = source_xa.next_i64() & 0xFF);
103
104    pt.data.encode_vec_i64(0, basek, k_pt, &data_want, 10);
105
106    ct.encrypt_sk(
107        &module,
108        &pt,
109        &sk_dft,
110        &mut source_xa,
111        &mut source_xe,
112        sigma,
113        scratch.borrow(),
114    );
115
116    pt.data.zero();
117
118    ct.decrypt(&module, &mut pt, &sk_dft, scratch.borrow());
119
120    let mut data_have: Vec<i64> = vec![0i64; module.n()];
121
122    pt.data
123        .decode_vec_i64(0, basek, pt.size() * basek, &mut data_have);
124
125    // TODO: properly assert the decryption noise through std(dec(ct) - pt)
126    let scale: f64 = (1 << (pt.size() * basek - k_pt)) as f64;
127    izip!(data_want.iter(), data_have.iter()).for_each(|(a, b)| {
128        let b_scaled = (*b as f64) / scale;
129        assert!(
130            (*a as f64 - b_scaled).abs() < 0.1,
131            "{} {}",
132            *a as f64,
133            b_scaled
134        )
135    });
136}
137
138fn test_encrypt_zero_sk(log_n: usize, basek: usize, k_ct: usize, sigma: f64, rank: usize) {
139    let module: Module<FFT64> = Module::<FFT64>::new(1 << log_n);
140
141    let mut pt: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::new(&module, basek, k_ct);
142
143    let mut source_xs: Source = Source::new([0u8; 32]);
144    let mut source_xe: Source = Source::new([1u8; 32]);
145    let mut source_xa: Source = Source::new([0u8; 32]);
146
147    let mut sk: SecretKey<Vec<u8>> = SecretKey::new(&module, rank);
148    sk.fill_ternary_prob(0.5, &mut source_xs);
149    let mut sk_dft: SecretKeyFourier<Vec<u8>, FFT64> = SecretKeyFourier::new(&module, rank);
150    sk_dft.dft(&module, &sk);
151
152    let mut ct_dft: GLWECiphertextFourier<Vec<u8>, FFT64> = GLWECiphertextFourier::new(&module, basek, k_ct, rank);
153
154    let mut scratch: ScratchOwned = ScratchOwned::new(
155        GLWECiphertextFourier::decrypt_scratch_space(&module, ct_dft.size())
156            | GLWECiphertextFourier::encrypt_sk_scratch_space(&module, rank, ct_dft.size()),
157    );
158
159    ct_dft.encrypt_zero_sk(
160        &module,
161        &sk_dft,
162        &mut source_xa,
163        &mut source_xe,
164        sigma,
165        scratch.borrow(),
166    );
167    ct_dft.decrypt(&module, &mut pt, &sk_dft, scratch.borrow());
168
169    assert!((sigma - pt.data.std(0, basek) * (k_ct as f64).exp2()) <= 0.2);
170}
171
172fn test_encrypt_pk(log_n: usize, basek: usize, k_ct: usize, k_pk: usize, sigma: f64, rank: usize) {
173    let module: Module<FFT64> = Module::<FFT64>::new(1 << log_n);
174
175    let mut ct: GLWECiphertext<Vec<u8>> = GLWECiphertext::new(&module, basek, k_ct, rank);
176    let mut pt_want: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::new(&module, basek, k_ct);
177
178    let mut source_xs: Source = Source::new([0u8; 32]);
179    let mut source_xe: Source = Source::new([0u8; 32]);
180    let mut source_xa: Source = Source::new([0u8; 32]);
181    let mut source_xu: Source = Source::new([0u8; 32]);
182
183    let mut sk: SecretKey<Vec<u8>> = SecretKey::new(&module, rank);
184    sk.fill_ternary_prob(0.5, &mut source_xs);
185    let mut sk_dft: SecretKeyFourier<Vec<u8>, FFT64> = SecretKeyFourier::new(&module, rank);
186    sk_dft.dft(&module, &sk);
187
188    let mut pk: GLWEPublicKey<Vec<u8>, FFT64> = GLWEPublicKey::new(&module, basek, k_pk, rank);
189    pk.generate(&module, &sk_dft, &mut source_xa, &mut source_xe, sigma);
190
191    let mut scratch: ScratchOwned = ScratchOwned::new(
192        GLWECiphertext::encrypt_sk_scratch_space(&module, ct.size())
193            | GLWECiphertext::decrypt_scratch_space(&module, ct.size())
194            | GLWECiphertext::encrypt_pk_scratch_space(&module, pk.size()),
195    );
196
197    let mut data_want: Vec<i64> = vec![0i64; module.n()];
198
199    data_want
200        .iter_mut()
201        .for_each(|x| *x = source_xa.next_i64() & 0);
202
203    pt_want.data.encode_vec_i64(0, basek, k_ct, &data_want, 10);
204
205    ct.encrypt_pk(
206        &module,
207        &pt_want,
208        &pk,
209        &mut source_xu,
210        &mut source_xe,
211        sigma,
212        scratch.borrow(),
213    );
214
215    let mut pt_have: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::new(&module, basek, k_ct);
216
217    ct.decrypt(&module, &mut pt_have, &sk_dft, scratch.borrow());
218
219    module.vec_znx_sub_ab_inplace(&mut pt_want, 0, &pt_have, 0);
220
221    let noise_have: f64 = pt_want.data.std(0, basek).log2();
222    let noise_want: f64 = ((((rank as f64) + 1.0) * module.n() as f64 * 0.5 * sigma * sigma).sqrt()).log2() - (k_ct as f64);
223
224    assert!(
225        (noise_have - noise_want).abs() < 0.2,
226        "{} {}",
227        noise_have,
228        noise_want
229    );
230}
231
232fn test_keyswitch(
233    log_n: usize,
234    basek: usize,
235    k_keyswitch: usize,
236    k_ct_in: usize,
237    k_ct_out: usize,
238    rank_in: usize,
239    rank_out: usize,
240    sigma: f64,
241) {
242    let module: Module<FFT64> = Module::<FFT64>::new(1 << log_n);
243    let rows: usize = (k_ct_in + basek - 1) / basek;
244
245    let mut ksk: GLWESwitchingKey<Vec<u8>, FFT64> = GLWESwitchingKey::new(&module, basek, k_keyswitch, rows, rank_in, rank_out);
246    let mut ct_in: GLWECiphertext<Vec<u8>> = GLWECiphertext::new(&module, basek, k_ct_in, rank_in);
247    let mut ct_out: GLWECiphertext<Vec<u8>> = GLWECiphertext::new(&module, basek, k_ct_out, rank_out);
248    let mut pt_want: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::new(&module, basek, k_ct_in);
249    let mut pt_have: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::new(&module, basek, k_ct_out);
250
251    let mut source_xs: Source = Source::new([0u8; 32]);
252    let mut source_xe: Source = Source::new([0u8; 32]);
253    let mut source_xa: Source = Source::new([0u8; 32]);
254
255    // Random input plaintext
256    pt_want
257        .data
258        .fill_uniform(basek, 0, pt_want.size(), &mut source_xa);
259
260    let mut scratch: ScratchOwned = ScratchOwned::new(
261        GLWESwitchingKey::encrypt_sk_scratch_space(&module, rank_in, ksk.size())
262            | GLWECiphertext::decrypt_scratch_space(&module, ct_out.size())
263            | GLWECiphertext::encrypt_sk_scratch_space(&module, ct_in.size())
264            | GLWECiphertext::keyswitch_scratch_space(
265                &module,
266                ct_out.size(),
267                rank_out,
268                ct_in.size(),
269                rank_in,
270                ksk.size(),
271            ),
272    );
273
274    let mut sk_in: SecretKey<Vec<u8>> = SecretKey::new(&module, rank_in);
275    sk_in.fill_ternary_prob(0.5, &mut source_xs);
276
277    let mut sk_in_dft: SecretKeyFourier<Vec<u8>, FFT64> = SecretKeyFourier::new(&module, rank_in);
278    sk_in_dft.dft(&module, &sk_in);
279
280    let mut sk_out: SecretKey<Vec<u8>> = SecretKey::new(&module, rank_out);
281    sk_out.fill_ternary_prob(0.5, &mut source_xs);
282
283    let mut sk_out_dft: SecretKeyFourier<Vec<u8>, FFT64> = SecretKeyFourier::new(&module, rank_out);
284    sk_out_dft.dft(&module, &sk_out);
285
286    ksk.encrypt_sk(
287        &module,
288        &sk_in,
289        &sk_out_dft,
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_dft,
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_dft, scratch.borrow());
309
310    module.vec_znx_sub_ab_inplace(&mut pt_have, 0, &pt_want, 0);
311
312    let noise_have: f64 = pt_have.data.std(0, basek).log2();
313    let noise_want: f64 = noise_gglwe_product(
314        module.n() as f64,
315        basek,
316        0.5,
317        0.5,
318        0f64,
319        sigma * sigma,
320        0f64,
321        rank_in as f64,
322        k_ct_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, k_ct: usize, rank: usize, sigma: f64) {
335    let module: Module<FFT64> = Module::<FFT64>::new(1 << log_n);
336    let rows: usize = (k_ct + basek - 1) / basek;
337
338    let mut ct_grlwe: GLWESwitchingKey<Vec<u8>, FFT64> = GLWESwitchingKey::new(&module, basek, k_ksk, rows, rank, rank);
339    let mut ct_rlwe: GLWECiphertext<Vec<u8>> = GLWECiphertext::new(&module, basek, k_ct, rank);
340    let mut pt_want: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::new(&module, basek, k_ct);
341    let mut pt_have: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::new(&module, basek, k_ct);
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, rank, ct_grlwe.size())
354            | GLWECiphertext::decrypt_scratch_space(&module, ct_rlwe.size())
355            | GLWECiphertext::encrypt_sk_scratch_space(&module, ct_rlwe.size())
356            | GLWECiphertext::keyswitch_inplace_scratch_space(&module, ct_rlwe.size(), rank, ct_grlwe.size()),
357    );
358
359    let mut sk0: SecretKey<Vec<u8>> = SecretKey::new(&module, rank);
360    sk0.fill_ternary_prob(0.5, &mut source_xs);
361
362    let mut sk0_dft: SecretKeyFourier<Vec<u8>, FFT64> = SecretKeyFourier::new(&module, rank);
363    sk0_dft.dft(&module, &sk0);
364
365    let mut sk1: SecretKey<Vec<u8>> = SecretKey::new(&module, rank);
366    sk1.fill_ternary_prob(0.5, &mut source_xs);
367
368    let mut sk1_dft: SecretKeyFourier<Vec<u8>, FFT64> = SecretKeyFourier::new(&module, rank);
369    sk1_dft.dft(&module, &sk1);
370
371    ct_grlwe.encrypt_sk(
372        &module,
373        &sk0,
374        &sk1_dft,
375        &mut source_xa,
376        &mut source_xe,
377        sigma,
378        scratch.borrow(),
379    );
380
381    ct_rlwe.encrypt_sk(
382        &module,
383        &pt_want,
384        &sk0_dft,
385        &mut source_xa,
386        &mut source_xe,
387        sigma,
388        scratch.borrow(),
389    );
390
391    ct_rlwe.keyswitch_inplace(&module, &ct_grlwe, scratch.borrow());
392
393    ct_rlwe.decrypt(&module, &mut pt_have, &sk1_dft, scratch.borrow());
394
395    module.vec_znx_sub_ab_inplace(&mut pt_have, 0, &pt_want, 0);
396
397    let noise_have: f64 = pt_have.data.std(0, basek).log2();
398    let noise_want: f64 = noise_gglwe_product(
399        module.n() as f64,
400        basek,
401        0.5,
402        0.5,
403        0f64,
404        sigma * sigma,
405        0f64,
406        rank as f64,
407        k_ct,
408        k_ksk,
409    );
410
411    assert!(
412        (noise_have - noise_want).abs() <= 0.1,
413        "{} {}",
414        noise_have,
415        noise_want
416    );
417}
418
419#[test]
420fn automorphism() {
421    (1..4).for_each(|rank| {
422        println!("test automorphism rank: {}", rank);
423        test_automorphism(12, 12, 1, 60, 45, 60, rank, 3.2);
424    });
425}
426
427fn test_automorphism(
428    log_n: usize,
429    basek: usize,
430    p: i64,
431    k_autokey: usize,
432    k_ct_in: usize,
433    k_ct_out: usize,
434    rank: usize,
435    sigma: f64,
436) {
437    let module: Module<FFT64> = Module::<FFT64>::new(1 << log_n);
438    let rows: usize = (k_ct_in + basek - 1) / basek;
439
440    let mut autokey: AutomorphismKey<Vec<u8>, FFT64> = AutomorphismKey::new(&module, basek, p, k_autokey, rows, rank);
441    let mut ct_in: GLWECiphertext<Vec<u8>> = GLWECiphertext::new(&module, basek, k_ct_in, rank);
442    let mut ct_out: GLWECiphertext<Vec<u8>> = GLWECiphertext::new(&module, basek, k_ct_out, rank);
443    let mut pt_want: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::new(&module, basek, k_ct_in);
444    let mut pt_have: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::new(&module, basek, k_ct_out);
445
446    let mut source_xs: Source = Source::new([0u8; 32]);
447    let mut source_xe: Source = Source::new([0u8; 32]);
448    let mut source_xa: Source = Source::new([0u8; 32]);
449
450    // Random input plaintext
451    // pt_want
452    // .data
453    // .fill_uniform(basek, 0, pt_want.size(), &mut source_xa);
454
455    pt_want
456        .to_mut()
457        .at_mut(0, 1)
458        .iter_mut()
459        .enumerate()
460        .for_each(|(i, x)| {
461            *x = i as i64;
462        });
463
464    let mut scratch: ScratchOwned = ScratchOwned::new(
465        GLWESwitchingKey::encrypt_sk_scratch_space(&module, rank, autokey.size())
466            | GLWECiphertext::decrypt_scratch_space(&module, ct_out.size())
467            | GLWECiphertext::encrypt_sk_scratch_space(&module, ct_in.size())
468            | GLWECiphertext::automorphism_scratch_space(&module, ct_out.size(), rank, ct_in.size(), autokey.size()),
469    );
470
471    let mut sk: SecretKey<Vec<u8>> = SecretKey::new(&module, rank);
472    sk.fill_ternary_prob(0.5, &mut source_xs);
473
474    let mut sk_dft: SecretKeyFourier<Vec<u8>, FFT64> = SecretKeyFourier::new(&module, rank);
475    sk_dft.dft(&module, &sk);
476
477    autokey.encrypt_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
499    ct_out.decrypt(&module, &mut pt_have, &sk_dft, scratch.borrow());
500
501    module.vec_znx_automorphism_inplace(p, &mut pt_want, 0);
502
503    module.vec_znx_sub_ab_inplace(&mut pt_have, 0, &pt_want, 0);
504
505    let noise_have: f64 = pt_have.data.std(0, basek).log2();
506    let noise_want: f64 = noise_gglwe_product(
507        module.n() as f64,
508        basek,
509        0.5,
510        0.5,
511        0f64,
512        sigma * sigma,
513        0f64,
514        rank as f64,
515        k_ct_in,
516        k_autokey,
517    );
518
519    assert!(
520        (noise_have - noise_want).abs() <= 0.1,
521        "{} {}",
522        noise_have,
523        noise_want
524    );
525}
526
527fn test_external_product(log_n: usize, basek: usize, k_ggsw: usize, k_ct_in: usize, k_ct_out: usize, rank: usize, sigma: f64) {
528    let module: Module<FFT64> = Module::<FFT64>::new(1 << log_n);
529
530    let rows: usize = (k_ct_in + basek - 1) / basek;
531
532    let mut ct_rgsw: GGSWCiphertext<Vec<u8>, FFT64> = GGSWCiphertext::new(&module, basek, k_ggsw, rows, rank);
533    let mut ct_rlwe_in: GLWECiphertext<Vec<u8>> = GLWECiphertext::new(&module, basek, k_ct_in, rank);
534    let mut ct_rlwe_out: GLWECiphertext<Vec<u8>> = GLWECiphertext::new(&module, basek, k_ct_out, rank);
535    let mut pt_rgsw: ScalarZnx<Vec<u8>> = module.new_scalar_znx(1);
536    let mut pt_want: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::new(&module, basek, k_ct_in);
537    let mut pt_have: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::new(&module, basek, k_ct_out);
538
539    let mut source_xs: Source = Source::new([0u8; 32]);
540    let mut source_xe: Source = Source::new([0u8; 32]);
541    let mut source_xa: Source = Source::new([0u8; 32]);
542
543    // Random input plaintext
544    pt_want
545        .data
546        .fill_uniform(basek, 0, pt_want.size(), &mut source_xa);
547
548    pt_want.to_mut().at_mut(0, 0)[1] = 1;
549
550    let k: usize = 1;
551
552    pt_rgsw.raw_mut()[k] = 1; // X^{k}
553
554    let mut scratch: ScratchOwned = ScratchOwned::new(
555        GGSWCiphertext::encrypt_sk_scratch_space(&module, rank, ct_rgsw.size())
556            | GLWECiphertext::decrypt_scratch_space(&module, ct_rlwe_out.size())
557            | GLWECiphertext::encrypt_sk_scratch_space(&module, ct_rlwe_in.size())
558            | GLWECiphertext::external_product_scratch_space(
559                &module,
560                ct_rlwe_out.size(),
561                ct_rlwe_in.size(),
562                ct_rgsw.size(),
563                rank,
564            ),
565    );
566
567    let mut sk: SecretKey<Vec<u8>> = SecretKey::new(&module, rank);
568    sk.fill_ternary_prob(0.5, &mut source_xs);
569
570    let mut sk_dft: SecretKeyFourier<Vec<u8>, FFT64> = SecretKeyFourier::new(&module, rank);
571    sk_dft.dft(&module, &sk);
572
573    ct_rgsw.encrypt_sk(
574        &module,
575        &pt_rgsw,
576        &sk_dft,
577        &mut source_xa,
578        &mut source_xe,
579        sigma,
580        scratch.borrow(),
581    );
582
583    ct_rlwe_in.encrypt_sk(
584        &module,
585        &pt_want,
586        &sk_dft,
587        &mut source_xa,
588        &mut source_xe,
589        sigma,
590        scratch.borrow(),
591    );
592
593    ct_rlwe_out.external_product(&module, &ct_rlwe_in, &ct_rgsw, scratch.borrow());
594
595    ct_rlwe_out.decrypt(&module, &mut pt_have, &sk_dft, scratch.borrow());
596
597    module.vec_znx_rotate_inplace(k as i64, &mut pt_want, 0);
598
599    module.vec_znx_sub_ab_inplace(&mut pt_have, 0, &pt_want, 0);
600
601    let noise_have: f64 = pt_have.data.std(0, basek).log2();
602
603    let var_gct_err_lhs: f64 = sigma * sigma;
604    let var_gct_err_rhs: f64 = 0f64;
605
606    let var_msg: f64 = 1f64 / module.n() as f64; // X^{k}
607    let var_a0_err: f64 = sigma * sigma;
608    let var_a1_err: f64 = 1f64 / 12f64;
609
610    let noise_want: f64 = noise_ggsw_product(
611        module.n() as f64,
612        basek,
613        0.5,
614        var_msg,
615        var_a0_err,
616        var_a1_err,
617        var_gct_err_lhs,
618        var_gct_err_rhs,
619        rank as f64,
620        k_ct_in,
621        k_ggsw,
622    );
623
624    assert!(
625        (noise_have - noise_want).abs() <= 0.1,
626        "{} {}",
627        noise_have,
628        noise_want
629    );
630}
631
632fn test_external_product_inplace(log_n: usize, basek: usize, k_ggsw: usize, k_ct: usize, rank: usize, sigma: f64) {
633    let module: Module<FFT64> = Module::<FFT64>::new(1 << log_n);
634    let rows: usize = (k_ct + basek - 1) / basek;
635
636    let mut ct_rgsw: GGSWCiphertext<Vec<u8>, FFT64> = GGSWCiphertext::new(&module, basek, k_ggsw, rows, rank);
637    let mut ct_rlwe: GLWECiphertext<Vec<u8>> = GLWECiphertext::new(&module, basek, k_ct, rank);
638    let mut pt_rgsw: ScalarZnx<Vec<u8>> = module.new_scalar_znx(1);
639    let mut pt_want: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::new(&module, basek, k_ct);
640    let mut pt_have: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::new(&module, basek, k_ct);
641
642    let mut source_xs: Source = Source::new([0u8; 32]);
643    let mut source_xe: Source = Source::new([0u8; 32]);
644    let mut source_xa: Source = Source::new([0u8; 32]);
645
646    // Random input plaintext
647    pt_want
648        .data
649        .fill_uniform(basek, 0, pt_want.size(), &mut source_xa);
650
651    pt_want.to_mut().at_mut(0, 0)[1] = 1;
652
653    let k: usize = 1;
654
655    pt_rgsw.raw_mut()[k] = 1; // X^{k}
656
657    let mut scratch: ScratchOwned = ScratchOwned::new(
658        GGSWCiphertext::encrypt_sk_scratch_space(&module, rank, ct_rgsw.size())
659            | GLWECiphertext::decrypt_scratch_space(&module, ct_rlwe.size())
660            | GLWECiphertext::encrypt_sk_scratch_space(&module, ct_rlwe.size())
661            | GLWECiphertext::external_product_inplace_scratch_space(&module, ct_rlwe.size(), ct_rgsw.size(), rank),
662    );
663
664    let mut sk: SecretKey<Vec<u8>> = SecretKey::new(&module, rank);
665    sk.fill_ternary_prob(0.5, &mut source_xs);
666
667    let mut sk_dft: SecretKeyFourier<Vec<u8>, FFT64> = SecretKeyFourier::new(&module, rank);
668    sk_dft.dft(&module, &sk);
669
670    ct_rgsw.encrypt_sk(
671        &module,
672        &pt_rgsw,
673        &sk_dft,
674        &mut source_xa,
675        &mut source_xe,
676        sigma,
677        scratch.borrow(),
678    );
679
680    ct_rlwe.encrypt_sk(
681        &module,
682        &pt_want,
683        &sk_dft,
684        &mut source_xa,
685        &mut source_xe,
686        sigma,
687        scratch.borrow(),
688    );
689
690    ct_rlwe.external_product_inplace(&module, &ct_rgsw, scratch.borrow());
691
692    ct_rlwe.decrypt(&module, &mut pt_have, &sk_dft, scratch.borrow());
693
694    module.vec_znx_rotate_inplace(k as i64, &mut pt_want, 0);
695
696    module.vec_znx_sub_ab_inplace(&mut pt_have, 0, &pt_want, 0);
697
698    let noise_have: f64 = pt_have.data.std(0, basek).log2();
699
700    let var_gct_err_lhs: f64 = sigma * sigma;
701    let var_gct_err_rhs: f64 = 0f64;
702
703    let var_msg: f64 = 1f64 / module.n() as f64; // X^{k}
704    let var_a0_err: f64 = sigma * sigma;
705    let var_a1_err: f64 = 1f64 / 12f64;
706
707    let noise_want: f64 = noise_ggsw_product(
708        module.n() as f64,
709        basek,
710        0.5,
711        var_msg,
712        var_a0_err,
713        var_a1_err,
714        var_gct_err_lhs,
715        var_gct_err_rhs,
716        rank as f64,
717        k_ct,
718        k_ggsw,
719    );
720
721    assert!(
722        (noise_have - noise_want).abs() <= 0.1,
723        "{} {}",
724        noise_have,
725        noise_want
726    );
727}