use std::{collections::HashMap, marker::PhantomData}; use crate::{ backend::{ModInit, VectorOps}, pbs::WithShoupRepr, random::{NewWithSeed, RandomFillUniformInModulus}, utils::ToShoup, Matrix, MatrixEntity, MatrixMut, RowEntity, RowMut, }; use super::parameters::{BoolParameters, CiphertextModulus}; pub(crate) trait SinglePartyClientKey { type Element; fn sk_rlwe(&self) -> Vec; fn sk_lwe(&self) -> Vec; } pub(crate) trait InteractiveMultiPartyClientKey { type Element; fn sk_rlwe(&self) -> Vec; fn sk_lwe(&self) -> Vec; } pub(crate) trait NonInteractiveMultiPartyClientKey { type Element; fn sk_rlwe(&self) -> Vec; fn sk_u_rlwe(&self) -> Vec; fn sk_lwe(&self) -> Vec; } /// Client key /// /// Key is used for all parameter varians - Single party, interactive /// multi-party, and non-interactive multi-party. The only stored the main seed /// and seeds of the Rlwe/Lwe secrets are derived at puncturing the seed desired /// number of times. /// /// ### Punctures required: /// /// Puncture 1 -> Seed of RLWE secret used as main RLWE secret for /// single-party, interactive/non-interactive multi-party /// /// Puncture 2 -> Seed of LWE secret used main LWE secret for single-party, /// interactive/non-interactive multi-party /// /// Puncture 3 -> Seed of RLWE secret used as `u` in /// non-interactive multi-party. #[derive(Clone)] pub struct ClientKey { seed: S, parameters: BoolParameters, } mod impl_ck { use crate::{ random::DefaultSecureRng, utils::{fill_random_ternary_secret_with_hamming_weight, puncture_p_rng}, }; use super::*; impl ClientKey<[u8; 32], E> { pub(in super::super) fn new(parameters: BoolParameters) -> ClientKey<[u8; 32], E> { let mut rng = DefaultSecureRng::new(); let mut seed = [0u8; 32]; rng.fill_bytes(&mut seed); Self { seed, parameters } } } impl SinglePartyClientKey for ClientKey<[u8; 32], E> { type Element = i32; fn sk_lwe(&self) -> Vec { let mut p_rng = DefaultSecureRng::new_seeded(self.seed); let lwe_seed = puncture_p_rng::<[u8; 32], DefaultSecureRng>(&mut p_rng, 2); let mut lwe_prng = DefaultSecureRng::new_seeded(lwe_seed); let mut out = vec![0i32; self.parameters.lwe_n().0]; fill_random_ternary_secret_with_hamming_weight( &mut out, self.parameters.lwe_n().0 >> 1, &mut lwe_prng, ); out } fn sk_rlwe(&self) -> Vec { let mut p_rng = DefaultSecureRng::new_seeded(self.seed); let rlwe_seed = puncture_p_rng::<[u8; 32], DefaultSecureRng>(&mut p_rng, 1); let mut rlwe_prng = DefaultSecureRng::new_seeded(rlwe_seed); let mut out = vec![0i32; self.parameters.rlwe_n().0]; fill_random_ternary_secret_with_hamming_weight( &mut out, self.parameters.rlwe_n().0 >> 1, &mut rlwe_prng, ); out } } impl InteractiveMultiPartyClientKey for ClientKey<[u8; 32], E> { type Element = i32; fn sk_lwe(&self) -> Vec { ::sk_lwe(&self) } fn sk_rlwe(&self) -> Vec { ::sk_rlwe(&self) } } impl NonInteractiveMultiPartyClientKey for ClientKey<[u8; 32], E> { type Element = i32; fn sk_lwe(&self) -> Vec { ::sk_lwe(&self) } fn sk_rlwe(&self) -> Vec { ::sk_rlwe(&self) } fn sk_u_rlwe(&self) -> Vec { let mut p_rng = DefaultSecureRng::new_seeded(self.seed); let rlwe_seed = puncture_p_rng::<[u8; 32], DefaultSecureRng>(&mut p_rng, 3); let mut rlwe_prng = DefaultSecureRng::new_seeded(rlwe_seed); let mut out = vec![0i32; self.parameters.rlwe_n().0]; fill_random_ternary_secret_with_hamming_weight( &mut out, self.parameters.rlwe_n().0 >> 1, &mut rlwe_prng, ); out } } } /// Public key pub struct PublicKey { key: M, _phantom: PhantomData<(Rng, ModOp)>, } pub(super) mod impl_pk { use super::*; impl PublicKey { pub(in super::super) fn key(&self) -> &M { &self.key } } impl< M: MatrixMut + MatrixEntity, Rng: NewWithSeed + RandomFillUniformInModulus<[M::MatElement], CiphertextModulus>, ModOp, > From, ModOp>> for PublicKey where ::R: RowMut, M::MatElement: Copy, { fn from( value: SeededPublicKey, ModOp>, ) -> Self { let mut prng = Rng::new_with_seed(value.seed); let mut key = M::zeros(2, value.parameters.rlwe_n().0); // sample A RandomFillUniformInModulus::random_fill( &mut prng, value.parameters.rlwe_q(), key.get_row_mut(0), ); // Copy over B key.get_row_mut(1).copy_from_slice(value.part_b.as_ref()); PublicKey { key, _phantom: PhantomData, } } } impl< M: MatrixMut + MatrixEntity, Rng: NewWithSeed + RandomFillUniformInModulus<[M::MatElement], CiphertextModulus>, ModOp: VectorOps + ModInit>, > From< &[CommonReferenceSeededCollectivePublicKeyShare< M::R, Rng::Seed, BoolParameters, >], > for PublicKey where ::R: RowMut, Rng::Seed: Copy + PartialEq, M::MatElement: PartialEq + Copy, { fn from( value: &[CommonReferenceSeededCollectivePublicKeyShare< M::R, Rng::Seed, BoolParameters, >], ) -> Self { assert!(value.len() > 0); let parameters = &value[0].parameters; let mut key = M::zeros(2, parameters.rlwe_n().0); // sample A let seed = value[0].cr_seed; let mut main_rng = Rng::new_with_seed(seed); RandomFillUniformInModulus::random_fill( &mut main_rng, parameters.rlwe_q(), key.get_row_mut(0), ); // Sum all Bs let rlweq_modop = ModOp::new(parameters.rlwe_q().clone()); value.iter().for_each(|share_i| { assert!(share_i.cr_seed == seed); assert!(&share_i.parameters == parameters); rlweq_modop.elwise_add_mut(key.get_row_mut(1), share_i.share.as_ref()); }); PublicKey { key, _phantom: PhantomData, } } } } /// Seeded public key struct SeededPublicKey { part_b: Ro, seed: S, parameters: P, _phantom: PhantomData, } mod impl_seeded_pk { use super::*; impl From<&[CommonReferenceSeededCollectivePublicKeyShare>]> for SeededPublicKey, ModOp> where ModOp: VectorOps + ModInit>, S: PartialEq + Clone, R: RowMut + RowEntity + Clone, R::Element: Clone + PartialEq, { fn from( value: &[CommonReferenceSeededCollectivePublicKeyShare< R, S, BoolParameters, >], ) -> Self { assert!(value.len() > 0); let parameters = &value[0].parameters; let cr_seed = value[0].cr_seed.clone(); // Sum all Bs let rlweq_modop = ModOp::new(parameters.rlwe_q().clone()); let mut part_b = value[0].share.clone(); value.iter().skip(1).for_each(|share_i| { assert!(&share_i.cr_seed == &cr_seed); assert!(&share_i.parameters == parameters); rlweq_modop.elwise_add_mut(part_b.as_mut(), share_i.share.as_ref()); }); Self { part_b, seed: cr_seed, parameters: parameters.clone(), _phantom: PhantomData, } } } } /// CRS seeded collective public key share pub struct CommonReferenceSeededCollectivePublicKeyShare { /// Public key share polynomial share: Ro, /// Common reference seed cr_seed: S, /// Parameters parameters: P, } impl CommonReferenceSeededCollectivePublicKeyShare { pub(super) fn new(share: Ro, cr_seed: S, parameters: P) -> Self { CommonReferenceSeededCollectivePublicKeyShare { share, cr_seed, parameters, } } } /// Common reference seed seeded interactive multi-party server key share pub struct CommonReferenceSeededInteractiveMultiPartyServerKeyShare { /// Public key encrypted RGSW(m = X^{s[i]}) ciphertexts for LWE secret /// indices for which `Self` is the leader. Note that when `Self` is /// leader RGSW ciphertext is encrypted using RLWE x RGSW decomposer self_leader_rgsws: Vec, /// Public key encrypted RGSW(m = X^{s[i]}) ciphertext for LWE secret /// indices for which `Self` is `not` the leader. Note that when `Self` /// is not the leader RGSW ciphertext is encrypted using RGSW1 /// decomposer for RGSW0 x RGSW1 not_self_leader_rgsws: Vec, /// Auto key shares for auto elements [-g, g, g^2, .., g^{w}] where `w` /// is the window size parameter. Share corresponding to auto element -g /// is stored at key `0` and share corresponding to auto element g^{k} is /// stored at key `k`. auto_keys: HashMap, /// LWE key switching key share to key switching ciphertext LWE_{q, s}(m) to /// LWE_{q, z}(m) where q is LWE ciphertext modulus, `s` is the ideal RLWE /// secret with dimension N, and `z` is the ideal LWE secret of dimension n. lwe_ksk: M::R, /// Common reference seed cr_seed: S, parameters: P, /// User id assigned by the server. /// /// User id must be unique and a number in range [0, total_users) user_id: usize, } impl CommonReferenceSeededInteractiveMultiPartyServerKeyShare { pub(super) fn new( self_leader_rgsws: Vec, not_self_leader_rgsws: Vec, auto_keys: HashMap, lwe_ksk: M::R, cr_seed: S, parameters: P, user_id: usize, ) -> Self { CommonReferenceSeededInteractiveMultiPartyServerKeyShare { self_leader_rgsws, not_self_leader_rgsws, auto_keys, lwe_ksk, cr_seed, parameters, user_id, } } pub(super) fn cr_seed(&self) -> &S { &self.cr_seed } pub(super) fn parameters(&self) -> &P { &self.parameters } pub(super) fn auto_keys(&self) -> &HashMap { &self.auto_keys } pub(crate) fn self_leader_rgsws(&self) -> &[M] { &self.self_leader_rgsws } pub(super) fn not_self_leader_rgsws(&self) -> &[M] { &self.not_self_leader_rgsws } pub(super) fn lwe_ksk(&self) -> &M::R { &self.lwe_ksk } pub(super) fn user_id(&self) -> usize { self.user_id } } /// Common reference seeded interactive multi-party server key pub struct SeededInteractiveMultiPartyServerKey { /// RGSW ciphertexts RGSW(X^{s[i]}) encrypted under ideal RLWE secret key /// where `s` is ideal LWE secret key for each LWE secret dimension. rgsw_cts: Vec, /// Seeded auto keys under ideal RLWE secret for RLWE automorphisms with /// auto elements [-g, g, g^2,..., g^{w}]. Auto key corresponidng to /// auto element -g is stored at key `0` and key corresponding to auto /// element g^{k} is stored at key `k` auto_keys: HashMap, /// Seeded LWE key switching key under ideal LWE secret to switch LWE_{q, /// s}(m) to LWE_{q, z}(m) where s is ideal RLWE secret and z is ideal LWE /// secret. lwe_ksk: M::R, /// Common reference seed cr_seed: S, parameters: P, } impl SeededInteractiveMultiPartyServerKey { pub(super) fn new( rgsw_cts: Vec, auto_keys: HashMap, lwe_ksk: M::R, cr_seed: S, parameters: P, ) -> Self { SeededInteractiveMultiPartyServerKey { rgsw_cts, auto_keys, lwe_ksk, cr_seed, parameters, } } pub(super) fn rgsw_cts(&self) -> &[M] { &self.rgsw_cts } } /// Seeded single party server key pub struct SeededSinglePartyServerKey { /// Rgsw cts of LWE secret elements pub(crate) rgsw_cts: Vec, /// Auto keys. Key corresponding to g^{k} is at index `k`. Key corresponding /// to -g is at 0 pub(crate) auto_keys: HashMap, /// LWE ksk to key switching LWE ciphertext from RLWE secret to LWE secret pub(crate) lwe_ksk: M::R, /// Parameters pub(crate) parameters: P, /// Main seed pub(crate) seed: S, } impl SeededSinglePartyServerKey, S> { pub(super) fn from_raw( auto_keys: HashMap, rgsw_cts: Vec, lwe_ksk: M::R, parameters: BoolParameters, seed: S, ) -> Self { // sanity checks auto_keys.iter().for_each(|v| { assert!( v.1.dimension() == ( parameters.auto_decomposition_count().0, parameters.rlwe_n().0 ) ) }); let (part_a_d, part_b_d) = parameters.rlwe_rgsw_decomposition_count(); rgsw_cts.iter().for_each(|v| { assert!(v.dimension() == (part_a_d.0 * 2 + part_b_d.0, parameters.rlwe_n().0)) }); assert!( lwe_ksk.as_ref().len() == (parameters.lwe_decomposition_count().0 * parameters.rlwe_n().0) ); SeededSinglePartyServerKey { rgsw_cts, auto_keys, lwe_ksk, parameters, seed, } } } /// Server key in evaluation domain pub(crate) struct ServerKeyEvaluationDomain { /// RGSW ciphertext RGSW(X^{s[i]}) for each LWE index in evaluation domain rgsw_cts: Vec, /// Auto keys for all auto elements [-g, g, g^2,..., g^w] in evaluation /// domain galois_keys: HashMap, /// LWE key switching key to key switch LWE_{q, s}(m) to LWE_{q, z}(m) lwe_ksk: M, parameters: P, _phanton: PhantomData<(R, N)>, } pub(super) mod impl_server_key_eval_domain { use itertools::{izip, Itertools}; use crate::{ evaluator::MultiPartyCrs, ntt::{Ntt, NttInit}, pbs::PbsKey, random::RandomFill, }; use super::*; impl ServerKeyEvaluationDomain { pub(in super::super) fn rgsw_cts(&self) -> &[M] { &self.rgsw_cts } } impl< M: MatrixMut + MatrixEntity, R: RandomFillUniformInModulus<[M::MatElement], CiphertextModulus> + NewWithSeed, N: NttInit> + Ntt, > From<&SeededSinglePartyServerKey, R::Seed>> for ServerKeyEvaluationDomain, R, N> where ::R: RowMut, M::MatElement: Copy, R::Seed: Clone, { fn from( value: &SeededSinglePartyServerKey, R::Seed>, ) -> Self { let mut main_prng = R::new_with_seed(value.seed.clone()); let parameters = &value.parameters; let g = parameters.g() as isize; let ring_size = value.parameters.rlwe_n().0; let lwe_n = value.parameters.lwe_n().0; let rlwe_q = value.parameters.rlwe_q(); let lwq_q = value.parameters.lwe_q(); let nttop = N::new(rlwe_q, ring_size); // galois keys let mut auto_keys = HashMap::new(); let auto_decomp_count = parameters.auto_decomposition_count().0; let auto_element_dlogs = parameters.auto_element_dlogs(); for i in auto_element_dlogs.into_iter() { let seeded_auto_key = value.auto_keys.get(&i).unwrap(); assert!(seeded_auto_key.dimension() == (auto_decomp_count, ring_size)); let mut data = M::zeros(auto_decomp_count * 2, ring_size); // sample RLWE'_A(-s(X^k)) data.iter_rows_mut().take(auto_decomp_count).for_each(|ri| { RandomFillUniformInModulus::random_fill(&mut main_prng, &rlwe_q, ri.as_mut()) }); // copy over RLWE'B_(-s(X^k)) izip!( data.iter_rows_mut().skip(auto_decomp_count), seeded_auto_key.iter_rows() ) .for_each(|(to_ri, from_ri)| to_ri.as_mut().copy_from_slice(from_ri.as_ref())); // Send to Evaluation domain data.iter_rows_mut() .for_each(|ri| nttop.forward(ri.as_mut())); auto_keys.insert(i, data); } // RGSW ciphertexts let (rlrg_a_decomp, rlrg_b_decomp) = parameters.rlwe_rgsw_decomposition_count(); let rgsw_cts = value .rgsw_cts .iter() .map(|seeded_rgsw_si| { assert!( seeded_rgsw_si.dimension() == (rlrg_a_decomp.0 * 2 + rlrg_b_decomp.0, ring_size) ); let mut data = M::zeros(rlrg_a_decomp.0 * 2 + rlrg_b_decomp.0 * 2, ring_size); // copy over RLWE'(-sm) izip!( data.iter_rows_mut().take(rlrg_a_decomp.0 * 2), seeded_rgsw_si.iter_rows().take(rlrg_a_decomp.0 * 2) ) .for_each(|(to_ri, from_ri)| to_ri.as_mut().copy_from_slice(from_ri.as_ref())); // sample RLWE'_A(m) data.iter_rows_mut() .skip(rlrg_a_decomp.0 * 2) .take(rlrg_b_decomp.0) .for_each(|ri| { RandomFillUniformInModulus::random_fill( &mut main_prng, &rlwe_q, ri.as_mut(), ) }); // copy over RLWE'_B(m) izip!( data.iter_rows_mut() .skip(rlrg_a_decomp.0 * 2 + rlrg_b_decomp.0), seeded_rgsw_si.iter_rows().skip(rlrg_a_decomp.0 * 2) ) .for_each(|(to_ri, from_ri)| to_ri.as_mut().copy_from_slice(from_ri.as_ref())); // send polynomials to evaluation domain data.iter_rows_mut() .for_each(|ri| nttop.forward(ri.as_mut())); data }) .collect_vec(); // LWE ksk let lwe_ksk = { let d = parameters.lwe_decomposition_count().0; assert!(value.lwe_ksk.as_ref().len() == d * ring_size); let mut data = M::zeros(d * ring_size, lwe_n + 1); izip!(data.iter_rows_mut(), value.lwe_ksk.as_ref().iter()).for_each( |(lwe_i, bi)| { RandomFillUniformInModulus::random_fill( &mut main_prng, &lwq_q, &mut lwe_i.as_mut()[1..], ); lwe_i.as_mut()[0] = *bi; }, ); data }; ServerKeyEvaluationDomain { rgsw_cts, galois_keys: auto_keys, lwe_ksk, parameters: parameters.clone(), _phanton: PhantomData, } } } impl< M: MatrixMut + MatrixEntity, Rng: NewWithSeed, N: NttInit> + Ntt, > From< &SeededInteractiveMultiPartyServerKey< M, MultiPartyCrs, BoolParameters, >, > for ServerKeyEvaluationDomain, Rng, N> where ::R: RowMut, Rng::Seed: Copy + Default, Rng: RandomFillUniformInModulus<[M::MatElement], CiphertextModulus> + RandomFill, M::MatElement: Copy, { fn from( value: &SeededInteractiveMultiPartyServerKey< M, MultiPartyCrs, BoolParameters, >, ) -> Self { let g = value.parameters.g() as isize; let rlwe_n = value.parameters.rlwe_n().0; let lwe_n = value.parameters.lwe_n().0; let rlwe_q = value.parameters.rlwe_q(); let lwe_q = value.parameters.lwe_q(); let rlwe_nttop = N::new(rlwe_q, rlwe_n); // auto keys let mut auto_keys = HashMap::new(); { let mut auto_prng = Rng::new_with_seed(value.cr_seed.auto_keys_cts_seed::()); let auto_d_count = value.parameters.auto_decomposition_count().0; let auto_element_dlogs = value.parameters.auto_element_dlogs(); for i in auto_element_dlogs.into_iter() { let mut key = M::zeros(auto_d_count * 2, rlwe_n); // sample a key.iter_rows_mut().take(auto_d_count).for_each(|ri| { RandomFillUniformInModulus::random_fill( &mut auto_prng, &rlwe_q, ri.as_mut(), ) }); let key_part_b = value.auto_keys.get(&i).unwrap(); assert!(key_part_b.dimension() == (auto_d_count, rlwe_n)); izip!( key.iter_rows_mut().skip(auto_d_count), key_part_b.iter_rows() ) .for_each(|(to_ri, from_ri)| { to_ri.as_mut().copy_from_slice(from_ri.as_ref()); }); // send to evaluation domain key.iter_rows_mut() .for_each(|ri| rlwe_nttop.forward(ri.as_mut())); auto_keys.insert(i, key); } } // rgsw cts let (rlrg_d_a, rlrg_d_b) = value.parameters.rlwe_rgsw_decomposition_count(); let rgsw_ct_out = rlrg_d_a.0 * 2 + rlrg_d_b.0 * 2; let rgsw_cts = value .rgsw_cts .iter() .map(|ct_i_in| { assert!(ct_i_in.dimension() == (rgsw_ct_out, rlwe_n)); let mut eval_ct_i_out = M::zeros(rgsw_ct_out, rlwe_n); izip!(eval_ct_i_out.iter_rows_mut(), ct_i_in.iter_rows()).for_each( |(to_ri, from_ri)| { to_ri.as_mut().copy_from_slice(from_ri.as_ref()); rlwe_nttop.forward(to_ri.as_mut()); }, ); eval_ct_i_out }) .collect_vec(); // lwe ksk let mut lwe_ksk_prng = Rng::new_with_seed(value.cr_seed.lwe_ksk_cts_seed_seed::()); let d_lwe = value.parameters.lwe_decomposition_count().0; let mut lwe_ksk = M::zeros(rlwe_n * d_lwe, lwe_n + 1); izip!(lwe_ksk.iter_rows_mut(), value.lwe_ksk.as_ref().iter()).for_each( |(lwe_i, bi)| { RandomFillUniformInModulus::random_fill( &mut lwe_ksk_prng, &lwe_q, &mut lwe_i.as_mut()[1..], ); lwe_i.as_mut()[0] = *bi; }, ); ServerKeyEvaluationDomain { rgsw_cts, galois_keys: auto_keys, lwe_ksk, parameters: value.parameters.clone(), _phanton: PhantomData, } } } impl PbsKey for ServerKeyEvaluationDomain { type AutoKey = M; type LweKskKey = M; type RgswCt = M; fn galois_key_for_auto(&self, k: usize) -> &Self::AutoKey { self.galois_keys.get(&k).unwrap() } fn rgsw_ct_lwe_si(&self, si: usize) -> &Self::RgswCt { &self.rgsw_cts[si] } fn lwe_ksk(&self) -> &Self::LweKskKey { &self.lwe_ksk } } #[cfg(test)] impl super::super::print_noise::CollectRuntimeServerKeyStats for ServerKeyEvaluationDomain { type M = M; fn galois_key_for_auto(&self, k: usize) -> &Self::M { self.galois_keys.get(&k).unwrap() } fn lwe_ksk(&self) -> &Self::M { &self.lwe_ksk } fn rgsw_cts_lwe_si(&self, s_index: usize) -> &Self::M { &self.rgsw_cts[s_index] } } } /// Non-interactive multi-party server key in evaluation domain. /// /// The key is derived from Seeded non-interactive mmulti-party server key /// `SeededNonInteractiveMultiPartyServerKey`. pub(crate) struct NonInteractiveServerKeyEvaluationDomain { /// RGSW ciphertexts RGSW(X^{s[i]}) under ideal RLWE secret key in /// evaluation domain rgsw_cts: Vec, /// Auto keys for all auto elements [-g, g, g^2, g^w] in evaluation /// domain auto_keys: HashMap, /// LWE key switching key to key switch LWE_{q, s}(m) to LWE_{q, z}(m) lwe_ksk: M, /// Key switching key from user j's secret u_j to ideal RLWE secret key `s` /// in evaluation domain. User j's key switching key is at j'th index. ui_to_s_ksks: Vec, parameters: P, _phanton: PhantomData<(R, N)>, } pub(super) mod impl_non_interactive_server_key_eval_domain { use itertools::{izip, Itertools}; use crate::{bool::evaluator::NonInteractiveMultiPartyCrs, random::RandomFill, Ntt, NttInit}; use super::*; impl NonInteractiveServerKeyEvaluationDomain { pub(in super::super) fn rgsw_cts(&self) -> &[M] { &self.rgsw_cts } } impl From< &SeededNonInteractiveMultiPartyServerKey< M, NonInteractiveMultiPartyCrs, BoolParameters, >, > for NonInteractiveServerKeyEvaluationDomain, Rng, N> where M: MatrixMut + MatrixEntity + Clone, Rng: NewWithSeed + RandomFillUniformInModulus<[M::MatElement], CiphertextModulus> + RandomFill<::Seed>, N: Ntt + NttInit>, M::R: RowMut, M::MatElement: Copy, Rng::Seed: Clone + Copy + Default, { fn from( value: &SeededNonInteractiveMultiPartyServerKey< M, NonInteractiveMultiPartyCrs, BoolParameters, >, ) -> Self { let rlwe_nttop = N::new(value.parameters.rlwe_q(), value.parameters.rlwe_n().0); let ring_size = value.parameters.rlwe_n().0; // RGSW cts // copy over rgsw cts and send to evaluation domain let mut rgsw_cts = value.rgsw_cts.clone(); rgsw_cts.iter_mut().for_each(|c| { c.iter_rows_mut() .for_each(|ri| rlwe_nttop.forward(ri.as_mut())) }); // Auto keys // populate pseudo random part of auto keys. Then send auto keys to // evaluation domain let mut auto_keys = HashMap::new(); let auto_seed = value.cr_seed.auto_keys_cts_seed::(); let mut auto_prng = Rng::new_with_seed(auto_seed); let auto_element_dlogs = value.parameters.auto_element_dlogs(); let d_auto = value.parameters.auto_decomposition_count().0; auto_element_dlogs.iter().for_each(|el| { let auto_part_b = value .auto_keys .get(el) .expect(&format!("Auto key for element g^{el} not found")); assert!(auto_part_b.dimension() == (d_auto, ring_size)); let mut auto_ct = M::zeros(d_auto * 2, ring_size); // sample part A auto_ct.iter_rows_mut().take(d_auto).for_each(|ri| { RandomFillUniformInModulus::random_fill( &mut auto_prng, value.parameters.rlwe_q(), ri.as_mut(), ) }); // Copy over part B izip!( auto_ct.iter_rows_mut().skip(d_auto), auto_part_b.iter_rows() ) .for_each(|(to_ri, from_ri)| to_ri.as_mut().copy_from_slice(from_ri.as_ref())); // send to evaluation domain auto_ct .iter_rows_mut() .for_each(|r| rlwe_nttop.forward(r.as_mut())); auto_keys.insert(*el, auto_ct); }); // LWE ksk // populate pseudo random part of lwe ciphertexts in ksk and copy over part b // elements let lwe_ksk_seed = value.cr_seed.lwe_ksk_cts_seed::(); let mut lwe_ksk_prng = Rng::new_with_seed(lwe_ksk_seed); let mut lwe_ksk = M::zeros( value.parameters.lwe_decomposition_count().0 * ring_size, value.parameters.lwe_n().0 + 1, ); lwe_ksk.iter_rows_mut().for_each(|ri| { // first element is resereved for part b. Only sample a_is in the rest RandomFillUniformInModulus::random_fill( &mut lwe_ksk_prng, value.parameters.lwe_q(), &mut ri.as_mut()[1..], ) }); // copy over part bs assert!( value.lwe_ksk.as_ref().len() == value.parameters.lwe_decomposition_count().0 * ring_size ); izip!(value.lwe_ksk.as_ref().iter(), lwe_ksk.iter_rows_mut()).for_each( |(b_el, lwe_ct)| { lwe_ct.as_mut()[0] = *b_el; }, ); // u_i to s ksk let d_uitos = value .parameters .non_interactive_ui_to_s_key_switch_decomposition_count() .0; let ui_to_s_ksks = value .ui_to_s_ksks .iter() .enumerate() .map(|(user_id, incoming_ksk_partb)| { let user_i_seed = value.cr_seed.ui_to_s_ks_seed_for_user_i::(user_id); let mut prng = Rng::new_with_seed(user_i_seed); let mut ksk_ct = M::zeros(d_uitos * 2, ring_size); ksk_ct.iter_rows_mut().take(d_uitos).for_each(|r| { RandomFillUniformInModulus::random_fill( &mut prng, value.parameters.rlwe_q(), r.as_mut(), ); }); assert!(incoming_ksk_partb.dimension() == (d_uitos, ring_size)); izip!( ksk_ct.iter_rows_mut().skip(d_uitos), incoming_ksk_partb.iter_rows() ) .for_each(|(to_ri, from_ri)| { to_ri.as_mut().copy_from_slice(from_ri.as_ref()); }); ksk_ct .iter_rows_mut() .for_each(|r| rlwe_nttop.forward(r.as_mut())); ksk_ct }) .collect_vec(); NonInteractiveServerKeyEvaluationDomain { rgsw_cts, auto_keys, lwe_ksk, ui_to_s_ksks, parameters: value.parameters.clone(), _phanton: PhantomData, } } } #[cfg(test)] impl super::super::print_noise::CollectRuntimeServerKeyStats for NonInteractiveServerKeyEvaluationDomain { type M = M; fn galois_key_for_auto(&self, k: usize) -> &Self::M { self.auto_keys.get(&k).unwrap() } fn lwe_ksk(&self) -> &Self::M { &self.lwe_ksk } fn rgsw_cts_lwe_si(&self, s_index: usize) -> &Self::M { &self.rgsw_cts[s_index] } } } /// Seeded non-interactive multi-party server key. /// /// Given common reference seeded non-interactive multi-party key shares of each /// users with unique user-ids, seeded non-interactive can be generated using /// `BoolEvaluator::aggregate_non_interactive_multi_party_key_share` pub struct SeededNonInteractiveMultiPartyServerKey { /// Key switching key from user j's secret u_j to ideal RLWE secret key `s`. /// User j's key switching key is at j'th index. ui_to_s_ksks: Vec, /// RGSW ciphertexts RGSW(X^{s[i]}) under ideal RLWE secret key rgsw_cts: Vec, /// Auto keys for all auto elements [-g, g, g^2, g^w] auto_keys: HashMap, /// LWE key switching key to key switch LWE_{q, s}(m) to LWE_{q, z}(m) lwe_ksk: M::R, /// Common reference seed cr_seed: S, parameters: P, } impl SeededNonInteractiveMultiPartyServerKey { pub(super) fn new( ui_to_s_ksks: Vec, rgsw_cts: Vec, auto_keys: HashMap, lwe_ksk: M::R, cr_seed: S, parameters: P, ) -> Self { Self { ui_to_s_ksks, rgsw_cts, auto_keys, lwe_ksk, cr_seed, parameters, } } } /// This key is equivalent to NonInteractiveServerKeyEvaluationDomain with the /// addition that each polynomial in evaluation domain has a corresponding shoup /// representation suitable for shoup multiplication. pub(crate) struct ShoupNonInteractiveServerKeyEvaluationDomain { rgsw_cts: Vec>, auto_keys: HashMap>, lwe_ksk: M, ui_to_s_ksks: Vec>, } mod impl_shoup_non_interactive_server_key_eval_domain { use itertools::Itertools; use num_traits::{FromPrimitive, PrimInt, ToPrimitive}; use super::*; use crate::{backend::Modulus, decomposer::NumInfo, pbs::PbsKey}; impl ShoupNonInteractiveServerKeyEvaluationDomain { pub(in super::super) fn ui_to_s_ksk(&self, user_id: usize) -> &NormalAndShoup { &self.ui_to_s_ksks[user_id] } } impl, R, N> From, R, N>> for ShoupNonInteractiveServerKeyEvaluationDomain where M::MatElement: FromPrimitive + ToPrimitive + PrimInt + NumInfo, { fn from( value: NonInteractiveServerKeyEvaluationDomain, R, N>, ) -> Self { let rlwe_q = value.parameters.rlwe_q().q().unwrap(); let rgsw_dim = ( value.parameters.rlwe_rgsw_decomposition_count().0 .0 * 2 + value.parameters.rlwe_rgsw_decomposition_count().1 .0 * 2, value.parameters.rlwe_n().0, ); let rgsw_cts = value .rgsw_cts .into_iter() .map(|m| { assert!(m.dimension() == rgsw_dim); NormalAndShoup::new_with_modulus(m, rlwe_q) }) .collect_vec(); let auto_dim = ( value.parameters.auto_decomposition_count().0 * 2, value.parameters.rlwe_n().0, ); let mut auto_keys = HashMap::new(); value.auto_keys.into_iter().for_each(|(k, v)| { assert!(v.dimension() == auto_dim); auto_keys.insert(k, NormalAndShoup::new_with_modulus(v, rlwe_q)); }); let ui_ks_dim = ( value .parameters .non_interactive_ui_to_s_key_switch_decomposition_count() .0 * 2, value.parameters.rlwe_n().0, ); let ui_to_s_ksks = value .ui_to_s_ksks .into_iter() .map(|m| { assert!(m.dimension() == ui_ks_dim); NormalAndShoup::new_with_modulus(m, rlwe_q) }) .collect_vec(); assert!( value.lwe_ksk.dimension() == ( value.parameters.rlwe_n().0 * value.parameters.lwe_decomposition_count().0, value.parameters.lwe_n().0 + 1 ) ); Self { rgsw_cts, auto_keys, lwe_ksk: value.lwe_ksk, ui_to_s_ksks, } } } impl PbsKey for ShoupNonInteractiveServerKeyEvaluationDomain { type AutoKey = NormalAndShoup; type LweKskKey = M; type RgswCt = NormalAndShoup; fn galois_key_for_auto(&self, k: usize) -> &Self::AutoKey { let d = self.auto_keys.get(&k).unwrap(); d } fn rgsw_ct_lwe_si(&self, si: usize) -> &Self::RgswCt { &self.rgsw_cts[si] } fn lwe_ksk(&self) -> &Self::LweKskKey { &self.lwe_ksk } } } /// This is equivalent to ServerKeyEvaluationDomain with the addition that each /// polynomial in evaluation domain has corresponding shoup representation /// suitable for shoup multiplication. pub(crate) struct ShoupServerKeyEvaluationDomain { rgsw_cts: Vec>, galois_keys: HashMap>, lwe_ksk: M, } mod shoup_server_key_eval_domain { use itertools::{izip, Itertools}; use num_traits::{FromPrimitive, PrimInt}; use crate::{backend::Modulus, decomposer::NumInfo, pbs::PbsKey}; use super::*; impl, R, N> From, R, N>> for ShoupServerKeyEvaluationDomain where ::R: RowMut, M::MatElement: PrimInt + FromPrimitive + NumInfo, { fn from(value: ServerKeyEvaluationDomain, R, N>) -> Self { let q = value.parameters.rlwe_q().q().unwrap(); // Rgsw ciphertexts let rgsw_cts = value .rgsw_cts .into_iter() .map(|ct| NormalAndShoup::new_with_modulus(ct, q)) .collect_vec(); let mut auto_keys = HashMap::new(); value.galois_keys.into_iter().for_each(|(index, key)| { auto_keys.insert(index, NormalAndShoup::new_with_modulus(key, q)); }); Self { rgsw_cts, galois_keys: auto_keys, lwe_ksk: value.lwe_ksk, } } } impl PbsKey for ShoupServerKeyEvaluationDomain { type AutoKey = NormalAndShoup; type LweKskKey = M; type RgswCt = NormalAndShoup; fn galois_key_for_auto(&self, k: usize) -> &Self::AutoKey { self.galois_keys.get(&k).unwrap() } fn rgsw_ct_lwe_si(&self, si: usize) -> &Self::RgswCt { &self.rgsw_cts[si] } fn lwe_ksk(&self) -> &Self::LweKskKey { &self.lwe_ksk } } } pub struct CommonReferenceSeededNonInteractiveMultiPartyServerKeyShare { /// Non-interactive RGSW ciphertexts for LWE secret indices for which user /// is the leader self_leader_ni_rgsw_cts: Vec, /// Non-interactive RGSW ciphertexts for LWE secret indices for which user /// is not the leader not_self_leader_ni_rgsw_cts: Vec, /// Zero encryptions for RGSW ciphertexts for all indices ni_rgsw_zero_encs: Vec, /// Key switching key from u_j to s where u_j is user j's RLWE secret `u` /// and `s` is ideal RLWE secret. Note that in server key share the key /// switching key is encrypted under user j's RLWE secret `s_j`. It is /// then switched to ideal RLWE secret after adding zero encryptions /// generated using same `a_k`s from other users. /// /// That is the key share has the following key switching key: /// (a_k*s_j + e + \beta u_j, a_k*s_j + e) ui_to_s_ksk: M, /// Zero encryptions to switch user l's key switching key u_l to s from /// user l's RLWE secret s_l to ideal RLWE secret `s`. /// /// If there are P total parties then zero encryption sets are generated for /// each party l \in [0, P) and l != j where j self's user_id. /// /// Zero encryption set for user `l` is stored at index l is l < j otherwise /// it is stored at index l - 1, where j is self's user_id ksk_zero_encs_for_others: Vec, /// RLWE auto key shares for auto elements [-g, g, g^2, g^{w}] where `w` /// is the window size. Auto key share corresponding to auto element -g /// is stored at key 0 and key share corresponding to auto element g^{k} is /// stored at key `k` auto_keys_share: HashMap, /// LWE key switching key share to key switching LWE_{q, s}(m) to LWE_{q, /// z}(m) lwe_ksk_share: M::R, /// User's id. /// /// If there are P total parties, then user id must be inque and in range /// [0, P) user_id: usize, /// Total users participating in multi-party compute total_users: usize, /// LWE dimension lwe_n: usize, /// Common reference seed cr_seed: S, parameters: P, } mod impl_common_ref_non_interactive_multi_party_server_share { use crate::evaluator::interactive_mult_party_user_id_lwe_segment; use super::*; impl CommonReferenceSeededNonInteractiveMultiPartyServerKeyShare { pub(in super::super) fn new( self_leader_ni_rgsw_cts: Vec, not_self_leader_ni_rgsw_cts: Vec, ni_rgsw_zero_encs: Vec, ui_to_s_ksk: M, ksk_zero_encs_for_others: Vec, auto_keys_share: HashMap, lwe_ksk_share: M::R, user_index: usize, total_users: usize, lwe_n: usize, cr_seed: S, parameters: P, ) -> Self { Self { self_leader_ni_rgsw_cts, not_self_leader_ni_rgsw_cts, ni_rgsw_zero_encs, ui_to_s_ksk, ksk_zero_encs_for_others, auto_keys_share, lwe_ksk_share, user_id: user_index, total_users, lwe_n, cr_seed, parameters, } } pub(in super::super) fn ni_rgsw_cts_for_self_leader_lwe_index( &self, lwe_index: usize, ) -> &M { let self_segment = interactive_mult_party_user_id_lwe_segment( self.user_id, self.total_users, self.lwe_n, ); assert!(lwe_index >= self_segment.0 && lwe_index < self_segment.1); &self.self_leader_ni_rgsw_cts[lwe_index - self_segment.0] } pub(in super::super) fn ni_rgsw_cts_for_self_not_leader_lwe_index( &self, lwe_index: usize, ) -> &M { let self_segment = interactive_mult_party_user_id_lwe_segment( self.user_id, self.total_users, self.lwe_n, ); // Non-interactive RGSW cts when self is not leader are stored in // sorted-order. For ex, if self is the leader for indices (5, 6] // then self stores NI-RGSW cts for rest of indices like [0, 1, 2, // 3, 4, 6, 7, 8, 9] assert!(lwe_index < self.lwe_n); assert!(lwe_index < self_segment.0 || lwe_index >= self_segment.1); if lwe_index < self_segment.0 { &self.not_self_leader_ni_rgsw_cts[lwe_index] } else { &self.not_self_leader_ni_rgsw_cts[lwe_index - (self_segment.1 - self_segment.0)] } } pub(in super::super) fn ni_rgsw_zero_enc_for_lwe_index(&self, lwe_index: usize) -> &M { &self.ni_rgsw_zero_encs[lwe_index] } pub(in super::super) fn ui_to_s_ksk(&self) -> &M { &self.ui_to_s_ksk } pub(in super::super) fn user_index(&self) -> usize { self.user_id } pub(in super::super) fn auto_keys_share(&self) -> &HashMap { &self.auto_keys_share } pub(in super::super) fn lwe_ksk_share(&self) -> &M::R { &self.lwe_ksk_share } pub(in super::super) fn ui_to_s_ksk_zero_encs_for_user_i(&self, user_i: usize) -> &M { assert!(user_i != self.user_id); if user_i < self.user_id { &self.ksk_zero_encs_for_others[user_i] } else { &self.ksk_zero_encs_for_others[user_i - 1] } } } } /// Stores both normal and shoup representation of elements in the container /// (for ex, a matrix). /// /// To access normal representation borrow self as a `self.as_ref()`. To access /// shoup representation call `self.shoup_repr()` pub(crate) struct NormalAndShoup(M, M); impl NormalAndShoup { fn new_with_modulus(value: M, modulus: ::Modulus) -> Self { let value_shoup = M::to_shoup(&value, modulus); NormalAndShoup(value, value_shoup) } } impl AsRef for NormalAndShoup { fn as_ref(&self) -> &M { &self.0 } } impl WithShoupRepr for NormalAndShoup { type M = M; fn shoup_repr(&self) -> &Self::M { &self.1 } } #[cfg(test)] pub(crate) mod key_size { use num_traits::{FromPrimitive, PrimInt}; use crate::{backend::Modulus, decomposer::NumInfo, SizeInBitsWithLogModulus}; use super::*; /// Size of the Key in Bits pub(crate) trait KeySize { /// Returns size of the key in bits fn size(&self) -> usize; } impl KeySize for CommonReferenceSeededInteractiveMultiPartyServerKeyShare, S> where M: SizeInBitsWithLogModulus, M::R: SizeInBitsWithLogModulus, El: PrimInt + NumInfo + FromPrimitive, { fn size(&self) -> usize { let mut total = 0; let log_rlweq = self.parameters().rlwe_q().log_q(); self.self_leader_rgsws .iter() .for_each(|v| total += v.size(log_rlweq)); self.not_self_leader_rgsws .iter() .for_each(|v| total += v.size(log_rlweq)); self.auto_keys .values() .for_each(|v| total += v.size(log_rlweq)); let log_lweq = self.parameters().lwe_q().log_q(); total += self.lwe_ksk.size(log_lweq); total } } impl KeySize for CommonReferenceSeededNonInteractiveMultiPartyServerKeyShare, S> where M: SizeInBitsWithLogModulus, M::R: SizeInBitsWithLogModulus, El: PrimInt + NumInfo + FromPrimitive, { fn size(&self) -> usize { let mut total = 0; let log_rlweq = self.parameters.rlwe_q().log_q(); self.self_leader_ni_rgsw_cts .iter() .for_each(|v| total += v.size(log_rlweq)); self.not_self_leader_ni_rgsw_cts .iter() .for_each(|v| total += v.size(log_rlweq)); self.ni_rgsw_zero_encs .iter() .for_each(|v| total += v.size(log_rlweq)); total += self.ui_to_s_ksk.size(log_rlweq); self.ksk_zero_encs_for_others .iter() .for_each(|v| total += v.size(log_rlweq)); self.auto_keys_share .values() .for_each(|v| total += v.size(log_rlweq)); let log_lweq = self.parameters.lwe_q().log_q(); total += self.lwe_ksk_share.size(log_lweq); total } } } pub(super) mod tests { use itertools::izip; use num_traits::{FromPrimitive, PrimInt, ToPrimitive, Zero}; use crate::{ backend::{GetModulus, Modulus}, bool::ClientKey, decomposer::NumInfo, lwe::decrypt_lwe, parameters::CiphertextModulus, utils::TryConvertFrom1, ArithmeticOps, Row, }; use super::SinglePartyClientKey; pub(crate) fn ideal_sk_rlwe(cks: &[ClientKey]) -> Vec { let mut ideal_rlwe_sk = cks[0].sk_rlwe(); cks.iter().skip(1).for_each(|k| { let sk_rlwe = k.sk_rlwe(); izip!(ideal_rlwe_sk.iter_mut(), sk_rlwe.iter()).for_each(|(a, b)| { *a = *a + b; }); }); ideal_rlwe_sk } pub(crate) fn ideal_sk_lwe(cks: &[ClientKey]) -> Vec { let mut ideal_rlwe_sk = cks[0].sk_lwe(); cks.iter().skip(1).for_each(|k| { let sk_rlwe = k.sk_lwe(); izip!(ideal_rlwe_sk.iter_mut(), sk_rlwe.iter()).for_each(|(a, b)| { *a = *a + b; }); }); ideal_rlwe_sk } pub(crate) fn measure_noise_lwe< R: Row, S, Modop: ArithmeticOps + GetModulus, Element = R::Element>, >( lwe_ct: &R, m_expected: R::Element, sk: &[S], modop: &Modop, ) -> f64 where R: TryConvertFrom1<[S], CiphertextModulus>, R::Element: Zero + FromPrimitive + PrimInt + NumInfo, { let noisy_m = decrypt_lwe(lwe_ct, &sk, modop); let noise = modop.sub(&m_expected, &noisy_m); modop .modulus() .map_element_to_i64(&noise) .abs() .to_f64() .unwrap() .log2() } }