arnaucube
arnaucube/phantom-zonePublic Self-hosted repository
phantom-zone/src/bool/print_noise.rs
src/bool/print_noise.rsRaw
  1use std::{fmt::Debug, iter::Sum};
  2
  3use itertools::izip;
  4use num_traits::{FromPrimitive, PrimInt, Zero};
  5use rand_distr::uniform::SampleUniform;
  6
  7use crate::{
  8    backend::{GetModulus, Modulus},
  9    decomposer::{Decomposer, NumInfo, RlweDecomposer},
 10    lwe::{decrypt_lwe, lwe_key_switch},
 11    parameters::{BoolParameters, CiphertextModulus},
 12    random::{DefaultSecureRng, RandomFillUniformInModulus},
 13    rgsw::{
 14        decrypt_rlwe, rlwe_auto, rlwe_auto_scratch_rows, RlweCiphertextMutRef, RlweKskRef,
 15        RuntimeScratchMutRef,
 16    },
 17    utils::{encode_x_pow_si_with_emebedding_factor, tests::Stats, TryConvertFrom1},
 18    ArithmeticOps, ClientKey, MatrixEntity, MatrixMut, ModInit, Ntt, NttInit, RowEntity, RowMut,
 19    VectorOps,
 20};
 21
 22use super::keys::tests::{ideal_sk_lwe, ideal_sk_rlwe};
 23
 24pub(crate) trait CollectRuntimeServerKeyStats {
 25    type M;
 26    /// RGSW ciphertext X^{s[s_index]} in evaluation domain where `s` the LWE
 27    /// secret
 28    fn rgsw_cts_lwe_si(&self, s_index: usize) -> &Self::M;
 29    /// Auto key in evaluation domain for automorphism g^k. For auto key for
 30    /// automorphism corresponding to -g, set k = 0
 31    fn galois_key_for_auto(&self, k: usize) -> &Self::M;
 32    /// LWE key switching key
 33    fn lwe_ksk(&self) -> &Self::M;
 34}
 35
 36#[derive(Default)]
 37struct ServerKeyStats<T> {
 38    /// Distribution of noise in RGSW ciphertexts
 39    ///
 40    /// We collect statistics for RLWE'(-sm) separately from RLWE'(m) because
 41    /// non-interactive protocol differents between the two. Although we expect
 42    /// the distribution of noise in both to be the same.
 43    brk_rgsw_cts: (Stats<T>, Stats<T>),
 44    /// Distribtion of noise added to RLWE ciphertext after automorphism using
 45    /// Server auto keys.
 46    post_1_auto: Stats<T>,
 47    /// Distribution of noise added in LWE key switching from LWE_{q, s} to
 48    /// LWE_{q, z} where `z` is ideal LWE secret and `s` is ideal RLWE secret
 49    /// using Server's LWE key switching key.
 50    post_lwe_key_switch: Stats<T>,
 51}
 52
 53impl<T: PrimInt + FromPrimitive + Debug + Sum> ServerKeyStats<T>
 54where
 55    T: for<'a> Sum<&'a T>,
 56{
 57    fn new() -> Self {
 58        ServerKeyStats {
 59            brk_rgsw_cts: (Stats::default(), Stats::default()),
 60            post_1_auto: Stats::default(),
 61            post_lwe_key_switch: Stats::default(),
 62        }
 63    }
 64
 65    fn add_noise_brk_rgsw_cts_nsm(&mut self, noise: &[T]) {
 66        self.brk_rgsw_cts.0.add_many_samples(noise);
 67    }
 68
 69    fn add_noise_brk_rgsw_cts_m(&mut self, noise: &[T]) {
 70        self.brk_rgsw_cts.1.add_many_samples(noise);
 71    }
 72
 73    fn add_noise_post_1_auto(&mut self, noise: &[T]) {
 74        self.post_1_auto.add_many_samples(&noise);
 75    }
 76
 77    fn add_noise_post_kwe_key_switch(&mut self, noise: &[T]) {
 78        self.post_lwe_key_switch.add_many_samples(&noise);
 79    }
 80
 81    fn merge_in(&mut self, other: &Self) {
 82        self.brk_rgsw_cts.0.merge_in(&other.brk_rgsw_cts.0);
 83        self.brk_rgsw_cts.1.merge_in(&other.brk_rgsw_cts.1);
 84
 85        self.post_1_auto.merge_in(&other.post_1_auto);
 86        self.post_lwe_key_switch
 87            .merge_in(&other.post_lwe_key_switch);
 88    }
 89}
 90
 91fn collect_server_key_stats<
 92    M: MatrixEntity + MatrixMut,
 93    D: Decomposer<Element = M::MatElement>,
 94    NttOp: NttInit<CiphertextModulus<M::MatElement>> + Ntt<Element = M::MatElement>,
 95    ModOp: VectorOps<Element = M::MatElement>
 96        + ArithmeticOps<Element = M::MatElement>
 97        + ModInit<M = CiphertextModulus<M::MatElement>>
 98        + GetModulus<M = CiphertextModulus<M::MatElement>, Element = M::MatElement>,
 99    S: CollectRuntimeServerKeyStats<M = M>,
