phantom-zone/src/bool/ni_mp_api.rs
src/bool/ni_mp_api.rsRaw
1use std::{cell::RefCell, sync::OnceLock};
2
3use crate::{
4 backend::ModulusPowerOf2,
5 bool::parameters::ParameterVariant,
6 parameters::NI_4P,
7 random::DefaultSecureRng,
8 utils::{Global, WithLocal},
9 ModularOpsU64, NttBackendU64,
10};
11
12use super::{
13 evaluator::NonInteractiveMultiPartyCrs,
14 keys::{
15 CommonReferenceSeededNonInteractiveMultiPartyServerKeyShare,
16 NonInteractiveServerKeyEvaluationDomain, SeededNonInteractiveMultiPartyServerKey,
17 ShoupNonInteractiveServerKeyEvaluationDomain,
18 },
19 parameters::{BoolParameters, CiphertextModulus, NI_2P},
20 ClientKey, ParameterSelector,
21};
22
23pub(crate) type BoolEvaluator = super::evaluator::BoolEvaluator<
24 Vec<Vec<u64>>,
25 NttBackendU64,
26 ModularOpsU64<CiphertextModulus<u64>>,
27 ModulusPowerOf2<CiphertextModulus<u64>>,
28 ShoupNonInteractiveServerKeyEvaluationDomain<Vec<Vec<u64>>>,
29>;
30
31thread_local! {
32 static BOOL_EVALUATOR: RefCell<Option<BoolEvaluator>> = RefCell::new(None);
33
34}
35static BOOL_SERVER_KEY: OnceLock<ShoupNonInteractiveServerKeyEvaluationDomain<Vec<Vec<u64>>>> =
36 OnceLock::new();
37
38static MULTI_PARTY_CRS: OnceLock<NonInteractiveMultiPartyCrs<[u8; 32]>> = OnceLock::new();
39
40pub fn set_parameter_set(select: ParameterSelector) {
41 match select {
42 ParameterSelector::NonInteractiveLTE2Party => {
43 BOOL_EVALUATOR.with_borrow_mut(|v| *v = Some(BoolEvaluator::new(NI_2P)));
44 }
45 ParameterSelector::NonInteractiveLTE4Party => {
46 BOOL_EVALUATOR.with_borrow_mut(|v| *v = Some(BoolEvaluator::new(NI_4P)));
47 }
48
49 _ => {
50 panic!("Paramerters not supported")
51 }
52 }
53}
54
55pub fn set_common_reference_seed(seed: [u8; 32]) {
56 BoolEvaluator::with_local(|e| {
57 assert_eq!(
58 e.parameters().variant(),
59 &ParameterVariant::NonInteractiveMultiParty,
60 "Set parameters do not support Non interactive multi-party"
61 );
62 });
63
64 assert!(
65 MULTI_PARTY_CRS
66 .set(NonInteractiveMultiPartyCrs { seed: seed })
67 .is_ok(),
68 "Attempted to set MP SEED twice."
69 )
70}
71
72pub fn gen_client_key() -> ClientKey {
73 BoolEvaluator::with_local(|e| e.client_key())
74}
75
76pub fn gen_server_key_share(
77 user_id: usize,
78 total_users: usize,
79 client_key: &ClientKey,
80) -> CommonReferenceSeededNonInteractiveMultiPartyServerKeyShare<
81 Vec<Vec<u64>>,
82 BoolParameters<u64>,
83 NonInteractiveMultiPartyCrs<[u8; 32]>,
84> {
85 BoolEvaluator::with_local(|e| {
86 let cr_seed = NonInteractiveMultiPartyCrs::global();
87 e.non_interactive_multi_party_key_share(cr_seed, user_id, total_users, client_key)
88 })
89}
90
91pub fn aggregate_server_key_shares(
92 shares: &[CommonReferenceSeededNonInteractiveMultiPartyServerKeyShare<
93 Vec<Vec<u64>>,
94 BoolParameters<u64>,
95 NonInteractiveMultiPartyCrs<[u8; 32]>,
96 >],
97) -> SeededNonInteractiveMultiPartyServerKey<
98 Vec<Vec<u64>>,
99 NonInteractiveMultiPartyCrs<[u8; 32]>,
100 BoolParameters<u64>,
101> {
102 BoolEvaluator::with_local(|e| {
103 let cr_seed = NonInteractiveMultiPartyCrs::global();
104 e.aggregate_non_interactive_multi_party_key_share(cr_seed, shares.len(), shares)
105 })
106}
107
108impl
109 SeededNonInteractiveMultiPartyServerKey<
110 Vec<Vec<u64>>,
111 NonInteractiveMultiPartyCrs<[u8; 32]>,
112 BoolParameters<u64>,
113 >
114{
115 pub fn set_server_key(&self) {
116 let eval_key = NonInteractiveServerKeyEvaluationDomain::<
117 _,
118 BoolParameters<u64>,
119 DefaultSecureRng,
120 NttBackendU64,
121 >::from(self);
122 assert!(
123 BOOL_SERVER_KEY
124 .set(ShoupNonInteractiveServerKeyEvaluationDomain::from(eval_key))
125 .is_ok(),
126 "Attempted to set server key twice!"
127 );
128 }
129}
130
131impl Global for NonInteractiveMultiPartyCrs<[u8; 32]> {
132 fn global() -> &'static Self {
133 MULTI_PARTY_CRS
134 .get()
135 .expect("Non-interactive multi-party common reference string not set")
136 }
137}
138
139// BOOL EVALUATOR //
140impl WithLocal for BoolEvaluator {
141 fn with_local<F, R>(func: F) -> R
142 where
143 F: Fn(&Self) -> R,
144 {
145 BOOL_EVALUATOR.with_borrow(|s| func(s.as_ref().expect("Parameters not set")))
146 }
147
148 fn with_local_mut<F, R>(func: F) -> R
149 where
150 F: Fn(&mut Self) -> R,
151 {
152 BOOL_EVALUATOR.with_borrow_mut(|s| func(s.as_mut().expect("Parameters not set")))
153 }
154
155 fn with_local_mut_mut<F, R>(func: &mut F) -> R
156 where
157 F: FnMut(&mut Self) -> R,
158 {
159 BOOL_EVALUATOR.with_borrow_mut(|s| func(s.as_mut().expect("Parameters not set")))
160 }
161}
162
163pub(crate) type RuntimeServerKey = ShoupNonInteractiveServerKeyEvaluationDomain<Vec<Vec<u64>>>;
164impl Global for RuntimeServerKey {
165 fn global() -> &'static Self {
166 BOOL_SERVER_KEY.get().expect("Server key not set!")
167 }
168}
169
170/// Batch of bool ciphertexts stored as vector of RLWE ciphertext under user j's
171/// RLWE secret `u_j`
172///
173/// To use the bool ciphertexts in multi-party protocol first key switch the
174/// ciphertexts from u_j to ideal RLWE secret `s` with
175/// `self.key_switch(user_id)` where `user_id` is user j's id. Key switch
176/// returns `BatchedFheBools` that stored key vector of key switched RLWE
177/// ciphertext.
178pub(super) struct NonInteractiveBatchedFheBools<C> {
179 data: Vec<C>,
180}
181
182/// Batch of Bool cipphertexts stored as vector of RLWE ciphertexts under the
183/// ideal RLWE secret key `s` of the protocol
184///
185/// Bool ciphertext at `index` can be extracted from the coefficient at `index %
186/// N` of `index / N`th RLWE ciphertext.
187///
188/// To extract bool ciphertext at `index` as LWE ciphertext use
189/// `self.extract(index)`
190pub(super) struct BatchedFheBools<C> {
191 pub(in super::super) data: Vec<C>,
192}
193
194/// Non interactive multi-party specfic encryptor decryptor routines
195mod impl_enc_dec {
196 use crate::{
197 bool::{evaluator::BoolEncoding, keys::NonInteractiveMultiPartyClientKey},
198 pbs::{sample_extract, PbsInfo, WithShoupRepr},
199 random::{NewWithSeed, RandomFillUniformInModulus},
200 rgsw::{rlwe_key_switch, seeded_secret_key_encrypt_rlwe},
201 utils::TryConvertFrom1,
202 Encryptor, KeySwitchWithId, Matrix, MatrixEntity, MatrixMut, RowEntity, RowMut,
203 };
204 use itertools::Itertools;
205 use num_traits::{ToPrimitive, Zero};
206
207 use super::*;
208
209 type Mat = Vec<Vec<u64>>;
210
211 // Implement `extract` to extract Bool LWE ciphertext at `index` from
212 // `BatchedFheBools`
213 impl<C: MatrixMut<MatElement = u64>> BatchedFheBools<C>
214 where
215 C::R: RowEntity + RowMut,
216 {
217 pub(super) fn extract(&self, index: usize) -> C::R {
218 BoolEvaluator::with_local(|e| {
219 let ring_size = e.parameters().rlwe_n().0;
220 let ct_index = index / ring_size;
221 let coeff_index = index % ring_size;
222 let mut lwe_out = C::R::zeros(e.parameters().rlwe_n().0 + 1);
223 sample_extract(
224 &mut lwe_out,
225 &self.data[ct_index],
226 e.pbs_info().modop_rlweq(),
227 coeff_index,
228 );
229 lwe_out
230 })
231 }
232 }
233
234 impl<M: MatrixEntity + MatrixMut<MatElement = u64>> From<&(Vec<M::R>, [u8; 32])>
235 for NonInteractiveBatchedFheBools<M>
236 where
237 <M as Matrix>::R: RowMut,
238 {
239 /// Derive `NonInteractiveBatchedFheBools` from a vector seeded RLWE
240 /// ciphertexts (Vec<RLWE>, Seed)
241 ///
242 /// Unseed the RLWE ciphertexts and store them as vector RLWE
243 /// ciphertexts in `NonInteractiveBatchedFheBools`
244 fn from(value: &(Vec<M::R>, [u8; 32])) -> Self {
245 BoolEvaluator::with_local(|e| {
246 let parameters = e.parameters();
247 let ring_size = parameters.rlwe_n().0;
248 let rlwe_q = parameters.rlwe_q();
249
250 let mut prng = DefaultSecureRng::new_seeded(value.1);
251 let rlwes = value
252 .0
253 .iter()
254 .map(|partb| {
255 let mut rlwe = M::zeros(2, ring_size);
256
257 // sample A
258 RandomFillUniformInModulus::random_fill(
259 &mut prng,
260 rlwe_q,
261 rlwe.get_row_mut(0),
262 );
263
264 // Copy over B
265 rlwe.get_row_mut(1).copy_from_slice(partb.as_ref());
266
267 rlwe
268 })
269 .collect_vec();
270 Self { data: rlwes }
271 })
272 }
273 }
274
275 impl<K> Encryptor<[bool], NonInteractiveBatchedFheBools<Mat>> for K
276 where
277 K: Encryptor<[bool], (Mat, [u8; 32])>,
278 {
279 /// Encrypt a vector bool of arbitrary length as vector of unseeded RLWE
280 /// ciphertexts in `NonInteractiveBatchedFheBools`
281 fn encrypt(&self, m: &[bool]) -> NonInteractiveBatchedFheBools<Mat> {
282 NonInteractiveBatchedFheBools::from(&K::encrypt(&self, m))
283 }
284 }
285
286 impl<K> Encryptor<[bool], (Vec<<Mat as Matrix>::R>, [u8; 32])> for K
287 where
288 K: NonInteractiveMultiPartyClientKey,
289 <Mat as Matrix>::R:
290 TryConvertFrom1<[K::Element], CiphertextModulus<<Mat as Matrix>::MatElement>>,
291 {
292 /// Encrypt a vector of bool of arbitrary length as vector of seeded
293 /// RLWE ciphertexts and returns (Vec<RLWE>, Seed)
294 fn encrypt(&self, m: &[bool]) -> (Mat, [u8; 32]) {
295 BoolEvaluator::with_local(|e| {
296 DefaultSecureRng::with_local_mut(|rng| {
297 let parameters = e.parameters();
298 let ring_size = parameters.rlwe_n().0;
299
300 let rlwe_count = ((m.len() as f64 / ring_size as f64).ceil())
301 .to_usize()
302 .unwrap();
303
304 let mut seed = <DefaultSecureRng as NewWithSeed>::Seed::default();
305 rng.fill_bytes(&mut seed);
306 let mut prng = DefaultSecureRng::new_seeded(seed);
307
308 let sk_u = self.sk_u_rlwe();
309
310 // encrypt `m` into ceil(len(m)/N) RLWE ciphertexts
311 let rlwes = (0..rlwe_count)
312 .map(|index| {
313 let mut message = vec![<Mat as Matrix>::MatElement::zero(); ring_size];
314 m[(index * ring_size)..std::cmp::min(m.len(), (index + 1) * ring_size)]
315 .iter()
316 .enumerate()
317 .for_each(|(i, v)| {
318 if *v {
319 message[i] = parameters.rlwe_q().true_el()
320 } else {
321 message[i] = parameters.rlwe_q().false_el()
322 }
323 });
324
325 // encrypt message
326 let mut rlwe_out =
327 <<Mat as Matrix>::R as RowEntity>::zeros(parameters.rlwe_n().0);
328
329 seeded_secret_key_encrypt_rlwe(
330 &message,
331 &mut rlwe_out,
332 &sk_u,
333 e.pbs_info().modop_rlweq(),
334 e.pbs_info().nttop_rlweq(),
335 &mut prng,
336 rng,
337 );
338
339 rlwe_out
340 })
341 .collect_vec();
342
343 (rlwes, seed)
344 })
345 })
346 }
347 }
348
349 impl KeySwitchWithId<Mat> for Mat {
350 /// Key switch RLWE ciphertext `Self` from user j's RLWE secret u_j
351 /// to ideal RLWE secret `s` of non-interactive multi-party protocol.
352 ///
353 /// - user_id: user j's user_id in the protocol
354 fn key_switch(&self, user_id: usize) -> Mat {
355 BoolEvaluator::with_local(|e| {
356 assert!(self.dimension() == (2, e.parameters().rlwe_n().0));
357 let server_key = BOOL_SERVER_KEY.get().unwrap();
358 let ksk = server_key.ui_to_s_ksk(user_id);
359 let decomposer = e.ni_ui_to_s_ks_decomposer().as_ref().unwrap();
360
361 // perform key switch
362 rlwe_key_switch(
363 self,
364 ksk.as_ref(),
365 ksk.shoup_repr(),
366 decomposer,
367 e.pbs_info().nttop_rlweq(),
368 e.pbs_info().modop_rlweq(),
369 )
370 })
371 }
372 }
373
374 impl<C> KeySwitchWithId<BatchedFheBools<C>> for NonInteractiveBatchedFheBools<C>
375 where
376 C: KeySwitchWithId<C>,
377 {
378 /// Key switch `Self`'s vector of RLWE ciphertexts from user j's RLWE
379 /// secret u_j to ideal RLWE secret `s` of non-interactive
380 /// multi-party protocol.
381 ///
382 /// Returns vector of key switched RLWE ciphertext as `BatchedFheBools`
383 /// which can then be used to extract individual Bool LWE ciphertexts.
384 ///
385 /// - user_id: user j's user_id in the protocol
386 fn key_switch(&self, user_id: usize) -> BatchedFheBools<C> {
387 let data = self
388 .data
389 .iter()
390 .map(|c| c.key_switch(user_id))
391 .collect_vec();
392 BatchedFheBools { data }
393 }
394 }
395}
396
397#[cfg(test)]
398mod tests {
399 use itertools::Itertools;
400 use rand::{thread_rng, RngCore};
401
402 use crate::{
403 bool::{
404 evaluator::BooleanGates,
405 keys::tests::{ideal_sk_rlwe, measure_noise_lwe},
406 },
407 Encoder, Encryptor, KeySwitchWithId, MultiPartyDecryptor,
408 };
409
410 use super::*;
411
412 #[test]
413 fn non_interactive_mp_bool_nand() {
414 set_parameter_set(ParameterSelector::NonInteractiveLTE2Party);
415 let mut seed = [0u8; 32];
416 thread_rng().fill_bytes(&mut seed);
417 set_common_reference_seed(seed);
418
419 let parties = 2;
420
421 let cks = (0..parties).map(|_| gen_client_key()).collect_vec();
422
423 let key_shares = cks
424 .iter()
425 .enumerate()
426 .map(|(user_index, ck)| gen_server_key_share(user_index, parties, ck))
427 .collect_vec();
428
429 let seeded_server_key = aggregate_server_key_shares(&key_shares);
430 seeded_server_key.set_server_key();
431
432 let parameters = BoolEvaluator::with_local(|e| e.parameters().clone());
433 let rlwe_modop = parameters.default_rlwe_modop();
434
435 let ideal_sk_rlwe = ideal_sk_rlwe(&cks);
436
437 let mut m0 = false;
438 let mut m1 = true;
439
440 let mut ct0 = {
441 let ct: NonInteractiveBatchedFheBools<_> = cks[0].encrypt(vec![m0].as_slice());
442 let ct = ct.key_switch(0);
443 ct.extract(0)
444 };
445 let mut ct1 = {
446 let ct: NonInteractiveBatchedFheBools<_> = cks[1].encrypt(vec![m1].as_slice());
447 let ct = ct.key_switch(1);
448 ct.extract(0)
449 };
450
451 for _ in 0..1000 {
452 // let now = std::time::Instant::now();
453 let ct_out =
454 BoolEvaluator::with_local_mut(|e| e.xor(&ct0, &ct1, RuntimeServerKey::global()));
455 // println!("Time: {:?}", now.elapsed());
456
457 let decryption_shares = cks
458 .iter()
459 .map(|k| k.gen_decryption_share(&ct_out))
460 .collect_vec();
461 let m_out = cks[0].aggregate_decryption_shares(&ct_out, &decryption_shares);
462
463 let m_expected = m0 ^ m1;
464
465 {
466 let noise = measure_noise_lwe(
467 &ct_out,
468 parameters.rlwe_q().encode(m_expected),
469 &ideal_sk_rlwe,
470 &rlwe_modop,
471 );
472 println!("Noise: {noise}");
473 }
474
475 assert!(m_out == m_expected, "Expected {m_expected} but got {m_out}");
476
477 m1 = m0;
478 m0 = m_out;
479
480 ct1 = ct0;
481 ct0 = ct_out;
482 }
483 }
484}