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