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