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