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