arnaucube
arnaucube/poulpyPublic Self-hosted repository
poulpy/core/src/test_fft64/rgsw.rs
core/src/test_fft64/rgsw.rsRaw
  1use base2k::{
  2    FFT64, Module, ScalarZnx, ScalarZnxAlloc, ScalarZnxDftOps, ScratchOwned, Stats, VecZnxBig, VecZnxBigAlloc, VecZnxBigOps,
  3    VecZnxDft, VecZnxDftAlloc, VecZnxDftOps, VecZnxOps, VecZnxToMut, ZnxViewMut, ZnxZero,
  4};
  5use sampling::source::Source;
  6
  7use crate::{
  8    elem::{GetRow, Infos},
  9    external_product::{
 10        ExternalProduct, ExternalProductInplace, ExternalProductInplaceScratchSpace, ExternalProductScratchSpace,
 11    },
 12    ggsw::GGSWCiphertext,
 13    glwe::{GLWECiphertextFourier, GLWEPlaintext},
 14    keys::{SecretKey, SecretKeyFourier},
 15    keyswitch::{KeySwitch, KeySwitchInplace, KeySwitchInplaceScratchSpace, KeySwitchScratchSpace},
 16    keyswitch_key::GLWEKeySwitchKey,
 17    test_fft64::grlwe::noise_grlwe_rlwe_product,
 18};
 19
 20#[test]
 21fn encrypt_sk() {
 22    let module: Module<FFT64> = Module::<FFT64>::new(2048);
 23    let log_base2k: usize = 8;
 24    let log_k_ct: usize = 54;
 25    let rows: usize = 4;
 26
 27    let sigma: f64 = 3.2;
 28    let bound: f64 = sigma * 6.0;
 29
 30    let mut ct: GGSWCiphertext<Vec<u8>, FFT64> = GGSWCiphertext::new(&module, log_base2k, log_k_ct, rows);
 31    let mut pt_have: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::new(&module, log_base2k, log_k_ct);
 32    let mut pt_want: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::new(&module, log_base2k, log_k_ct);
 33    let mut pt_scalar: ScalarZnx<Vec<u8>> = module.new_scalar_znx(1);
 34
 35    let mut source_xs: Source = Source::new([0u8; 32]);
 36    let mut source_xe: Source = Source::new([0u8; 32]);
 37    let mut source_xa: Source = Source::new([0u8; 32]);
 38
 39    pt_scalar.fill_ternary_hw(0, module.n(), &mut source_xs);
 40
 41    let mut scratch: ScratchOwned = ScratchOwned::new(
 42        GGSWCiphertext::encrypt_sk_scratch_space(&module, ct.size())
 43            | GLWECiphertextFourier::decrypt_scratch_space(&module, ct.size()),
 44    );
 45
 46    let mut sk: SecretKey<Vec<u8>> = SecretKey::new(&module);
 47    sk.fill_ternary_prob(0.5, &mut source_xs);
 48
 49    let mut sk_dft: SecretKeyFourier<Vec<u8>, FFT64> = SecretKeyFourier::new(&module);
 50    sk_dft.dft(&module, &sk);
 51
 52    ct.encrypt_sk(
 53        &module,
 54        &pt_scalar,
 55        &sk_dft,
 56        &mut source_xa,
 57        &mut source_xe,
 58        sigma,
 59        bound,
 60        scratch.borrow(),
 61    );
 62
 63    let mut ct_rlwe_dft: GLWECiphertextFourier<Vec<u8>, FFT64> = GLWECiphertextFourier::new(&module, log_base2k, log_k_ct);
 64    let mut pt_dft: VecZnxDft<Vec<u8>, FFT64> = module.new_vec_znx_dft(1, ct.size());
 65    let mut pt_big: VecZnxBig<Vec<u8>, FFT64> = module.new_vec_znx_big(1, ct.size());
 66
 67    (0..ct.rank()).for_each(|col_j| {
 68        (0..ct.rows()).for_each(|row_i| {
 69            module.vec_znx_add_scalar_inplace(&mut pt_want, 0, row_i, &pt_scalar, 0);
 70
 71            if col_j == 1 {
 72                module.vec_znx_dft(&mut pt_dft, 0, &pt_want, 0);
 73                module.svp_apply_inplace(&mut pt_dft, 0, &sk_dft, 0);
 74                module.vec_znx_idft_tmp_a(&mut pt_big, 0, &mut pt_dft, 0);
 75                module.vec_znx_big_normalize(log_base2k, &mut pt_want, 0, &pt_big, 0, scratch.borrow());
 76            }
 77
 78            ct.get_row(&module, row_i, col_j, &mut ct_rlwe_dft);
 79
 80            ct_rlwe_dft.decrypt(&module, &mut pt_have, &sk_dft, scratch.borrow());
 81
 82            module.vec_znx_sub_ab_inplace(&mut pt_have, 0, &pt_want, 0);
 83
 84            let std_pt: f64 = pt_have.data.std(0, log_base2k) * (log_k_ct as f64).exp2();
 85            assert!((sigma - std_pt).abs() <= 0.2, "{} {}", sigma, std_pt);
 86
 87            pt_want.data.zero();
 88        });
 89    });
 90}
 91
 92#[test]
 93fn keyswitch() {
 94    let module: Module<FFT64> = Module::<FFT64>::new(2048);
 95    let log_base2k: usize = 12;
 96    let log_k_grlwe: usize = 60;
 97    let log_k_rgsw_in: usize = 45;
 98    let log_k_rgsw_out: usize = 45;
 99    let rows: usize = (log_k_rgsw_in + log_base2k - 1) / log_base2k;
100
101    let sigma: f64 = 3.2;
102    let bound: f64 = sigma * 6.0;
103
104    let mut ct_grlwe: GLWEKeySwitchKey<Vec<u8>, FFT64> = GLWEKeySwitchKey::new(&module, log_base2k, log_k_grlwe, rows);
105    let mut ct_rgsw_in: GGSWCiphertext<Vec<u8>, FFT64> = GGSWCiphertext::new(&module, log_base2k, log_k_rgsw_in, rows);
106    let mut ct_rgsw_out: GGSWCiphertext<Vec<u8>, FFT64> = GGSWCiphertext::new(&module, log_base2k, log_k_rgsw_out, rows);
107    let mut pt_rgsw: ScalarZnx<Vec<u8>> = module.new_scalar_znx(1);
108
109    let mut source_xs: Source = Source::new([0u8; 32]);
110    let mut source_xe: Source = Source::new([0u8; 32]);
111    let mut source_xa: Source = Source::new([0u8; 32]);
112
113    // Random input plaintext
114    pt_rgsw.fill_ternary_prob(0, 0.5, &mut source_xs);
115
116    let mut scratch: ScratchOwned = ScratchOwned::new(
117        GLWEKeySwitchKey::encrypt_sk_scratch_space(&module, ct_grlwe.size())
118            | GLWECiphertextFourier::decrypt_scratch_space(&module, ct_rgsw_out.size())
119            | GGSWCiphertext::encrypt_sk_scratch_space(&module, ct_rgsw_in.size())
120            | GGSWCiphertext::keyswitch_scratch_space(
121                &module,
122                ct_rgsw_out.size(),
123                ct_rgsw_in.size(),
124                ct_grlwe.size(),
125            ),
126    );
127
128    let mut sk0: SecretKey<Vec<u8>> = SecretKey::new(&module);
129    sk0.fill_ternary_prob(0.5, &mut source_xs);
130
131    let mut sk0_dft: SecretKeyFourier<Vec<u8>, FFT64> = SecretKeyFourier::new(&module);
132    sk0_dft.dft(&module, &sk0);
133
134    let mut sk1: SecretKey<Vec<u8>> = SecretKey::new(&module);
135    sk1.fill_ternary_prob(0.5, &mut source_xs);
136
137    let mut sk1_dft: SecretKeyFourier<Vec<u8>, FFT64> = SecretKeyFourier::new(&module);
138    sk1_dft.dft(&module, &sk1);
139
140    ct_grlwe.encrypt_sk(
141        &module,
142        &sk0.data,
143        &sk1_dft,
144        &mut source_xa,
145        &mut source_xe,
146        sigma,
147        bound,
148        scratch.borrow(),
149    );
150
151    ct_rgsw_in.encrypt_sk(
152        &module,
153        &pt_rgsw,
154        &sk0_dft,
155        &mut source_xa,
156        &mut source_xe,
157        sigma,
158        bound,
159        scratch.borrow(),
160    );
161
162    ct_rgsw_out.keyswitch(&module, &ct_rgsw_in, &ct_grlwe, scratch.borrow());
163
164    let mut ct_rlwe_dft: GLWECiphertextFourier<Vec<u8>, FFT64> = GLWECiphertextFourier::new(&module, log_base2k, log_k_rgsw_out);
165    let mut pt: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::new(&module, log_base2k, log_k_rgsw_out);
166    let mut pt_dft: VecZnxDft<Vec<u8>, FFT64> = module.new_vec_znx_dft(1, ct_rgsw_out.size());
167    let mut pt_big: VecZnxBig<Vec<u8>, FFT64> = module.new_vec_znx_big(1, ct_rgsw_out.size());
168    let mut pt_want: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::new(&module, log_base2k, log_k_rgsw_out);
169
170    (0..ct_rgsw_out.rank()).for_each(|col_j| {
171        (0..ct_rgsw_out.rows()).for_each(|row_i| {
172            module.vec_znx_add_scalar_inplace(&mut pt_want, 0, row_i, &pt_rgsw, 0);
173
174            if col_j == 1 {
175                module.vec_znx_dft(&mut pt_dft, 0, &pt_want, 0);
176                module.svp_apply_inplace(&mut pt_dft, 0, &sk0_dft, 0);
177                module.vec_znx_idft_tmp_a(&mut pt_big, 0, &mut pt_dft, 0);
178                module.vec_znx_big_normalize(log_base2k, &mut pt_want, 0, &pt_big, 0, scratch.borrow());
179            }
180
181            ct_rgsw_out.get_row(&module, row_i, col_j, &mut ct_rlwe_dft);
182            ct_rlwe_dft.decrypt(&module, &mut pt, &sk1_dft, scratch.borrow());
183
184            module.vec_znx_sub_ab_inplace(&mut pt, 0, &pt_want, 0);
185
186            let noise_have: f64 = pt.data.std(0, log_base2k).log2();
187            let noise_want: f64 = noise_grlwe_rlwe_product(
188                module.n() as f64,
189                log_base2k,
190                0.5,
191                0.5,
192                0f64,
193                sigma * sigma,
194                0f64,
195                log_k_grlwe,
196                log_k_grlwe,
197            );
198
199            assert!(
200                (noise_have - noise_want).abs() <= 0.2,
201                "have: {} want: {}",
202                noise_have,
203                noise_want
204            );
205
206            pt_want.data.zero();
207        });
208    });
209}
210
211#[test]
212fn keyswitch_inplace() {
213    let module: Module<FFT64> = Module::<FFT64>::new(2048);
214    let log_base2k: usize = 12;
215    let log_k_grlwe: usize = 60;
216    let log_k_rgsw: usize = 45;
217    let rows: usize = (log_k_rgsw + log_base2k - 1) / log_base2k;
218
219    let sigma: f64 = 3.2;
220    let bound: f64 = sigma * 6.0;
221
222    let mut ct_grlwe: GLWEKeySwitchKey<Vec<u8>, FFT64> = GLWEKeySwitchKey::new(&module, log_base2k, log_k_grlwe, rows);
223    let mut ct_rgsw: GGSWCiphertext<Vec<u8>, FFT64> = GGSWCiphertext::new(&module, log_base2k, log_k_rgsw, rows);
224    let mut pt_rgsw: ScalarZnx<Vec<u8>> = module.new_scalar_znx(1);
225
226    let mut source_xs: Source = Source::new([0u8; 32]);
227    let mut source_xe: Source = Source::new([0u8; 32]);
228    let mut source_xa: Source = Source::new([0u8; 32]);
229
230    // Random input plaintext
231    pt_rgsw.fill_ternary_prob(0, 0.5, &mut source_xs);
232
233    let mut scratch: ScratchOwned = ScratchOwned::new(
234        GLWEKeySwitchKey::encrypt_sk_scratch_space(&module, ct_grlwe.size())
235            | GLWECiphertextFourier::decrypt_scratch_space(&module, ct_rgsw.size())
236            | GGSWCiphertext::encrypt_sk_scratch_space(&module, ct_rgsw.size())
237            | GGSWCiphertext::keyswitch_inplace_scratch_space(&module, ct_rgsw.size(), ct_grlwe.size()),
238    );
239
240    let mut sk0: SecretKey<Vec<u8>> = SecretKey::new(&module);
241    sk0.fill_ternary_prob(0.5, &mut source_xs);
242
243    let mut sk0_dft: SecretKeyFourier<Vec<u8>, FFT64> = SecretKeyFourier::new(&module);
244    sk0_dft.dft(&module, &sk0);
245
246    let mut sk1: SecretKey<Vec<u8>> = SecretKey::new(&module);
247    sk1.fill_ternary_prob(0.5, &mut source_xs);
248
249    let mut sk1_dft: SecretKeyFourier<Vec<u8>, FFT64> = SecretKeyFourier::new(&module);
250    sk1_dft.dft(&module, &sk1);
251
252    ct_grlwe.encrypt_sk(
253        &module,
254        &sk0.data,
255        &sk1_dft,
256        &mut source_xa,
257        &mut source_xe,
258        sigma,
259        bound,
260        scratch.borrow(),
261    );
262
263    ct_rgsw.encrypt_sk(
264        &module,
265        &pt_rgsw,
266        &sk0_dft,
267        &mut source_xa,
268        &mut source_xe,
269        sigma,
270        bound,
271        scratch.borrow(),
272    );
273
274    ct_rgsw.keyswitch_inplace(&module, &ct_grlwe, scratch.borrow());
275
276    let mut ct_rlwe_dft: GLWECiphertextFourier<Vec<u8>, FFT64> = GLWECiphertextFourier::new(&module, log_base2k, log_k_rgsw);
277    let mut pt: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::new(&module, log_base2k, log_k_rgsw);
278    let mut pt_dft: VecZnxDft<Vec<u8>, FFT64> = module.new_vec_znx_dft(1, ct_rgsw.size());
279    let mut pt_big: VecZnxBig<Vec<u8>, FFT64> = module.new_vec_znx_big(1, ct_rgsw.size());
280    let mut pt_want: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::new(&module, log_base2k, log_k_rgsw);
281
282    (0..ct_rgsw.rank()).for_each(|col_j| {
283        (0..ct_rgsw.rows()).for_each(|row_i| {
284            module.vec_znx_add_scalar_inplace(&mut pt_want, 0, row_i, &pt_rgsw, 0);
285
286            if col_j == 1 {
287                module.vec_znx_dft(&mut pt_dft, 0, &pt_want, 0);
288                module.svp_apply_inplace(&mut pt_dft, 0, &sk0_dft, 0);
289                module.vec_znx_idft_tmp_a(&mut pt_big, 0, &mut pt_dft, 0);
290                module.vec_znx_big_normalize(log_base2k, &mut pt_want, 0, &pt_big, 0, scratch.borrow());
291            }
292
293            ct_rgsw.get_row(&module, row_i, col_j, &mut ct_rlwe_dft);
294            ct_rlwe_dft.decrypt(&module, &mut pt, &sk1_dft, scratch.borrow());
295
296            module.vec_znx_sub_ab_inplace(&mut pt, 0, &pt_want, 0);
297
298            let noise_have: f64 = pt.data.std(0, log_base2k).log2();
299            let noise_want: f64 = noise_grlwe_rlwe_product(
300                module.n() as f64,
301                log_base2k,
302                0.5,
303                0.5,
304                0f64,
305                sigma * sigma,
306                0f64,
307                log_k_grlwe,
308                log_k_grlwe,
309            );
310
311            assert!(
312                (noise_have - noise_want).abs() <= 0.2,
313                "have: {} want: {}",
314                noise_have,
315                noise_want
316            );
317
318            pt_want.data.zero();
319        });
320    });
321}
322
323#[test]
324fn external_product() {
325    let module: Module<FFT64> = Module::<FFT64>::new(2048);
326    let log_base2k: usize = 12;
327    let log_k_rgsw_rhs: usize = 60;
328    let log_k_rgsw_lhs_in: usize = 45;
329    let log_k_rgsw_lhs_out: usize = 45;
330    let rows: usize = (log_k_rgsw_lhs_in + log_base2k - 1) / log_base2k;
331
332    let sigma: f64 = 3.2;
333    let bound: f64 = sigma * 6.0;
334
335    let mut ct_rgsw_rhs: GGSWCiphertext<Vec<u8>, FFT64> = GGSWCiphertext::new(&module, log_base2k, log_k_rgsw_rhs, rows);
336    let mut ct_rgsw_lhs_in: GGSWCiphertext<Vec<u8>, FFT64> = GGSWCiphertext::new(&module, log_base2k, log_k_rgsw_lhs_in, rows);
337    let mut ct_rgsw_lhs_out: GGSWCiphertext<Vec<u8>, FFT64> = GGSWCiphertext::new(&module, log_base2k, log_k_rgsw_lhs_out, rows);
338    let mut pt_rgsw_lhs: ScalarZnx<Vec<u8>> = module.new_scalar_znx(1);
339    let mut pt_rgsw_rhs: ScalarZnx<Vec<u8>> = module.new_scalar_znx(1);
340
341    let mut source_xs: Source = Source::new([0u8; 32]);
342    let mut source_xe: Source = Source::new([0u8; 32]);
343    let mut source_xa: Source = Source::new([0u8; 32]);
344
345    // Random input plaintext
346    pt_rgsw_lhs.fill_ternary_prob(0, 0.5, &mut source_xs);
347
348    let k: usize = 1;
349
350    pt_rgsw_rhs.to_mut().raw_mut()[k] = 1; //X^{k}
351
352    let mut scratch: ScratchOwned = ScratchOwned::new(
353        GLWEKeySwitchKey::encrypt_sk_scratch_space(&module, ct_rgsw_rhs.size())
354            | GLWECiphertextFourier::decrypt_scratch_space(&module, ct_rgsw_lhs_out.size())
355            | GGSWCiphertext::encrypt_sk_scratch_space(&module, ct_rgsw_lhs_in.size())
356            | GGSWCiphertext::external_product_scratch_space(
357                &module,
358                ct_rgsw_lhs_out.size(),
359                ct_rgsw_lhs_in.size(),
360                ct_rgsw_rhs.size(),
361            ),
362    );
363
364    let mut sk: SecretKey<Vec<u8>> = SecretKey::new(&module);
365    sk.fill_ternary_prob(0.5, &mut source_xs);
366
367    let mut sk_dft: SecretKeyFourier<Vec<u8>, FFT64> = SecretKeyFourier::new(&module);
368    sk_dft.dft(&module, &sk);
369
370    ct_rgsw_rhs.encrypt_sk(
371        &module,
372        &pt_rgsw_rhs,
373        &sk_dft,
374        &mut source_xa,
375        &mut source_xe,
376        sigma,
377        bound,
378        scratch.borrow(),
379    );
380
381    ct_rgsw_lhs_in.encrypt_sk(
382        &module,
383        &pt_rgsw_lhs,
384        &sk_dft,
385        &mut source_xa,
386        &mut source_xe,
387        sigma,
388        bound,
389        scratch.borrow(),
390    );
391
392    ct_rgsw_lhs_out.external_product(&module, &ct_rgsw_lhs_in, &ct_rgsw_rhs, scratch.borrow());
393
394    let mut ct_rlwe_dft: GLWECiphertextFourier<Vec<u8>, FFT64> =
395        GLWECiphertextFourier::new(&module, log_base2k, log_k_rgsw_lhs_out);
396    let mut pt: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::new(&module, log_base2k, log_k_rgsw_lhs_out);
397    let mut pt_dft: VecZnxDft<Vec<u8>, FFT64> = module.new_vec_znx_dft(1, ct_rgsw_lhs_out.size());
398    let mut pt_big: VecZnxBig<Vec<u8>, FFT64> = module.new_vec_znx_big(1, ct_rgsw_lhs_out.size());
399    let mut pt_want: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::new(&module, log_base2k, log_k_rgsw_lhs_out);
400
401    module.vec_znx_rotate_inplace(k as i64, &mut pt_rgsw_lhs, 0);
402
403    (0..ct_rgsw_lhs_out.rank()).for_each(|col_j| {
404        (0..ct_rgsw_lhs_out.rows()).for_each(|row_i| {
405            module.vec_znx_add_scalar_inplace(&mut pt_want, 0, row_i, &pt_rgsw_lhs, 0);
406
407            if col_j == 1 {
408                module.vec_znx_dft(&mut pt_dft, 0, &pt_want, 0);
409                module.svp_apply_inplace(&mut pt_dft, 0, &sk_dft, 0);
410                module.vec_znx_idft_tmp_a(&mut pt_big, 0, &mut pt_dft, 0);
411                module.vec_znx_big_normalize(log_base2k, &mut pt_want, 0, &pt_big, 0, scratch.borrow());
412            }
413
414            ct_rgsw_lhs_out.get_row(&module, row_i, col_j, &mut ct_rlwe_dft);
415            ct_rlwe_dft.decrypt(&module, &mut pt, &sk_dft, scratch.borrow());
416
417            module.vec_znx_sub_ab_inplace(&mut pt, 0, &pt_want, 0);
418
419            let noise_have: f64 = pt.data.std(0, log_base2k).log2();
420
421            let var_gct_err_lhs: f64 = sigma * sigma;
422            let var_gct_err_rhs: f64 = 0f64;
423
424            let var_msg: f64 = 1f64 / module.n() as f64; // X^{k}
425            let var_a0_err: f64 = sigma * sigma;
426            let var_a1_err: f64 = 1f64 / 12f64;
427
428            let noise_want: f64 = noise_rgsw_product(
429                module.n() as f64,
430                log_base2k,
431                0.5,
432                var_msg,
433                var_a0_err,
434                var_a1_err,
435                var_gct_err_lhs,
436                var_gct_err_rhs,
437                log_k_rgsw_lhs_in,
438                log_k_rgsw_rhs,
439            );
440
441            assert!(
442                (noise_have - noise_want).abs() <= 0.1,
443                "have: {} want: {}",
444                noise_have,
445                noise_want
446            );
447
448            pt_want.data.zero();
449        });
450    });
451}
452
453#[test]
454fn external_product_inplace() {
455    let module: Module<FFT64> = Module::<FFT64>::new(2048);
456    let log_base2k: usize = 12;
457    let log_k_rgsw_rhs: usize = 60;
458    let log_k_rgsw_lhs: usize = 45;
459    let rows: usize = (log_k_rgsw_lhs + log_base2k - 1) / log_base2k;
460
461    let sigma: f64 = 3.2;
462    let bound: f64 = sigma * 6.0;
463
464    let mut ct_rgsw_rhs: GGSWCiphertext<Vec<u8>, FFT64> = GGSWCiphertext::new(&module, log_base2k, log_k_rgsw_rhs, rows);
465    let mut ct_rgsw_lhs: GGSWCiphertext<Vec<u8>, FFT64> = GGSWCiphertext::new(&module, log_base2k, log_k_rgsw_lhs, rows);
466    let mut pt_rgsw_lhs: ScalarZnx<Vec<u8>> = module.new_scalar_znx(1);
467    let mut pt_rgsw_rhs: ScalarZnx<Vec<u8>> = module.new_scalar_znx(1);
468
469    let mut source_xs: Source = Source::new([0u8; 32]);
470    let mut source_xe: Source = Source::new([0u8; 32]);
471    let mut source_xa: Source = Source::new([0u8; 32]);
472
473    // Random input plaintext
474    pt_rgsw_lhs.fill_ternary_prob(0, 0.5, &mut source_xs);
475
476    let k: usize = 1;
477
478    pt_rgsw_rhs.to_mut().raw_mut()[k] = 1; //X^{k}
479
480    let mut scratch: ScratchOwned = ScratchOwned::new(
481        GLWEKeySwitchKey::encrypt_sk_scratch_space(&module, ct_rgsw_rhs.size())
482            | GLWECiphertextFourier::decrypt_scratch_space(&module, ct_rgsw_lhs.size())
483            | GGSWCiphertext::encrypt_sk_scratch_space(&module, ct_rgsw_lhs.size())
484            | GGSWCiphertext::external_product_inplace_scratch_space(&module, ct_rgsw_lhs.size(), ct_rgsw_rhs.size()),
485    );
486
487    let mut sk: SecretKey<Vec<u8>> = SecretKey::new(&module);
488    sk.fill_ternary_prob(0.5, &mut source_xs);
489
490    let mut sk_dft: SecretKeyFourier<Vec<u8>, FFT64> = SecretKeyFourier::new(&module);
491    sk_dft.dft(&module, &sk);
492
493    ct_rgsw_rhs.encrypt_sk(
494        &module,
495        &pt_rgsw_rhs,
496        &sk_dft,
497        &mut source_xa,
498        &mut source_xe,
499        sigma,
500        bound,
501        scratch.borrow(),
502    );
503
504    ct_rgsw_lhs.encrypt_sk(
505        &module,
506        &pt_rgsw_lhs,
507        &sk_dft,
508        &mut source_xa,
509        &mut source_xe,
510        sigma,
511        bound,
512        scratch.borrow(),
513    );
514
515    ct_rgsw_lhs.external_product_inplace(&module, &ct_rgsw_rhs, scratch.borrow());
516
517    let mut ct_rlwe_dft: GLWECiphertextFourier<Vec<u8>, FFT64> = GLWECiphertextFourier::new(&module, log_base2k, log_k_rgsw_lhs);
518    let mut pt: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::new(&module, log_base2k, log_k_rgsw_lhs);
519    let mut pt_dft: VecZnxDft<Vec<u8>, FFT64> = module.new_vec_znx_dft(1, ct_rgsw_lhs.size());
520    let mut pt_big: VecZnxBig<Vec<u8>, FFT64> = module.new_vec_znx_big(1, ct_rgsw_lhs.size());
521    let mut pt_want: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::new(&module, log_base2k, log_k_rgsw_lhs);
522
523    module.vec_znx_rotate_inplace(k as i64, &mut pt_rgsw_lhs, 0);
524
525    (0..ct_rgsw_lhs.rank()).for_each(|col_j| {
526        (0..ct_rgsw_lhs.rows()).for_each(|row_i| {
527            module.vec_znx_add_scalar_inplace(&mut pt_want, 0, row_i, &pt_rgsw_lhs, 0);
528
529            if col_j == 1 {
530                module.vec_znx_dft(&mut pt_dft, 0, &pt_want, 0);
531                module.svp_apply_inplace(&mut pt_dft, 0, &sk_dft, 0);
532                module.vec_znx_idft_tmp_a(&mut pt_big, 0, &mut pt_dft, 0);
533                module.vec_znx_big_normalize(log_base2k, &mut pt_want, 0, &pt_big, 0, scratch.borrow());
534            }
535
536            ct_rgsw_lhs.get_row(&module, row_i, col_j, &mut ct_rlwe_dft);
537            ct_rlwe_dft.decrypt(&module, &mut pt, &sk_dft, scratch.borrow());
538
539            module.vec_znx_sub_ab_inplace(&mut pt, 0, &pt_want, 0);
540
541            let noise_have: f64 = pt.data.std(0, log_base2k).log2();
542
543            let var_gct_err_lhs: f64 = sigma * sigma;
544            let var_gct_err_rhs: f64 = 0f64;
545
546            let var_msg: f64 = 1f64 / module.n() as f64; // X^{k}
547            let var_a0_err: f64 = sigma * sigma;
548            let var_a1_err: f64 = 1f64 / 12f64;
549
550            let noise_want: f64 = noise_rgsw_product(
551                module.n() as f64,
552                log_base2k,
553                0.5,
554                var_msg,
555                var_a0_err,
556                var_a1_err,
557                var_gct_err_lhs,
558                var_gct_err_rhs,
559                log_k_rgsw_lhs,
560                log_k_rgsw_rhs,
561            );
562
563            assert!(
564                (noise_have - noise_want).abs() <= 0.1,
565                "have: {} want: {}",
566                noise_have,
567                noise_want
568            );
569
570            pt_want.data.zero();
571        });
572    });
573}
574
575pub(crate) fn noise_rgsw_product(
576    n: f64,
577    log_base2k: usize,
578    var_xs: f64,
579    var_msg: f64,
580    var_a0_err: f64,
581    var_a1_err: f64,
582    var_gct_err_lhs: f64,
583    var_gct_err_rhs: f64,
584    a_logq: usize,
585    b_logq: usize,
586) -> f64 {
587    let a_logq: usize = a_logq.min(b_logq);
588    let a_cols: usize = (a_logq + log_base2k - 1) / log_base2k;
589
590    let b_scale = 2.0f64.powi(b_logq as i32);
591    let a_scale: f64 = 2.0f64.powi((b_logq - a_logq) as i32);
592
593    let base: f64 = (1 << (log_base2k)) as f64;
594    let var_base: f64 = base * base / 12f64;
595
596    // lhs = a_cols * n * (var_base * var_gct_err_lhs + var_e_a * var_msg * p^2)
597    // rhs = a_cols * n * var_base * var_gct_err_rhs * var_xs
598    let mut noise: f64 = 2.0 * (a_cols as f64) * n * var_base * (var_gct_err_lhs + var_xs * var_gct_err_rhs);
599    noise += var_msg * var_a0_err * a_scale * a_scale * n;
600    noise += var_msg * var_a1_err * a_scale * a_scale * n * var_xs;
601    noise = noise.sqrt();
602    noise /= b_scale;
603    noise.log2().min(-1.0) // max noise is [-2^{-1}, 2^{-1}]
604}