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