poulpy/poulpy-schemes/examples/circuit_bootstrapping.rs
poulpy-schemes/examples/circuit_bootstrapping.rsRaw
1use poulpy_core::{
2 GLWENormalize,
3 layouts::{
4 GGLWEToGGSWKeyLayout, GGSW, GGSWLayout, GLWE, GLWEAutomorphismKeyLayout, GLWELayout, GLWEPlaintext, GLWESecret, LWE,
5 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, VecZnxNormalizeInplace},
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: GGLWEToGGSWKeyLayout {
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.vec_znx_normalize_inplace(base2k, pt_lwe.data_mut(), 0, scratch.borrow());
159 println!("pt_lwe: {pt_lwe}");
160
161 // LWE ciphertext
162 let mut ct_lwe: LWE<Vec<u8>> = LWE::alloc_from_infos(&lwe_infos);
163
164 // Encrypt LWE Plaintext
165 ct_lwe.encrypt_sk(
166 &module,
167 &pt_lwe,
168 &sk_lwe,
169 &mut source_xa,
170 &mut source_xe,
171 scratch.borrow(),
172 );
173
174 let now: Instant = Instant::now();
175
176 // Circuit bootstrapping evaluation key
177 let mut cbt_key: CircuitBootstrappingKey<Vec<u8>, CGGI> = CircuitBootstrappingKey::alloc_from_infos(&cbt_infos);
178
179 cbt_key.encrypt_sk(
180 &module,
181 &sk_lwe,
182 &sk_glwe,
183 &mut source_xa,
184 &mut source_xe,
185 scratch.borrow(),
186 );
187
188 println!("CBT-KGEN: {} ms", now.elapsed().as_millis());
189
190 // Output GGSW
191 let mut res: GGSW<Vec<u8>> = GGSW::alloc_from_infos(&ggsw_infos);
192
193 // Circuit bootstrapping key prepared (opaque backend dependant write only struct)
194 let mut cbt_prepared: CircuitBootstrappingKeyPrepared<Vec<u8>, CGGI, BackendImpl> =
195 CircuitBootstrappingKeyPrepared::alloc_from_infos(&module, &cbt_infos);
196 cbt_prepared.prepare(&module, &cbt_key, scratch.borrow());
197
198 // Apply circuit bootstrapping: LWE(data * 2^{- (k_lwe_pt + 2)}) -> GGSW(data)
199 let now: Instant = Instant::now();
200 cbt_prepared.execute_to_constant(
201 &module,
202 &mut res,
203 &ct_lwe,
204 k_lwe_pt,
205 extension_factor,
206 scratch.borrow(),
207 );
208 println!("CBT: {} ms", now.elapsed().as_millis());
209
210 // Allocate "ideal" GGSW(data) plaintext
211 let mut pt_ggsw: ScalarZnx<Vec<u8>> = ScalarZnx::alloc(n_glwe, 1);
212 pt_ggsw.at_mut(0, 0)[0] = data;
213
214 // Prints noise of GGSW(data)
215 res.print_noise(&module, &sk_glwe_prepared, &pt_ggsw);
216
217 // Tests RLWE(1) * GGSW(data)
218
219 let glwe_infos: GLWELayout = GLWELayout {
220 n: n_glwe.into(),
221 base2k: base2k.into(),
222 k: (k_ggsw_res - base2k).into(),
223 rank: rank.into(),
224 };
225
226 // GLWE ciphertext modulus
227 let mut ct_glwe: GLWE<Vec<u8>> = GLWE::alloc_from_infos(&glwe_infos);
228
229 // Some GLWE plaintext with signed data
230 let k_glwe_pt: usize = 3;
231 let mut pt_glwe: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::alloc_from_infos(&glwe_infos);
232 let mut data_vec: Vec<i64> = vec![0i64; n_glwe];
233 data_vec
234 .iter_mut()
235 .enumerate()
236 .for_each(|(x, y)| *y = (x % (1 << (k_glwe_pt - 1))) as i64 - (1 << (k_glwe_pt - 2)));
237
238 pt_glwe.encode_vec_i64(&data_vec, (k_lwe_pt + 2).into());
239 module.glwe_normalize_inplace(&mut pt_glwe, scratch.borrow());
240
241 println!("{}", pt_glwe);
242
243 // Encrypt
244 ct_glwe.encrypt_sk(
245 &module,
246 &pt_glwe,
247 &sk_glwe_prepared,
248 &mut source_xa,
249 &mut source_xe,
250 scratch.borrow(),
251 );
252
253 // Prepare GGSW output of circuit bootstrapping (opaque backend dependant write only struct)
254 let mut res_prepared: GGSWPrepared<Vec<u8>, BackendImpl> = GGSWPrepared::alloc_from_infos(&module, &res);
255 res_prepared.prepare(&module, &res, scratch.borrow());
256
257 // Apply GLWE x GGSW
258 ct_glwe.external_product_inplace(&module, &res_prepared, scratch.borrow());
259
260 // Decrypt
261 let mut pt_res: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::alloc_from_infos(&glwe_infos);
262 ct_glwe.decrypt(&module, &mut pt_res, &sk_glwe_prepared, scratch.borrow());
263
264 println!("pt_res: {:?}", &pt_res.data.at(0, 0)[..64]);
265}