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