100>(
101    parameters: BoolParameters<M::MatElement>,
102    client_keys: &[ClientKey],
103    server_key: &S,
104) -> ServerKeyStats<i64>
105where
106    M::R: RowMut + RowEntity + TryConvertFrom1<[i32], CiphertextModulus<M::MatElement>> + Clone,
107    M::MatElement: Copy + PrimInt + FromPrimitive + SampleUniform + Zero + Debug + NumInfo,
108{
109    let ideal_sk_rlwe = ideal_sk_rlwe(client_keys);
110    let ideal_sk_lwe = ideal_sk_lwe(client_keys);
111
112    let embedding_factor = (2 * parameters.rlwe_n().0) / parameters.br_q();
113    let rlwe_n = parameters.rlwe_n().0;
114    let rlwe_q = parameters.rlwe_q();
115    let lwe_q = parameters.lwe_q();
116    let rlwe_modop = ModOp::new(rlwe_q.clone());
117    let rlwe_nttop = NttOp::new(rlwe_q, rlwe_n);
118    let lwe_modop = ModOp::new(*parameters.lwe_q());
119
120    let rlwe_x_rgsw_decomposer = parameters.rlwe_rgsw_decomposer::<D>();
121    let (rlwe_x_rgsw_gadget_a, rlwe_x_rgsw_gadget_b) = (
122        rlwe_x_rgsw_decomposer.a().gadget_vector(),
123        rlwe_x_rgsw_decomposer.b().gadget_vector(),
124    );
125
126    let lwe_ks_decomposer = parameters.lwe_decomposer::<D>();
127
128    let mut server_key_stats = ServerKeyStats::new();
129
130    let mut rng = DefaultSecureRng::new();
131
132    // RGSW ciphertext noise
133    // Check noise in RGSW ciphertexts of ideal LWE secret elements
134    {
135        ideal_sk_lwe.iter().enumerate().for_each(|(s_index, s_i)| {
136            let rgsw_ct_i = server_key.rgsw_cts_lwe_si(s_index);
137
138            // X^{s[i]}
139            let m_si = encode_x_pow_si_with_emebedding_factor::<M::R, _>(
140                *s_i,
141                embedding_factor,
142                rlwe_n,
143                rlwe_q,
144            );
145
146            // RLWE'(-sm)
147            let mut neg_s_eval = M::R::try_convert_from(ideal_sk_rlwe.as_slice(), rlwe_q);
148            rlwe_modop.elwise_neg_mut(neg_s_eval.as_mut());
149            rlwe_nttop.forward(neg_s_eval.as_mut());
150
151            for j in 0..rlwe_x_rgsw_decomposer.a().decomposition_count().0 {
152                // RLWE(B^{j} * -s[X]*X^{s_lwe[i]})
153
154                // -s[X]*X^{s_lwe[i]}*B_j
155                let mut m_ideal = m_si.clone();
156                rlwe_nttop.forward(m_ideal.as_mut());
157                rlwe_modop.elwise_mul_mut(m_ideal.as_mut(), neg_s_eval.as_ref());
158                rlwe_nttop.backward(m_ideal.as_mut());
159                rlwe_modop.elwise_scalar_mul_mut(m_ideal.as_mut(), &rlwe_x_rgsw_gadget_a[j]);
160
161                // RLWE(-s*X^{s_lwe[i]}*B_j)
162                let mut rlwe_ct = M::zeros(2, rlwe_n);
163                rlwe_ct
164                    .get_row_mut(0)
165                    .copy_from_slice(rgsw_ct_i.get_row_slice(j));
166                rlwe_ct.get_row_mut(1).copy_from_slice(
167                    rgsw_ct_i.get_row_slice(j + rlwe_x_rgsw_decomposer.a().decomposition_count().0),
168                );
169                // RGSW ciphertexts are in eval domain. We put RLWE ciphertexts back in
170                // coefficient domain
171                rlwe_ct
172                    .iter_rows_mut()
173                    .for_each(|r| rlwe_nttop.backward(r.as_mut()));
174
175                let mut m_back = M::R::zeros(rlwe_n);
176                decrypt_rlwe(
177                    &rlwe_ct,
178                    &ideal_sk_rlwe,
179                    &mut m_back,
180                    &rlwe_nttop,
181                    &rlwe_modop,
182                );
183
184                // diff
185                rlwe_modop.elwise_sub_mut(m_back.as_mut(), m_ideal.as_ref());
186                server_key_stats.add_noise_brk_rgsw_cts_nsm(&Vec::<i64>::try_convert_from(
187                    m_back.as_ref(),
188                    rlwe_q,
189                ));
190            }
191
192            // RLWE'(m)
193            for j in 0..rlwe_x_rgsw_decomposer.b().decomposition_count().0 {
194                // RLWE(B^{j} * X^{s_lwe[i]})
195
196                // X^{s_lwe[i]}*B_j
197                let mut m_ideal = m_si.clone();
198                rlwe_modop.elwise_scalar_mul_mut(m_ideal.as_mut(), &rlwe_x_rgsw_gadget_b[j]);
199
200                // RLWE(X^{s_lwe[i]}*B_j)
201                let mut rlwe_ct = M::zeros(2, rlwe_n);
202                rlwe_ct.get_row_mut(0).copy_from_slice(
203                    rgsw_ct_i.get_row_slice(
204                        j + (2 * rlwe_x_rgsw_decomposer.a().decomposition_count().0),
205                    ),
206                );
207                rlwe_ct
208                    .get_row_mut(1)
209                    .copy_from_slice(rgsw_ct_i.get_row_slice(
210                        j + (2 * rlwe_x_rgsw_decomposer.a().decomposition_count().0)
211                            + rlwe_x_rgsw_decomposer.b().decomposition_count().0,
212                    ));
213                rlwe_ct
214                    .iter_rows_mut()
215                    .for_each(|r| rlwe_nttop.backward(r.as_mut()));
216
217                let mut m_back = M::R::zeros(rlwe_n);
218                decrypt_rlwe(
219                    &rlwe_ct,
220                    &ideal_sk_rlwe,
221                    &mut m_back,
222                    &rlwe_nttop,
223                    &rlwe_modop,
224                );
225
226                // diff
227                rlwe_modop.elwise_sub_mut(m_back.as_mut(), m_ideal.as_ref());
228                server_key_stats.add_noise_brk_rgsw_cts_m(&Vec::<i64>::try_convert_from(
229                    m_back.as_ref(),
230                    rlwe_q,
231                ));
232            }
233        });
234    }
235
236    // Noise in ciphertext after 1 auto
237    // For each auto key g^k. Sample random polynomial m(X) and multiply with
238    // -s(X^{g^k}) using key corresponding to auto g^k. Then check the noise in
239    // resutling RLWE(m(X) * -s(X^{g^k}))
240    {
241        let neg_s = {
242            let mut s = M::R::try_convert_from(ideal_sk_rlwe.as_slice(), rlwe_q);
243            rlwe_modop.elwise_neg_mut(s.as_mut());
244            s
245        };
246        let g = parameters.g();
247        let br_q = parameters.br_q();
248        let g_dlogs = parameters.auto_element_dlogs();
249        let auto_decomposer = parameters.auto_decomposer::<D>();
250        let mut scratch_matrix = M::zeros(rlwe_auto_scratch_rows(&auto_decomposer), rlwe_n);
251        let mut scratch_matrix_ref = RuntimeScratchMutRef::new(scratch_matrix.as_mut());
252
253        g_dlogs.iter().for_each(|k| {
254            let g_pow_k = if *k == 0 {
255                -(g as isize)
256            } else {
257                (g.pow(*k as u32) % br_q) as isize
258            };
259
260            // Send s(X) -> s(X^{g^k})
261            let (auto_index_map, auto_sign_map) = crate::rgsw::generate_auto_map(rlwe_n, g_pow_k);
262            let mut neg_s_g_k = M::R::zeros(rlwe_n);
263            izip!(
264                neg_s.as_ref().iter(),
265                auto_index_map.iter(),
266                auto_sign_map.iter()
267            )
268            .for_each(|(el, to_index, to_sign)| {
269                if !to_sign {
270                    neg_s_g_k.as_mut()[*to_index] = rlwe_modop.neg(el);
271                } else {
272                    neg_s_g_k.as_mut()[*to_index] = *el;
273                }
274            });
275
276            let mut m = M::R::zeros(rlwe_n);
277            RandomFillUniformInModulus::random_fill(&mut rng, rlwe_q, m.as_mut());
278
279            // We want -m(X^{g^k})s(X^{g^k}) after key switch
280            let want_m = {
281                let mut m_g_k_eval = M::R::zeros(rlwe_n);
282                // send m(X) -> m(X^{g^k})
283                izip!(
284                    m.as_ref().iter(),
285                    auto_index_map.iter(),
286                    auto_sign_map.iter()
287                )
288                .for_each(|(el, to_index, to_sign)| {
289                    if !to_sign {
290                        m_g_k_eval.as_mut()[*to_index] = rlwe_modop.neg(el);
291                    } else {
292                        m_g_k_eval.as_mut()[*to_index] = *el;
293                    }
294                });
295
296                rlwe_nttop.forward(m_g_k_eval.as_mut());
297                let mut s_g_k = neg_s_g_k.clone();
298                rlwe_nttop.forward(s_g_k.as_mut());
299                rlwe_modop.elwise_mul_mut(m_g_k_eval.as_mut(), s_g_k.as_ref());
300                rlwe_nttop.backward(m_g_k_eval.as_mut());
301                m_g_k_eval
302            };
303
304            // RLWE auto sends part A, A(X), of RLWE to A(X^{g^k}) and then multiplies it
305            // with -s(X^{g^k}) using auto key. Deliberately set RLWE = (0, m(X))
306            // (ie. m in part A) to get back RLWE(-m(X^{g^k})s(X^{g^k}))
307            let mut rlwe = M::zeros(2, rlwe_n);
308            rlwe.get_row_mut(0).copy_from_slice(m.as_ref());
309
310            rlwe_auto(
311                &mut RlweCiphertextMutRef::new(rlwe.as_mut()),
312                &RlweKskRef::new(
313                    server_key.galois_key_for_auto(*k).as_ref(),
314                    auto_decomposer.decomposition_count().0,
315                ),
316                &mut scratch_matrix_ref,
317                &auto_index_map,
318                &auto_sign_map,
319                &rlwe_modop,
320                &rlwe_nttop,
321                &auto_decomposer,
322                false,
323            );
324
325            // decrypt RLWE(-m(X)s(X^{g^k]}))
326            let mut back_m = M::R::zeros(rlwe_n);
327            decrypt_rlwe(&rlwe, &ideal_sk_rlwe, &mut back_m, &rlwe_nttop, &rlwe_modop);
328
329            // check difference
330            let mut diff = back_m;
331            rlwe_modop.elwise_sub_mut(diff.as_mut(), want_m.as_ref());
332            server_key_stats
333                .add_noise_post_1_auto(&Vec::<i64>::try_convert_from(diff.as_ref(), rlwe_q));
334        });
335
336        // sample random m
337
338        // key switch
339    }
340
341    // LWE Key switch
342    // LWE key switches LWE_in = LWE_{Q_ks,N, s}(m) = (b, a_0, ... a_N) -> LWE_out =
343    // LWE_{Q_{ks}, n, z}(m) = (b', a'_0, ..., a'n)
344    // If LWE_in = (0, a = {a_0, ..., a_N}), then LWE_out = LWE(-a \cdot s_{rlwe})
345    for _ in 0..100 {
346        let mut lwe_in = M::R::zeros(rlwe_n + 1);
347        RandomFillUniformInModulus::random_fill(&mut rng, lwe_q, &mut lwe_in.as_mut()[1..]);
348
349        // Key switch
350        let mut lwe_out = M::R::zeros(parameters.lwe_n().0 + 1);
351        lwe_key_switch(
352            &mut lwe_out,
353            &lwe_in,
354            server_key.lwe_ksk(),
355            &lwe_modop,
356            &lwe_ks_decomposer,
357        );
358
359        // -a \cdot s
360        let mut want_m = M::MatElement::zero();
361        izip!(lwe_in.as_ref().iter().skip(1), ideal_sk_rlwe.iter()).for_each(|(a, b)| {
362            want_m = lwe_modop.add(
363                &want_m,
364                &lwe_modop.mul(a, &lwe_q.map_element_from_i64(*b as i64)),
365            );
366        });
367        want_m = lwe_modop.neg(&want_m);
368
369        // decrypt lwe out
370        let back_m = decrypt_lwe(&lwe_out, &ideal_sk_lwe, &lwe_modop);
371
372        let noise = lwe_modop.sub(&want_m, &back_m);
373        server_key_stats.add_noise_post_kwe_key_switch(&vec![lwe_q.map_element_to_i64(&noise)]);
374    }
375
376    server_key_stats
377    // Auto keys noise
378
379    // Ksk noise
380}
381
382#[cfg(test)]
383mod tests {
384    use itertools::Itertools;
385
386    #[test]
387    #[cfg(feature = "interactive_mp")]
388    fn interactive_key_noise() {
389        use crate::{
390            aggregate_public_key_shares, aggregate_server_key_shares,
391            bool::{
392                evaluator::InteractiveMultiPartyCrs,
393                keys::{key_size::KeySize, ServerKeyEvaluationDomain},
394            },
395            gen_client_key, gen_mp_keys_phase2, interactive_multi_party_round1_share,
396            parameters::CiphertextModulus,
397            random::DefaultSecureRng,
398            set_common_reference_seed, set_parameter_set,
399            utils::WithLocal,
400            BoolEvaluator, DefaultDecomposer, ModularOpsU64, NttBackendU64,
401        };
402
403        use super::*;
404
405        set_parameter_set(crate::ParameterSelector::InteractiveLTE8Party);
406        set_common_reference_seed(InteractiveMultiPartyCrs::random().seed);
407        let parties = 8;
408
409        let mut server_key_stats = ServerKeyStats::default();
410        let mut server_key_share_size = 0usize;
411
412        for i in 0..2 {
413            let cks = (0..parties).map(|_| gen_client_key()).collect_vec();
414            let pk_shares = cks
415                .iter()
416                .map(|k| interactive_multi_party_round1_share(k))
417                .collect_vec();
418
419            let pk = aggregate_public_key_shares(&pk_shares);
420            let server_key_shares = cks
421                .iter()
422                .enumerate()
423                .map(|(index, k)| gen_mp_keys_phase2(k, index, parties, &pk))
424                .collect_vec();
425
426            // In 0th iteration measure server key size
427            if i == 0 {
428                // Server key share size of user with last id may not equal server key share
429                // sizes of other users if LWE dimension does not divides number of parties.
430                server_key_share_size = std::cmp::max(
431                    server_key_shares.first().unwrap().size(),
432                    server_key_shares.last().unwrap().size(),
433                );
434            }
435
436            // println!("Size: {}", server_key_shares[0].size());
437            let seeded_server_key = aggregate_server_key_shares(&server_key_shares);
438            let server_key_eval =
439                ServerKeyEvaluationDomain::<_, _, DefaultSecureRng, NttBackendU64>::from(
440                    &seeded_server_key,
441                );
442
443            let parameters = BoolEvaluator::with_local(|e| e.parameters().clone());
444            server_key_stats.merge_in(&collect_server_key_stats::<
445                _,
446                DefaultDecomposer<u64>,
447                NttBackendU64,
448                ModularOpsU64<CiphertextModulus<u64>>,
449                _,
450            >(parameters, &cks, &server_key_eval));
451        }
452
453        println!(
454            "Common reference seeded server key share key size: {} Bits",
455            server_key_share_size
456        );
457
458        println!(
459            "Rgsw nsm std log2 {}",
460            server_key_stats.brk_rgsw_cts.0.std_dev().log2()
461        );
462        println!(
463            "Rgsw m std log2 {}",
464            server_key_stats.brk_rgsw_cts.1.std_dev().log2()
465        );
466        println!(
467            "rlwe post 1 auto std log2 {}",
468            server_key_stats.post_1_auto.std_dev().log2()
469        );
470        println!(
471            "key switching noise rlwe secret s to lwe secret z std log2 {}",
472            server_key_stats.post_lwe_key_switch.std_dev().log2()
473        );
474    }
475
476    const K: usize = 100000;
477
478    // #[test]
479    // #[cfg(feature = "interactive_mp")]
480    // fn interactive_mp_bool_gates() {}
481
482    #[test]
483    #[cfg(feature = "non_interactive_mp")]
484    fn non_interactive_mp_bool_gates() {
485        use rand::{thread_rng, RngCore};
486
487        use crate::{
488            aggregate_server_key_shares,
489            backend::Modulus,
490            bool::{
491                keys::{
492                    tests::{ideal_sk_rlwe, measure_noise_lwe},
493                    NonInteractiveServerKeyEvaluationDomain,
494                },
495                print_noise::collect_server_key_stats,
496                NonInteractiveBatchedFheBools,
497            },
498            gen_client_key, gen_server_key_share,
499            parameters::CiphertextModulus,
500            random::DefaultSecureRng,
501            set_common_reference_seed, set_parameter_set,
502            utils::{tests::Stats, Global, WithLocal},
503            BoolEvaluator, BooleanGates, DefaultDecomposer, Encoder, Encryptor, KeySwitchWithId,
504            ModInit, ModularOpsU64, MultiPartyDecryptor, NttBackendU64, ParameterSelector,
505            RuntimeServerKey,
506        };
507
508        set_parameter_set(ParameterSelector::NonInteractiveLTE8Party);
509        let mut seed = [0u8; 32];
510        thread_rng().fill_bytes(&mut seed);
511        set_common_reference_seed(seed);
512
513        let parties = 8;
514
515        let cks = (0..parties).map(|_| gen_client_key()).collect_vec();
516
517        let server_key_shares = cks
518            .iter()
519            .enumerate()
520            .map(|(user_index, ck)| gen_server_key_share(user_index, parties, ck))
521            .collect_vec();
522
523        let seeded_server_key = aggregate_server_key_shares(&server_key_shares);
524        seeded_server_key.set_server_key();
525
526        let parameters = BoolEvaluator::with_local(|e| e.parameters().clone());
527        let rlwe_modop = ModularOpsU64::new(*parameters.rlwe_q());
528
529        let ideal_sk_rlwe = ideal_sk_rlwe(&cks);
530
531        let mut m0 = false;
532        let mut m1 = true;
533
534        let mut ct0 = {
535            let ct: NonInteractiveBatchedFheBools<_> = cks[0].encrypt(vec![m0].as_slice());
536            let ct = ct.key_switch(0);
537            ct.extract(0)
538        };
539        let mut ct1 = {
540            let ct: NonInteractiveBatchedFheBools<_> = cks[1].encrypt(vec![m1].as_slice());
541            let ct = ct.key_switch(1);
542            ct.extract(0)
543        };
544
545        let mut stats = Stats::new();
546
547        for _ in 0..K {
548            // let now = std::time::Instant::now();
549            let ct_out =
550                BoolEvaluator::with_local_mut(|e| e.xor(&ct0, &ct1, RuntimeServerKey::global()));
551            // println!("Time: {:?}", now.elapsed());
552
553            let decryption_shares = cks
554                .iter()
555                .map(|k| k.gen_decryption_share(&ct_out))
556                .collect_vec();
557            let m_out = cks[0].aggregate_decryption_shares(&ct_out, &decryption_shares);
558
559            let m_expected = m0 ^ m1;
560
561            {
562                let noise = measure_noise_lwe(
563                    &ct_out,
564                    parameters.rlwe_q().encode(m_expected),
565                    &ideal_sk_rlwe,
566                    &rlwe_modop,
567                );
568                stats.add_sample(parameters.rlwe_q().map_element_to_i64(&noise));
569            }
570
571            assert!(m_out == m_expected, "Expected {m_expected} but got {m_out}");
572
573            m1 = m0;
574            m0 = m_out;
575
576            ct1 = ct0;
577            ct0 = ct_out;
578        }
579
580        // server key statistics
581        let server_key_stats = collect_server_key_stats::<
582            _,
583            DefaultDecomposer<u64>,
584            NttBackendU64,
585            ModularOpsU64<CiphertextModulus<u64>>,
586            _,
587        >(
588            parameters,
589            &cks,
590            &NonInteractiveServerKeyEvaluationDomain::<_, _, DefaultSecureRng, NttBackendU64>::from(
591                &seeded_server_key,
592            ),
593        );
594
595        println!("## Bootstrapping Statistics ##");
596        println!("Bootstrapped ciphertext noise std_dev: {}", stats.std_dev());
597
598        println!("## Key Statistics ##");
599        println!(
600            "Rgsw nsm std_dev {}",
601            server_key_stats.brk_rgsw_cts.0.std_dev()
602        );
603        println!(
604            "Rgsw m std_dev {}",
605            server_key_stats.brk_rgsw_cts.1.std_dev()
606        );
607        println!(
608            "rlwe post 1 auto std_dev {}",
609            server_key_stats.post_1_auto.std_dev()
610        );
611        println!(
612            "key switching noise rlwe secret s to lwe secret z std_dev {}",
613            server_key_stats.post_lwe_key_switch.std_dev()
614        );
615        println!();
616    }
617
618    #[test]
619    #[cfg(feature = "non_interactive_mp")]
620    fn non_interactive_key_noise() {
621        use crate::{
622            aggregate_server_key_shares,
623            bool::{
624                evaluator::NonInteractiveMultiPartyCrs,
625                keys::{key_size::KeySize, NonInteractiveServerKeyEvaluationDomain},
626            },
627            decomposer::DefaultDecomposer,
628            gen_client_key, gen_server_key_share,
629            parameters::CiphertextModulus,
630            random::DefaultSecureRng,
631            set_common_reference_seed, set_parameter_set,
632            utils::WithLocal,
633            BoolEvaluator, ModularOpsU64, NttBackendU64,
634        };
635
636        use super::*;
637
638        set_parameter_set(crate::ParameterSelector::NonInteractiveLTE8Party);
639        set_common_reference_seed(NonInteractiveMultiPartyCrs::random().seed);
640        let parties = 8;
641
642        let mut server_key_stats = ServerKeyStats::default();
643        let mut server_key_share_size = 0;
644        for i in 0..2 {
645            let cks = (0..parties).map(|_| gen_client_key()).collect_vec();
646            let server_key_shares = cks
647                .iter()
648                .enumerate()
649                .map(|(user_id, k)| gen_server_key_share(user_id, parties, k))
650                .collect_vec();
651
652            // Collect server key size in the 0th iteration
653            if i == 0 {
654                // Server key share size may differ for user with last id from
655                // the share size of other users if the LWE dimension `n` is not
656                // divisible by no. of parties.
657                server_key_share_size = std::cmp::max(
658                    server_key_shares.first().unwrap().size(),
659                    server_key_shares.last().unwrap().size(),
660                );
661            }
662
663            let server_key = aggregate_server_key_shares(&server_key_shares);
664
665            let server_key_eval = NonInteractiveServerKeyEvaluationDomain::<
666                _,
667                _,
668                DefaultSecureRng,
669                NttBackendU64,
670            >::from(&server_key);
671
672            let parameters = BoolEvaluator::with_local(|e| e.parameters().clone());
673            server_key_stats.merge_in(&collect_server_key_stats::<
674                _,
675                DefaultDecomposer<u64>,
676                NttBackendU64,
677                ModularOpsU64<CiphertextModulus<u64>>,
678                _,
679            >(parameters, &cks, &server_key_eval));
680        }
681
682        println!(
683            "Common reference seeded server key share key size: {} Bits",
684            server_key_share_size
685        );
686        println!(
687            "Rgsw nsm std log2 {}",
688            server_key_stats.brk_rgsw_cts.0.std_dev().abs().log2()
689        );
690        println!(
691            "Rgsw m std log2 {}",
692            server_key_stats.brk_rgsw_cts.1.std_dev().abs().log2()
693        );
694        println!(
695            "rlwe post 1 auto std log2 {}",
696            server_key_stats.post_1_auto.std_dev().abs().log2()
697        );
698        println!(
699            "key switching noise rlwe secret s to lwe secret z std log2 {}",
700            server_key_stats.post_lwe_key_switch.std_dev().abs().log2()
701        );
702    }
703
704    #[test]
705    #[cfg(feature = "non_interactive_mp")]
706    fn enc_under_sk_and_key_switch() {
707        use rand::{thread_rng, Rng};
708
709        use crate::{
710            aggregate_server_key_shares,
711            bool::{keys::tests::ideal_sk_rlwe, ni_mp_api::NonInteractiveBatchedFheBools},
712            gen_client_key, gen_server_key_share,
713            rgsw::decrypt_rlwe,
714            set_common_reference_seed, set_parameter_set,
715            utils::{tests::Stats, TryConvertFrom1, WithLocal},
716            BoolEvaluator, Encoder, Encryptor, KeySwitchWithId, ModInit, ModularOpsU64,
717            NttBackendU64, NttInit, ParameterSelector, VectorOps,
718        };
719
720        set_parameter_set(ParameterSelector::NonInteractiveLTE2Party);
721        set_common_reference_seed([2; 32]);
722
723        let parties = 2;
724
725        let cks = (0..parties).map(|_| gen_client_key()).collect_vec();
726
727        let key_shares = cks
728            .iter()
729            .enumerate()
730            .map(|(user_index, ck)| gen_server_key_share(user_index, parties, ck))
731            .collect_vec();
732
733        let seeded_server_key = aggregate_server_key_shares(&key_shares);
734        seeded_server_key.set_server_key();
735
736        let parameters = BoolEvaluator::with_local(|e| e.parameters().clone());
737        let nttop = NttBackendU64::new(parameters.rlwe_q(), parameters.rlwe_n().0);
738        let rlwe_q_modop = ModularOpsU64::new(*parameters.rlwe_q());
739
740        let m = (0..parameters.rlwe_n().0)
741            .map(|_| thread_rng().gen_bool(0.5))
742            .collect_vec();
743        let ct: NonInteractiveBatchedFheBools<_> = cks[0].encrypt(m.as_slice());
744        let ct = ct.key_switch(0);
745
746        let ideal_rlwe_sk = ideal_sk_rlwe(&cks);
747
748        let message = m
749            .iter()
750            .map(|b| parameters.rlwe_q().encode(*b))
751            .collect_vec();
752
753        let mut m_out = vec![0u64; parameters.rlwe_n().0];
754        decrypt_rlwe(
755            &ct.data[0],
756            &ideal_rlwe_sk,
757            &mut m_out,
758            &nttop,
759            &rlwe_q_modop,
760        );
761
762        let mut diff = m_out;
763        rlwe_q_modop.elwise_sub_mut(diff.as_mut_slice(), message.as_ref());
764
765        let mut stats = Stats::new();
766        stats.add_many_samples(&Vec::<i64>::try_convert_from(
767            diff.as_slice(),
768            parameters.rlwe_q(),
769        ));
770        println!("Noise std log2: {}", stats.std_dev().abs().log2());
771    }
772
773    #[test]
774    fn mod_switch_noise() {
775        // Experiment to check mod switch noise using different secret dist in
776        // multi-party setting
777
778        use itertools::izip;
779        use num_traits::ToPrimitive;
780
781        use crate::{
782            backend::{Modulus, ModulusPowerOf2},
783            parameters::SecretKeyDistribution,
784            random::{DefaultSecureRng, RandomFillGaussian, RandomFillUniformInModulus},
785            utils::{fill_random_ternary_secret_with_hamming_weight, tests::Stats},
786            ArithmeticOps, ModInit,
787        };
788
789        fn mod_switch(v: u64, q_from: u64, q_to: u64) -> f64 {
790            (v as f64) * (q_to as f64) / q_from as f64
791        }
792
793        fn mod_switch_round(v: u64, q_from: u64, q_to: u64) -> u64 {
794            mod_switch(v, q_from, q_to).round().to_u64().unwrap()
795        }
796
797        fn mod_switch_odd(v: u64, q_from: u64, q_to: u64) -> u64 {
798            let odd_v = mod_switch(v, q_from, q_to).floor().to_u64().unwrap();
799            odd_v + ((odd_v & 1) ^ 1)
800        }
801
802        fn sample_secret(n: usize, dist: &SecretKeyDistribution) -> Vec<i32> {
803            let mut s = vec![0i32; n];
804            let mut rng = DefaultSecureRng::new();
805
806            match dist {
807                SecretKeyDistribution::ErrorDistribution => {
808                    RandomFillGaussian::random_fill(&mut rng, s.as_mut_slice());
809                }
810                SecretKeyDistribution::TernaryDistribution => {
811                    fill_random_ternary_secret_with_hamming_weight(&mut s, n >> 1, &mut rng);
812                }
813            }
814
815            s
816        }
817
818        let parties = 2;
819        let q_from = 1 << 40;
820        let q_to = 1 << 20;
821        let n = 480;
822        let lweq_in_modop = ModulusPowerOf2::new(q_from);
823        let lweq_out_modop = ModulusPowerOf2::new(q_to);
824        let secret_dist = SecretKeyDistribution::ErrorDistribution;
825
826        let mut stats_ms_noise = Stats::new();
827        let mut stats_ms_rounding_err = Stats::new();
828
829        for _ in 0..1000000 {
830            let mut rng = DefaultSecureRng::new();
831
832            // sample secrets
833
834            let s = {
835                let mut s = vec![0i32; n];
836                for _ in 0..parties {
837                    let temp = sample_secret(n, &secret_dist);
838                    izip!(s.iter_mut(), temp.iter()).for_each(|(si, ti)| {
839                        *si = *si + *ti;
840                    });
841                }
842                s
843            };
844
845            let m = 10;
846
847            // LWE encryption without noise
848            let mut lwe_in = vec![0u64; n + 1];
849            {
850                RandomFillUniformInModulus::random_fill(&mut rng, &q_from, &mut lwe_in[1..]);
851                let mut b = m;
852                izip!(lwe_in.iter().skip(1), s.iter()).for_each(|(ai, si)| {
853                    b = lweq_in_modop.add(
854                        &b,
855                        &lweq_in_modop.mul(ai, &q_from.map_element_from_i64(*si as i64)),
856                    );
857                });
858                lwe_in[0] = b;
859            }
860
861            // Mod switch
862            let lwe_out = lwe_in
863                .iter()
864                .map(|v| {
865                    // mod_switch_round(*v, q_from, q_to)
866                    mod_switch_odd(*v, q_from, q_to)
867                })
868                .collect_vec();
869
870            let rounding_errors = izip!(lwe_out.iter(), lwe_in.iter())
871                .map(|(v_out, v_in)| {
872                    let r_i = mod_switch(*v_in, q_from, q_to) - (*v_out as f64);
873                    r_i
874                })
875                .collect_vec();
876            stats_ms_rounding_err.add_many_samples(&rounding_errors);
877
878            // LWE decrypt and calculate ms noise
879            let mut m_back = 0;
880            izip!(lwe_out.iter().skip(1), s.iter()).for_each(|(ai, si)| {
881                m_back = lweq_out_modop.add(
882                    &m_back,
883                    &lweq_out_modop.mul(ai, &q_from.map_element_from_i64(*si as i64)),
884                );
885            });
886            m_back = lweq_out_modop.sub(&lwe_out[0], &m_back);
887            let noise = lweq_out_modop.sub(&m_back, &m);
888            stats_ms_noise.add_many_samples(&vec![q_to.map_element_to_i64(&noise)]);
889        }
890
891        println!("ms noise variance: {}", stats_ms_noise.variance());
892        println!("ms rounding errors mean: {}", stats_ms_rounding_err.mean());
893        println!(
894            "ms rounding errors variance: {}",
895            stats_ms_rounding_err.variance()
896        );
897    }
898}