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::{DefaultSecureRng, NewWithSeed, RandomFillUniformInModulus},
171 rgsw::{key_switch, secret_key_encrypt_rlwe},
172 utils::{TryConvertFrom1, WithLocal},
173 Encryptor, KeySwitchWithId, Matrix, MatrixEntity, MatrixMut, MultiPartyDecryptor,
174 RowEntity, RowMut,
175 };
176 use itertools::Itertools;
177 use num_traits::{ToPrimitive, Zero};
178
179 use super::*;
180
181 type Mat = Vec<Vec<u64>>;
182
183 pub(super) struct BatchedFheBools<C> {
184 pub(super) data: Vec<C>,
185 }
186
187 impl<C: MatrixMut<MatElement = u64>> BatchedFheBools<C>
188 where
189 C::R: RowEntity + RowMut,
190 {
191 pub(super) fn extract(&self, index: usize) -> C::R {
192 BoolEvaluator::with_local(|e| {
193 let ring_size = e.parameters().rlwe_n().0;
194 let ct_index = index / ring_size;
195 let coeff_index = index % ring_size;
196 let mut lwe_out = C::R::zeros(e.parameters().rlwe_n().0 + 1);
197 sample_extract(
198 &mut lwe_out,
199 &self.data[ct_index],
200 e.pbs_info().modop_rlweq(),
201 coeff_index,
202 );
203 lwe_out
204 })
205 }
206 }
207
208 pub(super) struct NonInteractiveBatchedFheBools<C> {
209 data: Vec<C>,
210 }
211
212 impl<M: MatrixEntity + MatrixMut<MatElement = u64>> From<&(Vec<M::R>, [u8; 32])>
213 for NonInteractiveBatchedFheBools<M>
214 where
215 <M as Matrix>::R: RowMut,
216 {
217 fn from(value: &(Vec<M::R>, [u8; 32])) -> Self {
218 BoolEvaluator::with_local(|e| {
219 let parameters = e.parameters();
220 let ring_size = parameters.rlwe_n().0;
221 let rlwe_q = parameters.rlwe_q();
222
223 let mut prng = DefaultSecureRng::new_seeded(value.1);
224 let rlwes = value
225 .0
226 .iter()
227 .map(|partb| {
228 let mut rlwe = M::zeros(2, ring_size);
229
230 // sample A
231 RandomFillUniformInModulus::random_fill(
232 &mut prng,
233 rlwe_q,
234 rlwe.get_row_mut(0),
235 );
236
237 // Copy over B
238 rlwe.get_row_mut(1).copy_from_slice(partb.as_ref());
239
240 rlwe
241 })
242 .collect_vec();
243 Self { data: rlwes }
244 })
245 }
246 }
247
248 impl<K> Encryptor<[bool], NonInteractiveBatchedFheBools<Mat>> for K
249 where
250 K: Encryptor<[bool], (Mat, [u8; 32])>,
251 {
252 fn encrypt(&self, m: &[bool]) -> NonInteractiveBatchedFheBools<Mat> {
253 NonInteractiveBatchedFheBools::from(&K::encrypt(&self, m))
254 }
255 }
256
257 impl<K> Encryptor<[bool], (Mat, [u8; 32])> for K
258 where
259 K: NonInteractiveMultiPartyClientKey,
260 <Mat as Matrix>::R:
261 TryConvertFrom1<[K::Element], CiphertextModulus<<Mat as Matrix>::MatElement>>,
262 {
263 fn encrypt(&self, m: &[bool]) -> (Mat, [u8; 32]) {
264 BoolEvaluator::with_local(|e| {
265 DefaultSecureRng::with_local_mut(|rng| {
266 let parameters = e.parameters();
267 let ring_size = parameters.rlwe_n().0;
268
269 let rlwe_count = ((m.len() as f64 / ring_size as f64).ceil())
270 .to_usize()
271 .unwrap();
272
273 let mut seed = <DefaultSecureRng as NewWithSeed>::Seed::default();
274 rng.fill_bytes(&mut seed);
275 let mut prng = DefaultSecureRng::new_seeded(seed);
276
277 let sk_u = self.sk_u_rlwe();
278
279 // encrypt `m` into ceil(len(m)/N) RLWE ciphertexts
280 let rlwes = (0..rlwe_count)
281 .map(|index| {
282 let mut message = vec![<Mat as Matrix>::MatElement::zero(); ring_size];
283 m[(index * ring_size)..std::cmp::min(m.len(), (index + 1) * ring_size)]
284 .iter()
285 .enumerate()
286 .for_each(|(i, v)| {
287 if *v {
288 message[i] = parameters.rlwe_q().true_el()
289 } else {
290 message[i] = parameters.rlwe_q().false_el()
291 }
292 });
293
294 // encrypt message
295 let mut rlwe_out =
296 <<Mat as Matrix>::R as RowEntity>::zeros(parameters.rlwe_n().0);
297
298 secret_key_encrypt_rlwe(
299 &message,
300 &mut rlwe_out,
301 &sk_u,
302 e.pbs_info().modop_rlweq(),
303 e.pbs_info().nttop_rlweq(),
304 &mut prng,
305 rng,
306 );
307
308 rlwe_out
309 })
310 .collect_vec();
311
312 (rlwes, seed)
313 })
314 })
315 }
316 }
317
318 impl KeySwitchWithId<Mat> for Mat {
319 fn key_switch(&self, user_id: usize) -> Mat {
320 BoolEvaluator::with_local(|e| {
321 let server_key = BOOL_SERVER_KEY.get().unwrap();
322 let ksk = server_key.ui_to_s_ksk(user_id);
323 let decomposer = e.ni_ui_to_s_ks_decomposer().as_ref().unwrap();
324
325 // perform key switch
326 key_switch(
327 self,
328 ksk.as_ref(),
329 ksk.shoup_repr(),
330 decomposer,
331 e.pbs_info().nttop_rlweq(),
332 e.pbs_info().modop_rlweq(),
333 )
334 })
335 }
336 }
337
338 impl<C> KeySwitchWithId<BatchedFheBools<C>> for NonInteractiveBatchedFheBools<C>
339 where
340 C: KeySwitchWithId<C>,
341 {
342 fn key_switch(&self, user_id: usize) -> BatchedFheBools<C> {
343 let data = self
344 .data
345 .iter()
346 .map(|c| c.key_switch(user_id))
347 .collect_vec();
348 BatchedFheBools { data }
349 }
350 }
351}
352
353#[cfg(test)]
354mod tests {
355 use impl_enc_dec::NonInteractiveBatchedFheBools;
356 use itertools::{izip, Itertools};
357 use num_traits::{FromPrimitive, PrimInt, ToPrimitive, Zero};
358 use rand::{thread_rng, RngCore};
359
360 use crate::{
361 backend::{GetModulus, Modulus},
362 bool::{
363 evaluator::{BoolEncoding, BooleanGates},
364 keys::SinglePartyClientKey,
365 },
366 lwe::decrypt_lwe,
367 rgsw::decrypt_rlwe,
368 utils::{Stats, TryConvertFrom1},
369 ArithmeticOps, Encoder, Encryptor, KeySwitchWithId, ModInit, MultiPartyDecryptor, NttInit,
370 Row, VectorOps,
371 };
372
373 use super::*;
374
375 pub(crate) fn ideal_sk_rlwe(cks: &[ClientKey]) -> Vec<i32> {
376 let mut ideal_rlwe_sk = cks[0].sk_rlwe();
377 cks.iter().skip(1).for_each(|k| {
378 let sk_rlwe = k.sk_rlwe();
379 izip!(ideal_rlwe_sk.iter_mut(), sk_rlwe.iter()).for_each(|(a, b)| {
380 *a = *a + b;
381 });
382 });
383 ideal_rlwe_sk
384 }
385
386 pub(crate) fn measure_noise_lwe<
387 R: Row,
388 S,
389 Modop: ArithmeticOps<Element = R::Element>
390 + GetModulus<M = CiphertextModulus<R::Element>, Element = R::Element>,
391 >(
392 lwe_ct: &R,
393 m_expected: R::Element,
394 sk: &[S],
395 modop: &Modop,
396 ) -> f64
397 where
398 R: TryConvertFrom1<[S], CiphertextModulus<R::Element>>,
399 R::Element: Zero + FromPrimitive + PrimInt,
400 {
401 let noisy_m = decrypt_lwe(lwe_ct, &sk, modop);
402 let noise = modop.sub(&m_expected, &noisy_m);
403 modop
404 .modulus()
405 .map_element_to_i64(&noise)
406 .abs()
407 .to_f64()
408 .unwrap()
409 .log2()
410 }
411
412 #[test]
413 fn non_interactive_mp_bool_nand() {
414 set_parameter_set(ParameterSelector::NonInteractiveMultiPartyLessThanOrEqualTo16);
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..100 {
452 let ct_out =
453 BoolEvaluator::with_local_mut(|e| e.xor(&ct0, &ct1, RuntimeServerKey::global()));
454
455 let decryption_shares = cks
456 .iter()
457 .map(|k| k.gen_decryption_share(&ct_out))
458 .collect_vec();
459 let m_out = cks[0].aggregate_decryption_shares(&ct_out, &decryption_shares);
460
461 let m_expected = (m0 ^ m1);
462
463 {
464 let noise = measure_noise_lwe(
465 &ct_out,
466 parameters.rlwe_q().encode(m_expected),
467 &ideal_sk_rlwe,
468 &rlwe_modop,
469 );
470 println!("Noise: {noise}");
471 }
472
473 assert!(m_out == m_expected, "Expected {m_expected} but got {m_out}");
474
475 m1 = m0;
476 m0 = m_out;
477
478 ct1 = ct0;
479 ct0 = ct_out;
480 }
481 }
482
483 #[test]
484 fn trialtest() {
485 set_parameter_set(ParameterSelector::NonInteractiveMultiPartyLessThanOrEqualTo16);
486 set_common_reference_seed([2; 32]);
487
488 let parties = 2;
489
490 let cks = (0..parties).map(|_| gen_client_key()).collect_vec();
491
492 let key_shares = cks
493 .iter()
494 .enumerate()
495 .map(|(user_index, ck)| gen_server_key_share(user_index, parties, ck))
496 .collect_vec();
497
498 let seeded_server_key = aggregate_server_key_shares(&key_shares);
499 seeded_server_key.set_server_key();
500
501 let m = vec![false, true];
502 let ct: NonInteractiveBatchedFheBools<_> = cks[0].encrypt(m.as_slice());
503 let ct = ct.key_switch(0);
504
505 let parameters = BoolEvaluator::with_local(|e| e.parameters().clone());
506 let nttop = NttBackendU64::new(parameters.rlwe_q(), parameters.rlwe_n().0);
507 let rlwe_q_modop = ModularOpsU64::new(*parameters.rlwe_q());
508
509 let mut ideal_rlwe_sk = vec![0i32; parameters.rlwe_n().0];
510 cks.iter().for_each(|k| {
511 let sk_rlwe = k.sk_rlwe();
512 izip!(ideal_rlwe_sk.iter_mut(), sk_rlwe.iter()).for_each(|(a, b)| {
513 *a = *a + b;
514 });
515 });
516
517 let message = m
518 .iter()
519 .map(|b| parameters.rlwe_q().encode(*b))
520 .collect_vec();
521
522 let mut m_out = vec![0u64; parameters.rlwe_n().0];
523 decrypt_rlwe(
524 &ct.data[0],
525 &ideal_rlwe_sk,
526 &mut m_out,
527 &nttop,
528 &rlwe_q_modop,
529 );
530
531 let mut diff = m_out;
532 rlwe_q_modop.elwise_sub_mut(diff.as_mut_slice(), message.as_ref());
533
534 let mut stats = Stats::new();
535 stats.add_more(&Vec::<i64>::try_convert_from(
536 diff.as_slice(),
537 parameters.rlwe_q(),
538 ));
539 println!("Noise: {}", stats.std_dev().abs().log2());
540 }
541}