use std::{fmt::Debug, iter::Sum}; use itertools::izip; use num_traits::{FromPrimitive, PrimInt, Zero}; use rand_distr::uniform::SampleUniform; use crate::{ backend::{GetModulus, Modulus}, decomposer::{Decomposer, NumInfo, RlweDecomposer}, lwe::{decrypt_lwe, lwe_key_switch}, parameters::{BoolParameters, CiphertextModulus}, random::{DefaultSecureRng, RandomFillUniformInModulus}, rgsw::{ decrypt_rlwe, rlwe_auto, rlwe_auto_scratch_rows, RlweCiphertextMutRef, RlweKskRef, RuntimeScratchMutRef, }, utils::{encode_x_pow_si_with_emebedding_factor, tests::Stats, TryConvertFrom1}, ArithmeticOps, ClientKey, MatrixEntity, MatrixMut, ModInit, Ntt, NttInit, RowEntity, RowMut, VectorOps, }; use super::keys::tests::{ideal_sk_lwe, ideal_sk_rlwe}; pub(crate) trait CollectRuntimeServerKeyStats { type M; /// RGSW ciphertext X^{s[s_index]} in evaluation domain where `s` the LWE /// secret fn rgsw_cts_lwe_si(&self, s_index: usize) -> &Self::M; /// Auto key in evaluation domain for automorphism g^k. For auto key for /// automorphism corresponding to -g, set k = 0 fn galois_key_for_auto(&self, k: usize) -> &Self::M; /// LWE key switching key fn lwe_ksk(&self) -> &Self::M; } #[derive(Default)] struct ServerKeyStats { /// Distribution of noise in RGSW ciphertexts /// /// We collect statistics for RLWE'(-sm) separately from RLWE'(m) because /// non-interactive protocol differents between the two. Although we expect /// the distribution of noise in both to be the same. brk_rgsw_cts: (Stats, Stats), /// Distribtion of noise added to RLWE ciphertext after automorphism using /// Server auto keys. post_1_auto: Stats, /// Distribution of noise added in LWE key switching from LWE_{q, s} to /// LWE_{q, z} where `z` is ideal LWE secret and `s` is ideal RLWE secret /// using Server's LWE key switching key. post_lwe_key_switch: Stats, } impl ServerKeyStats where T: for<'a> Sum<&'a T>, { fn new() -> Self { ServerKeyStats { brk_rgsw_cts: (Stats::default(), Stats::default()), post_1_auto: Stats::default(), post_lwe_key_switch: Stats::default(), } } fn add_noise_brk_rgsw_cts_nsm(&mut self, noise: &[T]) { self.brk_rgsw_cts.0.add_many_samples(noise); } fn add_noise_brk_rgsw_cts_m(&mut self, noise: &[T]) { self.brk_rgsw_cts.1.add_many_samples(noise); } fn add_noise_post_1_auto(&mut self, noise: &[T]) { self.post_1_auto.add_many_samples(&noise); } fn add_noise_post_kwe_key_switch(&mut self, noise: &[T]) { self.post_lwe_key_switch.add_many_samples(&noise); } fn merge_in(&mut self, other: &Self) { self.brk_rgsw_cts.0.merge_in(&other.brk_rgsw_cts.0); self.brk_rgsw_cts.1.merge_in(&other.brk_rgsw_cts.1); self.post_1_auto.merge_in(&other.post_1_auto); self.post_lwe_key_switch .merge_in(&other.post_lwe_key_switch); } } fn collect_server_key_stats< M: MatrixEntity + MatrixMut, D: Decomposer, NttOp: NttInit> + Ntt, ModOp: VectorOps + ArithmeticOps + ModInit> + GetModulus, Element = M::MatElement>, S: CollectRuntimeServerKeyStats, >( parameters: BoolParameters, client_keys: &[ClientKey], server_key: &S, ) -> ServerKeyStats where M::R: RowMut + RowEntity + TryConvertFrom1<[i32], CiphertextModulus> + Clone, M::MatElement: Copy + PrimInt + FromPrimitive + SampleUniform + Zero + Debug + NumInfo, { let ideal_sk_rlwe = ideal_sk_rlwe(client_keys); let ideal_sk_lwe = ideal_sk_lwe(client_keys); let embedding_factor = (2 * parameters.rlwe_n().0) / parameters.br_q(); let rlwe_n = parameters.rlwe_n().0; let rlwe_q = parameters.rlwe_q(); let lwe_q = parameters.lwe_q(); let rlwe_modop = ModOp::new(rlwe_q.clone()); let rlwe_nttop = NttOp::new(rlwe_q, rlwe_n); let lwe_modop = ModOp::new(*parameters.lwe_q()); let rlwe_x_rgsw_decomposer = parameters.rlwe_rgsw_decomposer::(); let (rlwe_x_rgsw_gadget_a, rlwe_x_rgsw_gadget_b) = ( rlwe_x_rgsw_decomposer.a().gadget_vector(), rlwe_x_rgsw_decomposer.b().gadget_vector(), ); let lwe_ks_decomposer = parameters.lwe_decomposer::(); let mut server_key_stats = ServerKeyStats::new(); let mut rng = DefaultSecureRng::new(); // RGSW ciphertext noise // Check noise in RGSW ciphertexts of ideal LWE secret elements { ideal_sk_lwe.iter().enumerate().for_each(|(s_index, s_i)| { let rgsw_ct_i = server_key.rgsw_cts_lwe_si(s_index); // X^{s[i]} let m_si = encode_x_pow_si_with_emebedding_factor::( *s_i, embedding_factor, rlwe_n, rlwe_q, ); // RLWE'(-sm) let mut neg_s_eval = M::R::try_convert_from(ideal_sk_rlwe.as_slice(), rlwe_q); rlwe_modop.elwise_neg_mut(neg_s_eval.as_mut()); rlwe_nttop.forward(neg_s_eval.as_mut()); for j in 0..rlwe_x_rgsw_decomposer.a().decomposition_count().0 { // RLWE(B^{j} * -s[X]*X^{s_lwe[i]}) // -s[X]*X^{s_lwe[i]}*B_j let mut m_ideal = m_si.clone(); rlwe_nttop.forward(m_ideal.as_mut()); rlwe_modop.elwise_mul_mut(m_ideal.as_mut(), neg_s_eval.as_ref()); rlwe_nttop.backward(m_ideal.as_mut()); rlwe_modop.elwise_scalar_mul_mut(m_ideal.as_mut(), &rlwe_x_rgsw_gadget_a[j]); // RLWE(-s*X^{s_lwe[i]}*B_j) let mut rlwe_ct = M::zeros(2, rlwe_n); rlwe_ct .get_row_mut(0) .copy_from_slice(rgsw_ct_i.get_row_slice(j)); rlwe_ct.get_row_mut(1).copy_from_slice( rgsw_ct_i.get_row_slice(j + rlwe_x_rgsw_decomposer.a().decomposition_count().0), ); // RGSW ciphertexts are in eval domain. We put RLWE ciphertexts back in // coefficient domain rlwe_ct .iter_rows_mut() .for_each(|r| rlwe_nttop.backward(r.as_mut())); let mut m_back = M::R::zeros(rlwe_n); decrypt_rlwe( &rlwe_ct, &ideal_sk_rlwe, &mut m_back, &rlwe_nttop, &rlwe_modop, ); // diff rlwe_modop.elwise_sub_mut(m_back.as_mut(), m_ideal.as_ref()); server_key_stats.add_noise_brk_rgsw_cts_nsm(&Vec::::try_convert_from( m_back.as_ref(), rlwe_q, )); } // RLWE'(m) for j in 0..rlwe_x_rgsw_decomposer.b().decomposition_count().0 { // RLWE(B^{j} * X^{s_lwe[i]}) // X^{s_lwe[i]}*B_j let mut m_ideal = m_si.clone(); rlwe_modop.elwise_scalar_mul_mut(m_ideal.as_mut(), &rlwe_x_rgsw_gadget_b[j]); // RLWE(X^{s_lwe[i]}*B_j) let mut rlwe_ct = M::zeros(2, rlwe_n); rlwe_ct.get_row_mut(0).copy_from_slice( rgsw_ct_i.get_row_slice( j + (2 * rlwe_x_rgsw_decomposer.a().decomposition_count().0), ), ); rlwe_ct .get_row_mut(1) .copy_from_slice(rgsw_ct_i.get_row_slice( j + (2 * rlwe_x_rgsw_decomposer.a().decomposition_count().0) + rlwe_x_rgsw_decomposer.b().decomposition_count().0, )); rlwe_ct .iter_rows_mut() .for_each(|r| rlwe_nttop.backward(r.as_mut())); let mut m_back = M::R::zeros(rlwe_n); decrypt_rlwe( &rlwe_ct, &ideal_sk_rlwe, &mut m_back, &rlwe_nttop, &rlwe_modop, ); // diff rlwe_modop.elwise_sub_mut(m_back.as_mut(), m_ideal.as_ref()); server_key_stats.add_noise_brk_rgsw_cts_m(&Vec::::try_convert_from( m_back.as_ref(), rlwe_q, )); } }); } // Noise in ciphertext after 1 auto // For each auto key g^k. Sample random polynomial m(X) and multiply with // -s(X^{g^k}) using key corresponding to auto g^k. Then check the noise in // resutling RLWE(m(X) * -s(X^{g^k})) { let neg_s = { let mut s = M::R::try_convert_from(ideal_sk_rlwe.as_slice(), rlwe_q); rlwe_modop.elwise_neg_mut(s.as_mut()); s }; let g = parameters.g(); let br_q = parameters.br_q(); let g_dlogs = parameters.auto_element_dlogs(); let auto_decomposer = parameters.auto_decomposer::(); let mut scratch_matrix = M::zeros(rlwe_auto_scratch_rows(&auto_decomposer), rlwe_n); let mut scratch_matrix_ref = RuntimeScratchMutRef::new(scratch_matrix.as_mut()); g_dlogs.iter().for_each(|k| { let g_pow_k = if *k == 0 { -(g as isize) } else { (g.pow(*k as u32) % br_q) as isize }; // Send s(X) -> s(X^{g^k}) let (auto_index_map, auto_sign_map) = crate::rgsw::generate_auto_map(rlwe_n, g_pow_k); let mut neg_s_g_k = M::R::zeros(rlwe_n); izip!( neg_s.as_ref().iter(), auto_index_map.iter(), auto_sign_map.iter() ) .for_each(|(el, to_index, to_sign)| { if !to_sign { neg_s_g_k.as_mut()[*to_index] = rlwe_modop.neg(el); } else { neg_s_g_k.as_mut()[*to_index] = *el; } }); let mut m = M::R::zeros(rlwe_n); RandomFillUniformInModulus::random_fill(&mut rng, rlwe_q, m.as_mut()); // We want -m(X^{g^k})s(X^{g^k}) after key switch let want_m = { let mut m_g_k_eval = M::R::zeros(rlwe_n); // send m(X) -> m(X^{g^k}) izip!( m.as_ref().iter(), auto_index_map.iter(), auto_sign_map.iter() ) .for_each(|(el, to_index, to_sign)| { if !to_sign { m_g_k_eval.as_mut()[*to_index] = rlwe_modop.neg(el); } else { m_g_k_eval.as_mut()[*to_index] = *el; } }); rlwe_nttop.forward(m_g_k_eval.as_mut()); let mut s_g_k = neg_s_g_k.clone(); rlwe_nttop.forward(s_g_k.as_mut()); rlwe_modop.elwise_mul_mut(m_g_k_eval.as_mut(), s_g_k.as_ref()); rlwe_nttop.backward(m_g_k_eval.as_mut()); m_g_k_eval }; // RLWE auto sends part A, A(X), of RLWE to A(X^{g^k}) and then multiplies it // with -s(X^{g^k}) using auto key. Deliberately set RLWE = (0, m(X)) // (ie. m in part A) to get back RLWE(-m(X^{g^k})s(X^{g^k})) let mut rlwe = M::zeros(2, rlwe_n); rlwe.get_row_mut(0).copy_from_slice(m.as_ref()); rlwe_auto( &mut RlweCiphertextMutRef::new(rlwe.as_mut()), &RlweKskRef::new( server_key.galois_key_for_auto(*k).as_ref(), auto_decomposer.decomposition_count().0, ), &mut scratch_matrix_ref, &auto_index_map, &auto_sign_map, &rlwe_modop, &rlwe_nttop, &auto_decomposer, false, ); // decrypt RLWE(-m(X)s(X^{g^k]})) let mut back_m = M::R::zeros(rlwe_n); decrypt_rlwe(&rlwe, &ideal_sk_rlwe, &mut back_m, &rlwe_nttop, &rlwe_modop); // check difference let mut diff = back_m; rlwe_modop.elwise_sub_mut(diff.as_mut(), want_m.as_ref()); server_key_stats .add_noise_post_1_auto(&Vec::::try_convert_from(diff.as_ref(), rlwe_q)); }); // sample random m // key switch } // LWE Key switch // LWE key switches LWE_in = LWE_{Q_ks,N, s}(m) = (b, a_0, ... a_N) -> LWE_out = // LWE_{Q_{ks}, n, z}(m) = (b', a'_0, ..., a'n) // If LWE_in = (0, a = {a_0, ..., a_N}), then LWE_out = LWE(-a \cdot s_{rlwe}) for _ in 0..100 { let mut lwe_in = M::R::zeros(rlwe_n + 1); RandomFillUniformInModulus::random_fill(&mut rng, lwe_q, &mut lwe_in.as_mut()[1..]); // Key switch let mut lwe_out = M::R::zeros(parameters.lwe_n().0 + 1); lwe_key_switch( &mut lwe_out, &lwe_in, server_key.lwe_ksk(), &lwe_modop, &lwe_ks_decomposer, ); // -a \cdot s let mut want_m = M::MatElement::zero(); izip!(lwe_in.as_ref().iter().skip(1), ideal_sk_rlwe.iter()).for_each(|(a, b)| { want_m = lwe_modop.add( &want_m, &lwe_modop.mul(a, &lwe_q.map_element_from_i64(*b as i64)), ); }); want_m = lwe_modop.neg(&want_m); // decrypt lwe out let back_m = decrypt_lwe(&lwe_out, &ideal_sk_lwe, &lwe_modop); let noise = lwe_modop.sub(&want_m, &back_m); server_key_stats.add_noise_post_kwe_key_switch(&vec![lwe_q.map_element_to_i64(&noise)]); } server_key_stats // Auto keys noise // Ksk noise } #[cfg(test)] mod tests { use itertools::Itertools; #[test] #[cfg(feature = "interactive_mp")] fn interactive_key_noise() { use crate::{ aggregate_public_key_shares, aggregate_server_key_shares, bool::{ evaluator::InteractiveMultiPartyCrs, keys::{key_size::KeySize, ServerKeyEvaluationDomain}, }, collective_pk_share, collective_server_key_share, gen_client_key, parameters::CiphertextModulus, random::DefaultSecureRng, set_common_reference_seed, set_parameter_set, utils::WithLocal, BoolEvaluator, DefaultDecomposer, ModularOpsU64, NttBackendU64, }; use super::*; set_parameter_set(crate::ParameterSelector::InteractiveLTE8Party); set_common_reference_seed(InteractiveMultiPartyCrs::random().seed); let parties = 8; let mut server_key_stats = ServerKeyStats::default(); let mut server_key_share_size = 0usize; for i in 0..2 { let cks = (0..parties).map(|_| gen_client_key()).collect_vec(); let pk_shares = cks.iter().map(|k| collective_pk_share(k)).collect_vec(); let pk = aggregate_public_key_shares(&pk_shares); let server_key_shares = cks .iter() .enumerate() .map(|(index, k)| collective_server_key_share(k, index, parties, &pk)) .collect_vec(); // In 0th iteration measure server key size if i == 0 { // Server key share size of user with last id may not equal server key share // sizes of other users if LWE dimension does not divides number of parties. server_key_share_size = std::cmp::max( server_key_shares.first().unwrap().size(), server_key_shares.last().unwrap().size(), ); } // println!("Size: {}", server_key_shares[0].size()); let seeded_server_key = aggregate_server_key_shares(&server_key_shares); let server_key_eval = ServerKeyEvaluationDomain::<_, _, DefaultSecureRng, NttBackendU64>::from( &seeded_server_key, ); let parameters = BoolEvaluator::with_local(|e| e.parameters().clone()); server_key_stats.merge_in(&collect_server_key_stats::< _, DefaultDecomposer, NttBackendU64, ModularOpsU64>, _, >(parameters, &cks, &server_key_eval)); } println!( "Common reference seeded server key share key size: {} Bits", server_key_share_size ); println!( "Rgsw nsm std log2 {}", server_key_stats.brk_rgsw_cts.0.std_dev().log2() ); println!( "Rgsw m std log2 {}", server_key_stats.brk_rgsw_cts.1.std_dev().log2() ); println!( "rlwe post 1 auto std log2 {}", server_key_stats.post_1_auto.std_dev().log2() ); println!( "key switching noise rlwe secret s to lwe secret z std log2 {}", server_key_stats.post_lwe_key_switch.std_dev().log2() ); } const K: usize = 10; #[test] #[cfg(feature = "interactive_mp")] fn interactive_mp_bool_gates() { use rand::{thread_rng, RngCore}; use crate::{ aggregate_public_key_shares, aggregate_server_key_shares, backend::Modulus, bool::{ keys::{ tests::{ideal_sk_rlwe, measure_noise_lwe}, ServerKeyEvaluationDomain, }, print_noise::collect_server_key_stats, }, collective_pk_share, collective_server_key_share, gen_client_key, parameters::CiphertextModulus, random::DefaultSecureRng, set_common_reference_seed, set_parameter_set, utils::{tests::Stats, Global, WithLocal}, BoolEvaluator, BooleanGates, DefaultDecomposer, Encoder, Encryptor, ModInit, ModularOpsU64, MultiPartyDecryptor, NttBackendU64, ParameterSelector, RuntimeServerKey, }; set_parameter_set(ParameterSelector::InteractiveLTE8Party); let mut seed = [0u8; 32]; thread_rng().fill_bytes(&mut seed); set_common_reference_seed(seed); let no_of_parties = 8; let cks = (0..no_of_parties).map(|_| gen_client_key()).collect_vec(); // round 1 let pk_shares = cks.iter().map(|k| collective_pk_share(k)).collect_vec(); let pk = aggregate_public_key_shares(&pk_shares); // round 2 let server_key_shares = cks .iter() .enumerate() .map(|(user_id, k)| collective_server_key_share(k, user_id, no_of_parties, &pk)) .collect_vec(); let server_key = aggregate_server_key_shares(&server_key_shares); server_key.set_server_key(); let mut m0 = false; let mut m1 = true; let mut ct0 = pk.encrypt(&m0); let mut ct1 = pk.encrypt(&m1); let ideal_sk_rlwe = ideal_sk_rlwe(&cks); let parameters = BoolEvaluator::with_local(|e| e.parameters().clone()); let rlwe_modop = ModularOpsU64::new(*parameters.rlwe_q()); let mut stats = Stats::new(); for _ in 0..K { // let now = std::time::Instant::now(); let ct_out = BoolEvaluator::with_local_mut(|e| e.xor(&ct0, &ct1, RuntimeServerKey::global())); // println!("Time: {:?}", now.elapsed()); let m_expected = m0 ^ m1; let decryption_shares = cks .iter() .map(|k| k.gen_decryption_share(&ct_out)) .collect_vec(); let m_out = cks[0].aggregate_decryption_shares(&ct_out, &decryption_shares); assert!(m_out == m_expected, "Expected {m_expected}, got {m_out}"); { let noise = measure_noise_lwe( &ct_out, parameters.rlwe_q().encode(m_expected), &ideal_sk_rlwe, &rlwe_modop, ); stats.add_sample(parameters.rlwe_q().map_element_to_i64(&noise)); } m1 = m0; m0 = m_expected; ct1 = ct0; ct0 = ct_out; } let server_key_stats = collect_server_key_stats::< _, DefaultDecomposer, NttBackendU64, ModularOpsU64>, _, >( parameters, &cks, &ServerKeyEvaluationDomain::<_, _, DefaultSecureRng, NttBackendU64>::from(&server_key), ); println!("## Bootstrapping Statistics ##"); println!("Bootstrapped ciphertext noise std_dev: {}", stats.std_dev()); println!("## Key Statistics ##"); println!( "Rgsw nsm std_dev {}", server_key_stats.brk_rgsw_cts.0.std_dev() ); println!( "Rgsw m std_dev {}", server_key_stats.brk_rgsw_cts.1.std_dev() ); println!( "rlwe post 1 auto std_dev {}", server_key_stats.post_1_auto.std_dev() ); println!( "key switching noise rlwe secret s to lwe secret z std_dev {}", server_key_stats.post_lwe_key_switch.std_dev() ); println!(); } #[test] #[cfg(feature = "non_interactive_mp")] fn non_interactive_mp_bool_gates() { use rand::{thread_rng, RngCore}; use crate::{ aggregate_server_key_shares, backend::Modulus, bool::{ keys::{ tests::{ideal_sk_rlwe, measure_noise_lwe}, NonInteractiveServerKeyEvaluationDomain, }, print_noise::collect_server_key_stats, }, gen_client_key, gen_server_key_share, parameters::CiphertextModulus, random::DefaultSecureRng, set_common_reference_seed, set_parameter_set, utils::{tests::Stats, Global, WithLocal}, BoolEvaluator, BooleanGates, DefaultDecomposer, Encoder, Encryptor, KeySwitchWithId, ModInit, ModularOpsU64, MultiPartyDecryptor, NttBackendU64, ParameterSelector, RuntimeServerKey, SampleExtractor, }; set_parameter_set(ParameterSelector::NonInteractiveLTE8Party); let mut seed = [0u8; 32]; thread_rng().fill_bytes(&mut seed); set_common_reference_seed(seed); let parties = 8; let cks = (0..parties).map(|_| gen_client_key()).collect_vec(); let server_key_shares = cks .iter() .enumerate() .map(|(user_index, ck)| gen_server_key_share(user_index, parties, ck)) .collect_vec(); let seeded_server_key = aggregate_server_key_shares(&server_key_shares); seeded_server_key.set_server_key(); let parameters = BoolEvaluator::with_local(|e| e.parameters().clone()); let rlwe_modop = ModularOpsU64::new(*parameters.rlwe_q()); let ideal_sk_rlwe = ideal_sk_rlwe(&cks); let mut m0 = false; let mut m1 = true; let mut ct0 = { cks[0] .encrypt(vec![m0].as_slice()) .unseed::>>() .key_switch(0) .extract_at(0) .data }; let mut ct1 = { cks[1] .encrypt(vec![m1].as_slice()) .unseed::>>() .key_switch(1) .extract_at(0) .data }; let mut stats = Stats::new(); for _ in 0..K { // let now = std::time::Instant::now(); let ct_out = BoolEvaluator::with_local_mut(|e| e.xor(&ct0, &ct1, RuntimeServerKey::global())); // println!("Time: {:?}", now.elapsed()); let decryption_shares = cks .iter() .map(|k| k.gen_decryption_share(&ct_out)) .collect_vec(); let m_out = cks[0].aggregate_decryption_shares(&ct_out, &decryption_shares); let m_expected = m0 ^ m1; { let noise = measure_noise_lwe( &ct_out, parameters.rlwe_q().encode(m_expected), &ideal_sk_rlwe, &rlwe_modop, ); stats.add_sample(parameters.rlwe_q().map_element_to_i64(&noise)); } assert!(m_out == m_expected, "Expected {m_expected} but got {m_out}"); m1 = m0; m0 = m_out; ct1 = ct0; ct0 = ct_out; } // server key statistics let server_key_stats = collect_server_key_stats::< _, DefaultDecomposer, NttBackendU64, ModularOpsU64>, _, >( parameters, &cks, &NonInteractiveServerKeyEvaluationDomain::<_, _, DefaultSecureRng, NttBackendU64>::from( &seeded_server_key, ), ); println!("## Bootstrapping Statistics ##"); println!("Bootstrapped ciphertext noise std_dev: {}", stats.std_dev()); println!("## Key Statistics ##"); println!( "Rgsw nsm std_dev {}", server_key_stats.brk_rgsw_cts.0.std_dev() ); println!( "Rgsw m std_dev {}", server_key_stats.brk_rgsw_cts.1.std_dev() ); println!( "rlwe post 1 auto std_dev {}", server_key_stats.post_1_auto.std_dev() ); println!( "key switching noise rlwe secret s to lwe secret z std_dev {}", server_key_stats.post_lwe_key_switch.std_dev() ); println!(); } #[test] #[cfg(feature = "non_interactive_mp")] fn non_interactive_key_noise() { use crate::{ aggregate_server_key_shares, bool::{ evaluator::NonInteractiveMultiPartyCrs, keys::{key_size::KeySize, NonInteractiveServerKeyEvaluationDomain}, }, decomposer::DefaultDecomposer, gen_client_key, gen_server_key_share, parameters::CiphertextModulus, random::DefaultSecureRng, set_common_reference_seed, set_parameter_set, utils::WithLocal, BoolEvaluator, ModularOpsU64, NttBackendU64, }; use super::*; set_parameter_set(crate::ParameterSelector::NonInteractiveLTE8Party); set_common_reference_seed(NonInteractiveMultiPartyCrs::random().seed); let parties = 8; let mut server_key_stats = ServerKeyStats::default(); let mut server_key_share_size = 0; for i in 0..2 { let cks = (0..parties).map(|_| gen_client_key()).collect_vec(); let server_key_shares = cks .iter() .enumerate() .map(|(user_id, k)| gen_server_key_share(user_id, parties, k)) .collect_vec(); // Collect server key size in the 0th iteration if i == 0 { // Server key share size may differ for user with last id from // the share size of other users if the LWE dimension `n` is not // divisible by no. of parties. server_key_share_size = std::cmp::max( server_key_shares.first().unwrap().size(), server_key_shares.last().unwrap().size(), ); } let server_key = aggregate_server_key_shares(&server_key_shares); let server_key_eval = NonInteractiveServerKeyEvaluationDomain::< _, _, DefaultSecureRng, NttBackendU64, >::from(&server_key); let parameters = BoolEvaluator::with_local(|e| e.parameters().clone()); server_key_stats.merge_in(&collect_server_key_stats::< _, DefaultDecomposer, NttBackendU64, ModularOpsU64>, _, >(parameters, &cks, &server_key_eval)); } println!( "Common reference seeded server key share key size: {} Bits", server_key_share_size ); println!( "Rgsw nsm std log2 {}", server_key_stats.brk_rgsw_cts.0.std_dev().abs().log2() ); println!( "Rgsw m std log2 {}", server_key_stats.brk_rgsw_cts.1.std_dev().abs().log2() ); println!( "rlwe post 1 auto std log2 {}", server_key_stats.post_1_auto.std_dev().abs().log2() ); println!( "key switching noise rlwe secret s to lwe secret z std log2 {}", server_key_stats.post_lwe_key_switch.std_dev().abs().log2() ); } #[test] #[cfg(feature = "non_interactive_mp")] fn enc_under_sk_and_key_switch() { use rand::{thread_rng, Rng}; use crate::{ aggregate_server_key_shares, bool::keys::tests::ideal_sk_rlwe, gen_client_key, gen_server_key_share, rgsw::decrypt_rlwe, set_common_reference_seed, set_parameter_set, utils::{tests::Stats, TryConvertFrom1, WithLocal}, BoolEvaluator, Encoder, Encryptor, KeySwitchWithId, ModInit, ModularOpsU64, NttBackendU64, NttInit, ParameterSelector, VectorOps, }; set_parameter_set(ParameterSelector::NonInteractiveLTE2Party); set_common_reference_seed([2; 32]); let parties = 2; let cks = (0..parties).map(|_| gen_client_key()).collect_vec(); let key_shares = cks .iter() .enumerate() .map(|(user_index, ck)| gen_server_key_share(user_index, parties, ck)) .collect_vec(); let seeded_server_key = aggregate_server_key_shares(&key_shares); seeded_server_key.set_server_key(); let parameters = BoolEvaluator::with_local(|e| e.parameters().clone()); let nttop = NttBackendU64::new(parameters.rlwe_q(), parameters.rlwe_n().0); let rlwe_q_modop = ModularOpsU64::new(*parameters.rlwe_q()); let m = (0..parameters.rlwe_n().0) .map(|_| thread_rng().gen_bool(0.5)) .collect_vec(); let ct = cks[0] .encrypt(m.as_slice()) .unseed::>>() .key_switch(0); assert!(ct.data().len() == 1); let ideal_rlwe_sk = ideal_sk_rlwe(&cks); let message = m .iter() .map(|b| parameters.rlwe_q().encode(*b)) .collect_vec(); let mut m_out = vec![0u64; parameters.rlwe_n().0]; decrypt_rlwe( &ct.data()[0], &ideal_rlwe_sk, &mut m_out, &nttop, &rlwe_q_modop, ); let mut diff = m_out; rlwe_q_modop.elwise_sub_mut(diff.as_mut_slice(), message.as_ref()); let mut stats = Stats::new(); stats.add_many_samples(&Vec::::try_convert_from( diff.as_slice(), parameters.rlwe_q(), )); println!("Noise std log2: {}", stats.std_dev().abs().log2()); } #[test] fn mod_switch_noise() { // Experiment to check mod switch noise using different secret dist in // multi-party setting use itertools::izip; use num_traits::ToPrimitive; use crate::{ backend::{Modulus, ModulusPowerOf2}, parameters::SecretKeyDistribution, random::{DefaultSecureRng, RandomFillGaussian, RandomFillUniformInModulus}, utils::{fill_random_ternary_secret_with_hamming_weight, tests::Stats}, ArithmeticOps, ModInit, }; fn mod_switch(v: u64, q_from: u64, q_to: u64) -> f64 { (v as f64) * (q_to as f64) / q_from as f64 } fn mod_switch_round(v: u64, q_from: u64, q_to: u64) -> u64 { mod_switch(v, q_from, q_to).round().to_u64().unwrap() } fn mod_switch_odd(v: u64, q_from: u64, q_to: u64) -> u64 { let odd_v = mod_switch(v, q_from, q_to).floor().to_u64().unwrap(); odd_v + ((odd_v & 1) ^ 1) } fn sample_secret(n: usize, dist: &SecretKeyDistribution) -> Vec { let mut s = vec![0i32; n]; let mut rng = DefaultSecureRng::new(); match dist { SecretKeyDistribution::ErrorDistribution => { RandomFillGaussian::random_fill(&mut rng, s.as_mut_slice()); } SecretKeyDistribution::TernaryDistribution => { fill_random_ternary_secret_with_hamming_weight(&mut s, n >> 1, &mut rng); } } s } let parties = 2; let q_from = 1 << 40; let q_to = 1 << 20; let n = 480; let lweq_in_modop = ModulusPowerOf2::new(q_from); let lweq_out_modop = ModulusPowerOf2::new(q_to); let secret_dist = SecretKeyDistribution::ErrorDistribution; let mut stats_ms_noise = Stats::new(); let mut stats_ms_rounding_err = Stats::new(); for _ in 0..1000000 { let mut rng = DefaultSecureRng::new(); // sample secrets let s = { let mut s = vec![0i32; n]; for _ in 0..parties { let temp = sample_secret(n, &secret_dist); izip!(s.iter_mut(), temp.iter()).for_each(|(si, ti)| { *si = *si + *ti; }); } s }; let m = 10; // LWE encryption without noise let mut lwe_in = vec![0u64; n + 1]; { RandomFillUniformInModulus::random_fill(&mut rng, &q_from, &mut lwe_in[1..]); let mut b = m; izip!(lwe_in.iter().skip(1), s.iter()).for_each(|(ai, si)| { b = lweq_in_modop.add( &b, &lweq_in_modop.mul(ai, &q_from.map_element_from_i64(*si as i64)), ); }); lwe_in[0] = b; } // Mod switch let lwe_out = lwe_in .iter() .map(|v| { // mod_switch_round(*v, q_from, q_to) mod_switch_odd(*v, q_from, q_to) }) .collect_vec(); let rounding_errors = izip!(lwe_out.iter(), lwe_in.iter()) .map(|(v_out, v_in)| { let r_i = mod_switch(*v_in, q_from, q_to) - (*v_out as f64); r_i }) .collect_vec(); stats_ms_rounding_err.add_many_samples(&rounding_errors); // LWE decrypt and calculate ms noise let mut m_back = 0; izip!(lwe_out.iter().skip(1), s.iter()).for_each(|(ai, si)| { m_back = lweq_out_modop.add( &m_back, &lweq_out_modop.mul(ai, &q_from.map_element_from_i64(*si as i64)), ); }); m_back = lweq_out_modop.sub(&lwe_out[0], &m_back); let noise = lweq_out_modop.sub(&m_back, &m); stats_ms_noise.add_many_samples(&vec![q_to.map_element_to_i64(&noise)]); } println!("ms noise variance: {}", stats_ms_noise.variance()); println!("ms rounding errors mean: {}", stats_ms_rounding_err.mean()); println!( "ms rounding errors variance: {}", stats_ms_rounding_err.variance() ); } }