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