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