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