arnaucube
arnaucube/poulpyPublic Self-hosted repository
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(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    for row in 0..res.dnum().as_usize() {
216        for col in 0..res.rank().as_usize() + 1 {
217            println!(
218                "row:{row} col:{col} -> {}",
219                res.noise(
220                    &module,
221                    row,
222                    col,
223                    &pt_ggsw,
224                    &sk_glwe_prepared,
225                    scratch.borrow()
226                )
227                .std()
228                .log2()
229            )
230        }
231    }
232
233    // Tests RLWE(1) * GGSW(data)
234
235    let glwe_infos: GLWELayout = GLWELayout {
236        n: n_glwe.into(),
237        base2k: base2k.into(),
238        k: (k_ggsw_res - base2k).into(),
239        rank: rank.into(),
240    };
241
242    // GLWE ciphertext modulus
243    let mut ct_glwe: GLWE<Vec<u8>> = GLWE::alloc_from_infos(&glwe_infos);
244
245    // Some GLWE plaintext with signed data
246    let k_glwe_pt: usize = 3;
247    let mut pt_glwe: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::alloc_from_infos(&glwe_infos);
248    let mut data_vec: Vec<i64> = vec![0i64; n_glwe];
249    data_vec
250        .iter_mut()
251        .enumerate()
252        .for_each(|(x, y)| *y = (x % (1 << (k_glwe_pt - 1))) as i64 - (1 << (k_glwe_pt - 2)));
253
254    pt_glwe.encode_vec_i64(&data_vec, (k_lwe_pt + 2).into());
255    module.glwe_normalize_inplace(&mut pt_glwe, scratch.borrow());
256
257    println!("{}", pt_glwe);
258
259    // Encrypt
260    ct_glwe.encrypt_sk(
261        &module,
262        &pt_glwe,
263        &sk_glwe_prepared,
264        &mut source_xa,
265        &mut source_xe,
266        scratch.borrow(),
267    );
268
269    // Prepare GGSW output of circuit bootstrapping (opaque backend dependant write only struct)
270    let mut res_prepared: GGSWPrepared<Vec<u8>, BackendImpl> = GGSWPrepared::alloc_from_infos(&module, &res);
271    res_prepared.prepare(&module, &res, scratch.borrow());
272
273    // Apply GLWE x GGSW
274    ct_glwe.external_product_inplace(&module, &res_prepared, scratch.borrow());
275
276    // Decrypt
277    let mut pt_res: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::alloc_from_infos(&glwe_infos);
278    ct_glwe.decrypt(&module, &mut pt_res, &sk_glwe_prepared, scratch.borrow());
279
280    println!("pt_res: {:?}", &pt_res.data.at(0, 0)[..64]);
281}