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