poulpy/poulpy-schemes/examples/max_array.rs
poulpy-schemes/examples/max_array.rsRaw
1use std::collections::HashMap;
2
3use poulpy_core::{
4 GLWECopy, GLWEDecrypt, GLWEEncryptSk, GLWEExternalProduct, LWEEncryptSk, ScratchTakeCore,
5 layouts::{
6 Base2K, Degree, Dnum, Dsize, GGLWEToGGSWKeyLayout, GGSWLayout, GGSWPreparedFactory, GLWEAutomorphismKeyLayout,
7 GLWELayout, GLWESecret, GLWESecretPrepared, GLWESecretPreparedFactory, GLWESwitchingKeyLayout, GLWEToLWEKeyLayout,
8 GLWEToMut, GLWEToRef, LWESecret, Rank, TorusPrecision,
9 },
10};
11use poulpy_hal::{
12 api::{ModuleN, ModuleNew, ScratchOwnedAlloc, ScratchOwnedBorrow, VecZnxRotateInplace},
13 layouts::{Backend, Module, Scratch, ScratchOwned},
14 source::Source,
15};
16use poulpy_schemes::bin_fhe::{
17 bdd_arithmetic::{
18 BDDKey, BDDKeyEncryptSk, BDDKeyLayout, BDDKeyPrepared, BDDKeyPreparedFactory, ExecuteBDDCircuit2WTo1W, FheUint,
19 FheUintPrepare, FheUintPrepared, GLWEBlindSelection, Sltu,
20 },
21 blind_rotation::{BlindRotationAlgo, BlindRotationKey, BlindRotationKeyFactory, BlindRotationKeyLayout, CGGI},
22 circuit_bootstrapping::CircuitBootstrappingKeyLayout,
23};
24use rand::Rng;
25
26#[cfg(all(feature = "enable-avx", target_arch = "x86_64"))]
27use poulpy_cpu_avx::FFT64Avx;
28#[cfg(not(all(feature = "enable-avx", target_arch = "x86_64")))]
29use poulpy_cpu_ref::FFT64Ref;
30
31// This example demonstrates and end-to-end example usage of the BDD arithmetic API
32// to compute the maximum of an array of integers.
33
34fn example_max_array<BE: Backend, BRA: BlindRotationAlgo>()
35where
36 Module<BE>: ModuleNew<BE>
37 + ModuleN
38 + GLWESecretPreparedFactory<BE>
39 + GLWEExternalProduct<BE>
40 + GLWEDecrypt<BE>
41 + LWEEncryptSk<BE>
42 + GGSWPreparedFactory<BE>
43 + GLWEEncryptSk<BE>
44 + VecZnxRotateInplace<BE>
45 + BDDKeyEncryptSk<BRA, BE>
46 + BDDKeyPreparedFactory<BRA, BE>
47 + FheUintPrepare<BRA, BE>
48 + ExecuteBDDCircuit2WTo1W<BE>
49 + GLWEBlindSelection<u32, BE>,
50 BlindRotationKey<Vec<u8>, BRA>: BlindRotationKeyFactory<BRA>, // TODO find a way to remove this bound or move it to CBT KEY
51 ScratchOwned<BE>: ScratchOwnedAlloc<BE> + ScratchOwnedBorrow<BE>,
52 Scratch<BE>: ScratchTakeCore<BE>,
53{
54 ////////// Parameter Selection
55 const N_GLWE: u32 = 1024;
56 const N_LWE: u32 = 567;
57 const BINARY_BLOCK_SIZE: u32 = 7;
58 const BASE2K: u32 = 17;
59 const RANK: u32 = 1;
60
61 // GLWE layout, used to generate GLWE Ciphertexts, keys, switching keys, etc
62 let glwe_layout = GLWELayout {
63 n: Degree(N_GLWE),
64 base2k: Base2K(BASE2K),
65 k: TorusPrecision(2 * BASE2K),
66 rank: Rank(RANK),
67 };
68
69 // Used to generate GGSW Ciphertexts
70 let ggsw_layout = GGSWLayout {
71 n: Degree(N_GLWE),
72 base2k: Base2K(BASE2K),
73 k: TorusPrecision(3 * BASE2K),
74 rank: Rank(RANK),
75 dnum: Dnum(3),
76 dsize: Dsize(1),
77 };
78
79 // Used to generate BDD Keys, for the arithmetic operations
80 let bdd_layout = BDDKeyLayout {
81 cbt_layout: CircuitBootstrappingKeyLayout {
82 brk_layout: BlindRotationKeyLayout {
83 n_glwe: Degree(N_GLWE),
84 n_lwe: Degree(N_LWE),
85 base2k: Base2K(BASE2K),
86 k: TorusPrecision(4 * BASE2K),
87 dnum: Dnum(4),
88 rank: Rank(RANK),
89 },
90 atk_layout: GLWEAutomorphismKeyLayout {
91 n: Degree(N_GLWE),
92 base2k: Base2K(BASE2K),
93 k: TorusPrecision(4 * BASE2K),
94 dnum: Dnum(4),
95 dsize: Dsize(1),
96 rank: Rank(RANK),
97 },
98 tsk_layout: GGLWEToGGSWKeyLayout {
99 n: Degree(N_GLWE),
100 base2k: Base2K(BASE2K),
101 k: TorusPrecision(4 * BASE2K),
102 dnum: Dnum(4),
103 dsize: Dsize(1),
104 rank: Rank(RANK),
105 },
106 },
107 ks_glwe_layout: Some(GLWESwitchingKeyLayout {
108 n: Degree(N_GLWE),
109 base2k: Base2K(BASE2K),
110 k: TorusPrecision(4 * BASE2K),
111 dnum: Dnum(4),
112 dsize: Dsize(1),
113 rank_in: Rank(RANK),
114 rank_out: Rank(1),
115 }),
116 ks_lwe_layout: GLWEToLWEKeyLayout {
117 n: Degree(N_GLWE),
118 base2k: Base2K(BASE2K),
119 k: TorusPrecision(4 * BASE2K),
120 rank_in: Rank(RANK),
121 dnum: Dnum(4),
122 },
123 };
124
125 let module = Module::<BE>::new(N_GLWE as u64);
126
127 // Secret key sampling source
128 let mut source_xs: Source = Source::new([1u8; 32]);
129
130 // Public randomness sampling source
131 let mut source_xa: Source = Source::new([1u8; 32]);
132
133 // Noise sampling source
134 let mut source_xe: Source = Source::new([1u8; 32]);
135
136 // Scratch space (4MB)
137 let mut scratch: ScratchOwned<BE> = ScratchOwned::alloc(1 << 22);
138
139 ////////// Key Generation and Preparation
140 // Generating the GLWE and LWE key
141 let mut sk_glwe = GLWESecret::alloc_from_infos(&glwe_layout);
142 sk_glwe.fill_ternary_prob(0.5, &mut source_xs);
143
144 let mut sk_lwe = LWESecret::alloc(Degree(N_LWE));
145 sk_lwe.fill_binary_block(BINARY_BLOCK_SIZE as usize, &mut source_xs);
146
147 // Preparing the private keys
148 let mut sk_glwe_prepared = GLWESecretPrepared::alloc_from_infos(&module, &glwe_layout);
149 sk_glwe_prepared.prepare(&module, &sk_glwe);
150
151 // Creating the public BDD Key
152 // This key is required to prepare all Fhe Integers for operations,
153 // and for performing the operations themselves
154 let mut bdd_key: BDDKey<Vec<u8>, BRA> = BDDKey::alloc_from_infos(&bdd_layout);
155 bdd_key.encrypt_sk(
156 &module,
157 &sk_lwe,
158 &sk_glwe,
159 &mut source_xa,
160 &mut source_xe,
161 scratch.borrow(),
162 );
163
164 ////////// Input Encryption
165 // Encrypting the inputs
166 let mut rng = rand::rng();
167 let inputs: Vec<u32> = (0..3).map(|_| rng.random_range(0..u32::MAX - 1)).collect();
168
169 let mut inputs_enc: Vec<FheUint<Vec<u8>, u32>> = Vec::new();
170 for input in &inputs {
171 let mut next_input = FheUint::alloc_from_infos(&glwe_layout);
172 next_input.encrypt_sk(
173 &module,
174 *input,
175 &sk_glwe_prepared,
176 &mut source_xa,
177 &mut source_xe,
178 scratch.borrow(),
179 );
180 inputs_enc.push(next_input);
181 }
182
183 //////// Homomorphic computation starts here ////////
184
185 // Preparing the BDD Key
186 // The BDD key must be prepared once before any operation is performed
187 let mut bdd_key_prepared: BDDKeyPrepared<Vec<u8>, BRA, BE> = BDDKeyPrepared::alloc_from_infos(&module, &bdd_layout);
188 bdd_key_prepared.prepare(&module, &bdd_key, scratch.borrow());
189
190 let mut max_enc: FheUint<Vec<u8>, u32> = FheUint::alloc_from_infos(&glwe_layout);
191 max_enc.encrypt_sk(
192 &module,
193 0,
194 &sk_glwe_prepared,
195 &mut source_xa,
196 &mut source_xe,
197 scratch.borrow(),
198 );
199 // Copy of max_enc for the HashMap
200 let mut max_enc_copy: FheUint<Vec<u8>, u32> = FheUint::alloc_from_infos(&glwe_layout);
201
202 // Allocating the intermediate ciphertext c_enc
203 let mut compare_enc: FheUint<Vec<u8>, u32> = FheUint::alloc_from_infos(&glwe_layout);
204 let mut compare_enc_prepared: FheUintPrepared<Vec<u8>, u32, BE> = FheUintPrepared::alloc_from_infos(&module, &ggsw_layout);
205
206 for input_i in inputs_enc.iter_mut() {
207 let mut max_enc_prepared: FheUintPrepared<Vec<u8>, u32, BE> = FheUintPrepared::alloc_from_infos(&module, &ggsw_layout);
208 max_enc_prepared.prepare(&module, &max_enc, &bdd_key_prepared, scratch.borrow());
209
210 let mut input_i_enc_prepared: FheUintPrepared<Vec<u8>, u32, BE> =
211 FheUintPrepared::alloc_from_infos(&module, &ggsw_layout);
212 input_i_enc_prepared.prepare(&module, input_i, &bdd_key_prepared, scratch.borrow());
213
214 // b = (input_i < max)
215 compare_enc.sltu(
216 &module,
217 &input_i_enc_prepared,
218 &max_enc_prepared,
219 &bdd_key_prepared,
220 scratch.borrow(),
221 );
222
223 compare_enc_prepared.prepare(&module, &compare_enc, &bdd_key_prepared, scratch.borrow());
224
225 module.glwe_copy(&mut max_enc_copy.to_mut(), &max_enc.to_ref());
226
227 let cts = HashMap::from([(0, input_i), (1, &mut max_enc_copy)]);
228
229 <Module<BE> as GLWEBlindSelection<u32, BE>>::glwe_blind_selection(
230 &module,
231 &mut max_enc,
232 cts,
233 &compare_enc_prepared,
234 0,
235 1,
236 scratch.borrow(),
237 );
238 }
239
240 //////// Homomorphic computation ends here ////////
241
242 // Decrypting the result
243 let result_dec = max_enc.decrypt(&module, &sk_glwe_prepared, scratch.borrow());
244
245 // result = max of inputs
246 let result_correct = inputs.iter().max().unwrap();
247 println!("Result: {} == {}", result_dec, result_correct);
248}
249
250fn main() {
251 #[cfg(all(feature = "enable-avx", target_arch = "x86_64"))]
252 example_max_array::<FFT64Avx, CGGI>();
253
254 #[cfg(not(all(feature = "enable-avx", target_arch = "x86_64")))]
255 example_max_array::<FFT64Ref, CGGI>();
256}