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