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}