poulpy/core/src/test_fft64/rgsw.rs
core/src/test_fft64/rgsw.rsRaw
1#[cfg(test)]
2mod tests {
3 use base2k::{
4 FFT64, Module, ScalarZnx, ScalarZnxAlloc, ScalarZnxDftOps, ScratchOwned, Stats, VecZnxBig, VecZnxBigAlloc, VecZnxBigOps,
5 VecZnxDft, VecZnxDftAlloc, VecZnxDftOps, VecZnxOps, VecZnxToMut, ZnxViewMut, ZnxZero,
6 };
7 use sampling::source::Source;
8
9 use crate::{
10 elem::{GetRow, Infos},
11 keys::{SecretKey, SecretKeyDft},
12 rgsw::RGSWCt,
13 rlwe::{RLWECt, RLWECtDft, RLWEPt},
14 test_fft64::rgsw::noise_rgsw_rlwe_product,
15 };
16
17 #[test]
18 fn encrypt_rgsw_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
24 let sigma: f64 = 3.2;
25 let bound: f64 = sigma * 6.0;
26
27 let mut ct: RGSWCt<Vec<u8>, FFT64> = RGSWCt::new(&module, log_base2k, log_k_ct, rows);
28 let mut pt_have: RLWEPt<Vec<u8>> = RLWEPt::new(&module, log_base2k, log_k_ct);
29 let mut pt_want: RLWEPt<Vec<u8>> = RLWEPt::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 RGSWCt::encrypt_sk_scratch_space(&module, ct.size()) | RLWECtDft::decrypt_scratch_space(&module, ct.size()),
40 );
41
42 let mut sk: SecretKey<Vec<u8>> = SecretKey::new(&module);
43 sk.fill_ternary_prob(0.5, &mut source_xs);
44
45 let mut sk_dft: SecretKeyDft<Vec<u8>, FFT64> = SecretKeyDft::new(&module);
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: RLWECtDft<Vec<u8>, FFT64> = RLWECtDft::new(&module, log_base2k, log_k_ct);
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.cols()).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 module.free();
88 }
89
90 #[test]
91 fn mul_rlwe() {
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_rlwe_in: usize = 45;
96 let log_k_rlwe_out: usize = 60;
97 let rows: usize = (log_k_rlwe_in + log_base2k - 1) / log_base2k;
98
99 let sigma: f64 = 3.2;
100 let bound: f64 = sigma * 6.0;
101
102 let mut ct_rgsw: RGSWCt<Vec<u8>, FFT64> = RGSWCt::new(&module, log_base2k, log_k_grlwe, rows);
103 let mut ct_rlwe_in: RLWECt<Vec<u8>> = RLWECt::new(&module, log_base2k, log_k_rlwe_in);
104 let mut ct_rlwe_out: RLWECt<Vec<u8>> = RLWECt::new(&module, log_base2k, log_k_rlwe_out);
105 let mut pt_rgsw: ScalarZnx<Vec<u8>> = module.new_scalar_znx(1);
106 let mut pt_want: RLWEPt<Vec<u8>> = RLWEPt::new(&module, log_base2k, log_k_rlwe_in);
107 let mut pt_have: RLWEPt<Vec<u8>> = RLWEPt::new(&module, log_base2k, log_k_rlwe_out);
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_want
115 // .data
116 // .fill_uniform(log_base2k, 0, pt_want.size(), &mut source_xa);
117
118 pt_want.to_mut().at_mut(0, 0)[1] = 1;
119
120 let k: usize = 1;
121
122 pt_rgsw.raw_mut()[k] = 1; // X^{k}
123
124 let mut scratch: ScratchOwned = ScratchOwned::new(
125 RGSWCt::encrypt_sk_scratch_space(&module, ct_rgsw.size())
126 | RLWECt::decrypt_scratch_space(&module, ct_rlwe_out.size())
127 | RLWECt::encrypt_sk_scratch_space(&module, ct_rlwe_in.size())
128 | RGSWCt::mul_rlwe_scratch_space(
129 &module,
130 ct_rlwe_out.size(),
131 ct_rlwe_in.size(),
132 ct_rgsw.size(),
133 ),
134 );
135
136 let mut sk: SecretKey<Vec<u8>> = SecretKey::new(&module);
137 sk.fill_ternary_prob(0.5, &mut source_xs);
138
139 let mut sk_dft: SecretKeyDft<Vec<u8>, FFT64> = SecretKeyDft::new(&module);
140 sk_dft.dft(&module, &sk);
141
142 ct_rgsw.encrypt_sk(
143 &module,
144 &pt_rgsw,
145 &sk_dft,
146 &mut source_xa,
147 &mut source_xe,
148 sigma,
149 bound,
150 scratch.borrow(),
151 );
152
153 ct_rlwe_in.encrypt_sk(
154 &module,
155 Some(&pt_want),
156 &sk_dft,
157 &mut source_xa,
158 &mut source_xe,
159 sigma,
160 bound,
161 scratch.borrow(),
162 );
163
164 ct_rgsw.mul_rlwe(&module, &mut ct_rlwe_out, &ct_rlwe_in, scratch.borrow());
165
166 ct_rlwe_out.decrypt(&module, &mut pt_have, &sk_dft, scratch.borrow());
167
168 module.vec_znx_rotate_inplace(k as i64, &mut pt_want, 0);
169
170 module.vec_znx_sub_ab_inplace(&mut pt_have, 0, &pt_want, 0);
171
172 let noise_have: f64 = pt_have.data.std(0, log_base2k).log2();
173
174 let var_gct_err_lhs: f64 = sigma * sigma;
175 let var_gct_err_rhs: f64 = 0f64;
176
177 let var_msg: f64 = 1f64 / module.n() as f64; // X^{k}
178 let var_a0_err: f64 = sigma * sigma;
179 let var_a1_err: f64 = 1f64 / 12f64;
180
181 let noise_want: f64 = noise_rgsw_rlwe_product(
182 module.n() as f64,
183 log_base2k,
184 0.5,
185 var_msg,
186 var_a0_err,
187 var_a1_err,
188 var_gct_err_lhs,
189 var_gct_err_rhs,
190 log_k_rlwe_in,
191 log_k_grlwe,
192 );
193
194 assert!(
195 (noise_have - noise_want).abs() <= 0.1,
196 "{} {}",
197 noise_have,
198 noise_want
199 );
200
201 module.free();
202 }
203}
204
205#[allow(dead_code)]
206pub(crate) fn noise_rgsw_rlwe_product(
207 n: f64,
208 log_base2k: usize,
209 var_xs: f64,
210 var_msg: f64,
211 var_a0_err: f64,
212 var_a1_err: f64,
213 var_gct_err_lhs: f64,
214 var_gct_err_rhs: f64,
215 a_logq: usize,
216 b_logq: usize,
217) -> f64 {
218 let a_logq: usize = a_logq.min(b_logq);
219 let a_cols: usize = (a_logq + log_base2k - 1) / log_base2k;
220
221 let b_scale = 2.0f64.powi(b_logq as i32);
222 let a_scale: f64 = 2.0f64.powi((b_logq - a_logq) as i32);
223
224 let base: f64 = (1 << (log_base2k)) as f64;
225 let var_base: f64 = base * base / 12f64;
226
227 // lhs = a_cols * n * (var_base * var_gct_err_lhs + var_e_a * var_msg * p^2)
228 // rhs = a_cols * n * var_base * var_gct_err_rhs * var_xs
229 let mut noise: f64 = 2.0 * (a_cols as f64) * n * var_base * (var_gct_err_lhs + var_xs * var_gct_err_rhs);
230 noise += var_msg * var_a0_err * a_scale * a_scale * n;
231 noise += var_msg * var_a1_err * a_scale * a_scale * n * var_xs;
232 noise = noise.sqrt();
233 noise /= b_scale;
234 noise.log2().min(-1.0) // max noise is [-2^{-1}, 2^{-1}]
235}