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    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}