poulpy/poulpy-schemes/examples/circuit_bootstrapping.rs
poulpy-schemes/examples/circuit_bootstrapping.rsRaw
1use poulpy_core::{
2 GLWENormalize,
3 layouts::{
4 GGSW, GGSWLayout, GLWE, GLWEAutomorphismKeyLayout, GLWELayout, GLWEPlaintext, GLWESecret, GLWETensorKeyLayout, LWE,
5 LWEInfos, LWELayout, LWEPlaintext, LWESecret,
6 prepared::{GGSWPrepared, GLWESecretPrepared},
7 },
8};
9use std::time::Instant;
10
11#[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
12use poulpy_backend::FFT64Avx as BackendImpl;
13
14#[cfg(not(any(target_arch = "x86_64", target_arch = "x86")))]
15use poulpy_backend::FFT64Ref as BackendImpl;
16
17use poulpy_hal::{
18 api::{ModuleNew, ScratchOwnedAlloc, ScratchOwnedBorrow, ZnNormalizeInplace},
19 layouts::{Module, ScalarZnx, ScratchOwned, ZnxView, ZnxViewMut},
20 source::Source,
21};
22
23use poulpy_schemes::tfhe::{
24 blind_rotation::{BlindRotationKeyLayout, CGGI},
25 circuit_bootstrapping::{CircuitBootstrappingKey, CircuitBootstrappingKeyLayout, CircuitBootstrappingKeyPrepared},
26};
27
28fn main() {
29 // GLWE ring degree
30 let n_glwe: usize = 1024;
31
32 // Module provides access to the backend arithmetic
33 let module: Module<BackendImpl> = Module::<BackendImpl>::new(n_glwe as u64);
34
35 // Base 2 loga
36 let base2k: usize = 13;
37
38 // Lookup table extension factor
39 let extension_factor: usize = 1;
40
41 // GLWE rank
42 let rank: usize = 1;
43
44 // LWE degree
45 let n_lwe: usize = 574;
46
47 // LWE plaintext modulus
48 let k_lwe_pt: usize = 1;
49
50 // LWE ciphertext modulus
51 let k_lwe_ct: usize = 13;
52
53 // LWE block binary key block size
54 let block_size: usize = 7;
55
56 // GGSW output number of dnum
57 let rows_ggsw_res: usize = 2;
58
59 // GGSW output modulus
60 let k_ggsw_res: usize = (rows_ggsw_res + 1) * base2k;
61
62 // Blind rotation key GGSW number of dnum
63 let rows_brk: usize = rows_ggsw_res + 1;
64
65 // Blind rotation key GGSW modulus
66 let k_brk: usize = (rows_brk + 1) * base2k;
67
68 // GGLWE automorphism keys number of dnum
69 let rows_trace: usize = rows_ggsw_res + 1;
70
71 // GGLWE automorphism keys modulus
72 let k_trace: usize = (rows_trace + 1) * base2k;
73
74 // GGLWE tensor key number of dnum
75 let rows_tsk: usize = rows_ggsw_res + 1;
76
77 // GGLWE tensor key modulus
78 let k_tsk: usize = (rows_tsk + 1) * base2k;
79
80 let cbt_infos: CircuitBootstrappingKeyLayout = CircuitBootstrappingKeyLayout {
81 layout_brk: BlindRotationKeyLayout {
82 n_glwe: n_glwe.into(),
83 n_lwe: n_lwe.into(),
84 base2k: base2k.into(),
85 k: k_brk.into(),
86 dnum: rows_brk.into(),
87 rank: rank.into(),
88 },
89 layout_atk: GLWEAutomorphismKeyLayout {
90 n: n_glwe.into(),
91 base2k: base2k.into(),
92 k: k_trace.into(),
93 dnum: rows_trace.into(),
94 dsize: 1_u32.into(),
95 rank: rank.into(),
96 },
97 layout_tsk: GLWETensorKeyLayout {
98 n: n_glwe.into(),
99 base2k: base2k.into(),
100 k: k_tsk.into(),
101 dnum: rows_tsk.into(),
102 dsize: 1_u32.into(),
103 rank: rank.into(),
104 },
105 };
106
107 let ggsw_infos: GGSWLayout = GGSWLayout {
108 n: n_glwe.into(),
109 base2k: base2k.into(),
110 k: k_ggsw_res.into(),
111 dnum: rows_ggsw_res.into(),
112 dsize: 1_u32.into(),
113 rank: rank.into(),
114 };
115
116 let lwe_infos = LWELayout {
117 n: n_lwe.into(),
118 k: k_lwe_ct.into(),
119 base2k: base2k.into(),
120 };
121
122 // Scratch space (4MB)
123 let mut scratch: ScratchOwned<BackendImpl> = ScratchOwned::alloc(1 << 22);
124
125 // Secret key sampling source
126 let mut source_xs: Source = Source::new([1u8; 32]);
127
128 // Public randomness sampling source
129 let mut source_xa: Source = Source::new([1u8; 32]);
130
131 // Noise sampling source
132 let mut source_xe: Source = Source::new([1u8; 32]);
133
134 // LWE secret
135 let mut sk_lwe: LWESecret<Vec<u8>> = LWESecret::alloc(n_lwe.into());
136 sk_lwe.fill_binary_block(block_size, &mut source_xs);
137 sk_lwe.fill_zero();
138
139 // GLWE secret
140 let mut sk_glwe: GLWESecret<Vec<u8>> = GLWESecret::alloc(n_glwe.into(), rank.into());
141 sk_glwe.fill_ternary_prob(0.5, &mut source_xs);
142 // sk_glwe.fill_zero();
143
144 // GLWE secret prepared (opaque backend dependant write only struct)
145 let mut sk_glwe_prepared: GLWESecretPrepared<Vec<u8>, BackendImpl> = GLWESecretPrepared::alloc(&module, rank.into());
146 sk_glwe_prepared.prepare(&module, &sk_glwe);
147
148 // Plaintext value to circuit bootstrap
149 let data: i64 = 1 % (1 << k_lwe_pt);
150
151 // LWE plaintext
152 let mut pt_lwe: LWEPlaintext<Vec<u8>> = LWEPlaintext::alloc(base2k.into(), k_lwe_pt.into());
153
154 // LWE plaintext(data * 2^{- (k_lwe_pt - 1)})
155 pt_lwe.encode_i64(data, (k_lwe_pt + 1).into()); // +1 for padding bit
156
157 // Normalize plaintext to nicely print coefficients
158 module.zn_normalize_inplace(
159 pt_lwe.n().into(),
160 base2k,
161 pt_lwe.data_mut(),
162 0,
163 scratch.borrow(),
164 );
165 println!("pt_lwe: {pt_lwe}");
166
167 // LWE ciphertext
168 let mut ct_lwe: LWE<Vec<u8>> = LWE::alloc_from_infos(&lwe_infos);
169
170 // Encrypt LWE Plaintext
171 ct_lwe.encrypt_sk(&module, &pt_lwe, &sk_lwe, &mut source_xa, &mut source_xe);
172
173 let now: Instant = Instant::now();
174
175 // Circuit bootstrapping evaluation key
176 let mut cbt_key: CircuitBootstrappingKey<Vec<u8>, CGGI> = CircuitBootstrappingKey::alloc_from_infos(&cbt_infos);
177
178 cbt_key.encrypt_sk(
179 &module,
180 &sk_lwe,
181 &sk_glwe,
182 &mut source_xa,
183 &mut source_xe,
184 scratch.borrow(),
185 );
186
187 println!("CBT-KGEN: {} ms", now.elapsed().as_millis());
188
189 // Output GGSW
190 let mut res: GGSW<Vec<u8>> = GGSW::alloc_from_infos(&ggsw_infos);
191
192 // Circuit bootstrapping key prepared (opaque backend dependant write only struct)
193 let mut cbt_prepared: CircuitBootstrappingKeyPrepared<Vec<u8>, CGGI, BackendImpl> =
194 CircuitBootstrappingKeyPrepared::alloc_from_infos(&module, &cbt_infos);
195 cbt_prepared.prepare(&module, &cbt_key, scratch.borrow());
196
197 // Apply circuit bootstrapping: LWE(data * 2^{- (k_lwe_pt + 2)}) -> GGSW(data)
198 let now: Instant = Instant::now();
199 cbt_prepared.execute_to_constant(
200 &module,
201 &mut res,
202 &ct_lwe,
203 k_lwe_pt,
204 extension_factor,
205 scratch.borrow(),
206 );
207 println!("CBT: {} ms", now.elapsed().as_millis());
208
209 // Allocate "ideal" GGSW(data) plaintext
210 let mut pt_ggsw: ScalarZnx<Vec<u8>> = ScalarZnx::alloc(n_glwe, 1);
211 pt_ggsw.at_mut(0, 0)[0] = data;
212
213 // Prints noise of GGSW(data)
214 res.print_noise(&module, &sk_glwe_prepared, &pt_ggsw);
215
216 // Tests RLWE(1) * GGSW(data)
217
218 let glwe_infos: GLWELayout = GLWELayout {
219 n: n_glwe.into(),
220 base2k: base2k.into(),
221 k: (k_ggsw_res - base2k).into(),
222 rank: rank.into(),
223 };
224
225 // GLWE ciphertext modulus
226 let mut ct_glwe: GLWE<Vec<u8>> = GLWE::alloc_from_infos(&glwe_infos);
227
228 // Some GLWE plaintext with signed data
229 let k_glwe_pt: usize = 3;
230 let mut pt_glwe: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::alloc_from_infos(&glwe_infos);
231 let mut data_vec: Vec<i64> = vec![0i64; n_glwe];
232 data_vec
233 .iter_mut()
234 .enumerate()
235 .for_each(|(x, y)| *y = (x % (1 << (k_glwe_pt - 1))) as i64 - (1 << (k_glwe_pt - 2)));
236
237 pt_glwe.encode_vec_i64(&data_vec, (k_lwe_pt + 2).into());
238 module.glwe_normalize_inplace(&mut pt_glwe, scratch.borrow());
239
240 println!("{}", pt_glwe);
241
242 // Encrypt
243 ct_glwe.encrypt_sk(
244 &module,
245 &pt_glwe,
246 &sk_glwe_prepared,
247 &mut source_xa,
248 &mut source_xe,
249 scratch.borrow(),
250 );
251
252 // Prepare GGSW output of circuit bootstrapping (opaque backend dependant write only struct)
253 let mut res_prepared: GGSWPrepared<Vec<u8>, BackendImpl> = GGSWPrepared::alloc_from_infos(&module, &res);
254 res_prepared.prepare(&module, &res, scratch.borrow());
255
256 // Apply GLWE x GGSW
257 ct_glwe.external_product_inplace(&module, &res_prepared, scratch.borrow());
258
259 // Decrypt
260 let mut pt_res: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::alloc_from_infos(&glwe_infos);
261 ct_glwe.decrypt(&module, &mut pt_res, &sk_glwe_prepared, scratch.borrow());
262
263 println!("pt_res: {:?}", &pt_res.data.at(0, 0)[..64]);
264}