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}