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