Browse Source

non interactive mp works

par-agg-key-shares
Janmajaya Mall 10 months ago
parent
commit
a95751f560
17 changed files with 1242 additions and 508 deletions
  1. +19
    -16
      examples/fheuint8.rs
  2. +6
    -1
      src/backend/modulus_u64.rs
  3. +56
    -45
      src/bool/evaluator.rs
  4. +114
    -7
      src/bool/keys.rs
  5. +7
    -2
      src/bool/mod.rs
  6. +60
    -76
      src/bool/mp_api.rs
  7. +517
    -39
      src/bool/ni_mp_api.rs
  8. +5
    -1
      src/bool/parameters.rs
  9. +25
    -0
      src/bool/sp_api.rs
  10. +8
    -5
      src/lib.rs
  11. +1
    -0
      src/pbs.rs
  12. +2
    -1
      src/rgsw/keygen.rs
  13. +2
    -2
      src/rgsw/mod.rs
  14. +64
    -0
      src/rgsw/runtime.rs
  15. +196
    -0
      src/shortint/enc_dec.rs
  16. +160
    -299
      src/shortint/mod.rs
  17. +0
    -14
      src/shortint/types.rs

+ 19
- 16
examples/fheuint8.rs

@ -6,9 +6,9 @@ fn plain_circuit(a: u8, b: u8, c: u8) -> u8 {
(a + b) * c
}
fn fhe_circuit(fhe_a: &FheUint8, fhe_b: &FheUint8, fhe_c: &FheUint8) -> FheUint8 {
&(fhe_a + fhe_b) * fhe_c
}
// fn fhe_circuit(fhe_a: &FheUint8, fhe_b: &FheUint8, fhe_c: &FheUint8) ->
// FheUint8 { &(fhe_a + fhe_b) * fhe_c
// }
fn main() {
set_parameter_set(ParameterSelector::MultiPartyLessThanOrEqualTo16);
@ -50,22 +50,25 @@ fn main() {
let fhe_b = public_key.encrypt(&b);
let fhe_c = public_key.encrypt(&c);
let fhe_batched = public_key.encrypt(vec![12, 3u8].as_slice());
// fhe evaluation
let now = std::time::Instant::now();
let fhe_out = fhe_circuit(&fhe_a, &fhe_b, &fhe_c);
println!("Circuit time: {:?}", now.elapsed());
// let now = std::time::Instant::now();
// let fhe_out = fhe_circuit(&fhe_a, &fhe_b, &fhe_c);
// println!("Circuit time: {:?}", now.elapsed());
// plain evaluation
let out = plain_circuit(a, b, c);
// // plain evaluation
// let out = plain_circuit(a, b, c);
// generate decryption shares to decrypt ciphertext fhe_out
let decryption_shares = client_keys
.iter()
.map(|k| k.gen_decryption_share(&fhe_out))
.collect_vec();
// // generate decryption shares to decrypt ciphertext fhe_out
// let decryption_shares = client_keys
// .iter()
// .map(|k| k.gen_decryption_share(&fhe_out))
// .collect_vec();
// decrypt fhe_out using decryption shares
let got_out = client_keys[0].aggregate_decryption_shares(&fhe_out, &decryption_shares);
// // decrypt fhe_out using decryption shares
// let got_out = client_keys[0].aggregate_decryption_shares(&fhe_out,
// &decryption_shares);
assert_eq!(got_out, out);
// assert_eq!(got_out, out);
}

+ 6
- 1
src/backend/modulus_u64.rs

@ -231,7 +231,12 @@ impl VectorOps for ModularOpsU64 {
impl<R: RowMut<Element = u64>, T> ShoupMatrixFMA<R> for ModularOpsU64<T> {
fn shoup_matrix_fma(&self, out: &mut [R::Element], a: &[R], a_shoup: &[R], b: &[R]) {
assert!(a.len() == a_shoup.len());
assert!(a.len() == b.len());
assert!(
a.len() == b.len(),
"Unequal length {}!={}",
a.len(),
b.len()
);
let q = self.q;
let q_twice = self.q << 1;

+ 56
- 45
src/bool/evaluator.rs

@ -20,6 +20,7 @@ use crate::{
backend::{
ArithmeticOps, GetModulus, ModInit, ModularOpsU64, Modulus, ShoupMatrixFMA, VectorOps,
},
bool::parameters::ParameterVariant,
decomposer::{Decomposer, DefaultDecomposer, NumInfo, RlweDecomposer},
lwe::{decrypt_lwe, encrypt_lwe, lwe_key_switch, lwe_ksk_keygen, measure_noise_lwe, LweSecret},
multi_party::{
@ -48,10 +49,12 @@ use crate::{
use super::{
keys::{
ClientKey, CommonReferenceSeededCollectivePublicKeyShare,
CommonReferenceSeededMultiPartyServerKeyShare, InteractiveMultiPartyClientKey,
NonInteractiveMultiPartyClientKey, SeededMultiPartyServerKey,
SeededNonInteractiveMultiPartyServerKey, SeededSinglePartyServerKey,
ServerKeyEvaluationDomain, ShoupServerKeyEvaluationDomain, SinglePartyClientKey,
CommonReferenceSeededMultiPartyServerKeyShare,
CommonReferenceSeededNonInteractiveMultiPartyServerKeyShare,
InteractiveMultiPartyClientKey, NonInteractiveMultiPartyClientKey,
SeededMultiPartyServerKey, SeededNonInteractiveMultiPartyServerKey,
SeededSinglePartyServerKey, ServerKeyEvaluationDomain, ShoupServerKeyEvaluationDomain,
SinglePartyClientKey,
},
parameters::{
BoolParameters, CiphertextModulus, DecompositionCount, DecompostionLogBase,
@ -59,30 +62,6 @@ use super::{
},
};
pub struct CommonReferenceSeededNonInteractiveMultiPartyServerKeyShare<M: Matrix, S> {
/// (ak*si + e + \beta ui, ak*si + e)
ni_rgsw_cts: (Vec<M>, Vec<M>),
ui_to_s_ksk: M,
others_ksk_zero_encs: Vec<M>,
auto_keys_share: HashMap<usize, M>,
lwe_ksk_share: M::R,
user_index: usize,
cr_seed: S,
}
impl<M: Matrix, S> CommonReferenceSeededNonInteractiveMultiPartyServerKeyShare<M, S> {
fn ui_to_s_ksk_zero_encs_for_user_i(&self, user_i: usize) -> &M {
assert!(user_i != self.user_index);
if user_i < self.user_index {
&self.others_ksk_zero_encs[user_i]
} else {
&self.others_ksk_zero_encs[user_i - 1]
}
}
}
pub struct MultiPartyCrs<S> {
pub(super) seed: S,
}
@ -405,19 +384,25 @@ where
nor_test_vec: M::R,
xor_test_vec: M::R,
xnor_test_vec: M::R,
/// Non-interactive u_i -> s key switch decomposer
ni_ui_to_s_ks_decomposer: Option<DefaultDecomposer<M::MatElement>>,
_phantom: PhantomData<SKey>,
}
impl<M: Matrix, NttOp, RlweModOp, LweModOp, Skey>
BoolEvaluator<M, NttOp, RlweModOp, LweModOp, Skey>
{
pub(super) fn parameters(&self) -> &BoolParameters<M::MatElement> {
pub(crate) fn parameters(&self) -> &BoolParameters<M::MatElement> {
&self.pbs_info.parameters
}
pub(super) fn pbs_info(&self) -> &BoolPbsInfo<M, NttOp, RlweModOp, LweModOp> {
&self.pbs_info
}
pub(super) fn ni_ui_to_s_ks_decomposer(&self) -> &Option<DefaultDecomposer<M::MatElement>> {
&self.ni_ui_to_s_ks_decomposer
}
}
fn trim_rgsw_ct_matrix_from_rgrg_to_rlrg<
@ -628,6 +613,15 @@ where
let scratch_memory = ScratchMemory::new(&parameters);
let ni_ui_to_s_ks_decomposer = if parameters.variant()
== &ParameterVariant::NonInteractiveMultiParty
{
Some(parameters
.non_interactive_ui_to_s_key_switch_decomposer::<DefaultDecomposer<M::MatElement>>())
} else {
None
};
let pbs_info = BoolPbsInfo {
auto_decomposer: parameters.auto_decomposer(),
lwe_decomposer: parameters.lwe_decomposer(),
@ -651,6 +645,7 @@ where
nor_test_vec,
xnor_test_vec,
xor_test_vec,
ni_ui_to_s_ks_decomposer,
_phantom: PhantomData,
}
}
@ -665,6 +660,8 @@ where
&self,
client_key: &K,
) -> SeededSinglePartyServerKey<M, BoolParameters<M::MatElement>, [u8; 32]> {
assert_eq!(self.parameters().variant(), &ParameterVariant::SingleParty);
DefaultSecureRng::with_local_mut(|rng| {
let mut main_seed = [0u8; 32];
rng.fill_bytes(&mut main_seed);
@ -773,6 +770,8 @@ where
client_key: &K,
) -> CommonReferenceSeededMultiPartyServerKeyShare<M, BoolParameters<M::MatElement>, [u8; 32]>
{
assert_eq!(self.parameters().variant(), &ParameterVariant::MultiParty);
DefaultSecureRng::with_local_mut(|rng| {
let mut main_prng = DefaultSecureRng::new_seeded(cr_seed);
@ -908,9 +907,14 @@ where
where
M: Clone + Debug,
{
assert_eq!(
self.parameters().variant(),
&ParameterVariant::NonInteractiveMultiParty
);
// sanity checks
let key_order = {
let existing_key_order = key_shares.iter().map(|s| s.user_index).collect_vec();
let existing_key_order = key_shares.iter().map(|s| s.user_index()).collect_vec();
// record the order s.t. key_order[i] stores the position of i^th
// users key share in existing order
@ -940,15 +944,15 @@ where
let mut ui_to_s_ksks = key_shares
.iter()
.map(|share| {
let mut useri_ui_to_s_ksk = share.ui_to_s_ksk.clone();
let mut useri_ui_to_s_ksk = share.ui_to_s_ksk().clone();
assert!(
useri_ui_to_s_ksk.dimension() == (ui_to_s_ksk_decomposition_count.0, ring_size)
);
key_shares
.iter()
.filter(|x| x.user_index != share.user_index)
.filter(|x| x.user_index() != share.user_index())
.for_each(|(other_share)| {
let op2 = other_share.ui_to_s_ksk_zero_encs_for_user_i(share.user_index);
let op2 = other_share.ui_to_s_ksk_zero_encs_for_user_i(share.user_index());
assert!(op2.dimension() == (ui_to_s_ksk_decomposition_count.0, ring_size));
izip!(useri_ui_to_s_ksk.iter_rows_mut(), op2.iter_rows()).for_each(
|(add_to, add_from)| {
@ -982,7 +986,7 @@ where
.map(|share| {
let mut ksk_prng = DefaultSecureRng::new_seeded(
cr_seed
.ui_to_s_ks_seed_for_user_i::<DefaultSecureRng>(share.user_index),
.ui_to_s_ks_seed_for_user_i::<DefaultSecureRng>(share.user_index()),
);
let mut ais = M::zeros(ui_to_s_ksk_decomposition_count.0, ring_size);
@ -1036,7 +1040,7 @@ where
key_shares.iter().for_each(|s| {
rlwe_modop.elwise_add_mut(
tmp_space.as_mut(),
s.ni_rgsw_cts.1[lwe_index].get_row_slice(d_index),
s.ni_rgsw_cts().1[lwe_index].get_row_slice(d_index),
);
});
@ -1130,7 +1134,7 @@ where
|(share, user_uitos_ksk_partb_eval, user_uitos_ksk_parta_eval)| {
// RGSW_s(X^{s[i]})
let rgsw_cts_user_i_eval = izip!(
share.ni_rgsw_cts.0.iter(),
share.ni_rgsw_cts().0.iter(),
decomp_ni_rgsw_neg_ais.iter(),
decomp_ni_rgsws_part_1_acc.iter()
)
@ -1370,7 +1374,8 @@ where
let mut key = M::zeros(self.parameters().auto_decomposition_count().0, ring_size);
key_shares.iter().for_each(|s| {
let auto_key_share_i = s.auto_keys_share.get(&i).expect("Auto key {i} missing");
let auto_key_share_i =
s.auto_keys_share().get(&i).expect("Auto key {i} missing");
assert!(
auto_key_share_i.dimension()
== (self.parameters().auto_decomposition_count().0, ring_size)
@ -1393,10 +1398,10 @@ where
M::R::zeros(self.parameters().lwe_decomposition_count().0 * ring_size);
key_shares.iter().for_each(|s| {
assert!(
s.lwe_ksk_share.as_ref().len()
s.lwe_ksk_share().as_ref().len()
== self.parameters().lwe_decomposition_count().0 * ring_size
);
lwe_modop.elwise_add_mut(lwe_ksk.as_mut(), s.lwe_ksk_share.as_ref());
lwe_modop.elwise_add_mut(lwe_ksk.as_mut(), s.lwe_ksk_share().as_ref());
});
lwe_ksk
};
@ -1425,6 +1430,11 @@ where
M,
NonInteractiveMultiPartyCrs<[u8; 32]>,
> {
assert_eq!(
self.parameters().variant(),
&ParameterVariant::NonInteractiveMultiParty
);
// TODO: check whether parameters support `total_users`
let nttop = self.pbs_info().nttop_rlweq();
let rlwe_modop = self.pbs_info().modop_rlweq();
@ -1539,15 +1549,15 @@ where
self._common_rountine_multi_party_lwe_ksk_share_gen(lwe_ksk_seed, &sk_rlwe, &sk_lwe)
};
CommonReferenceSeededNonInteractiveMultiPartyServerKeyShare {
CommonReferenceSeededNonInteractiveMultiPartyServerKeyShare::new(
ni_rgsw_cts,
ui_to_s_ksk,
others_ksk_zero_encs: zero_encs_for_others,
user_index: self_index,
zero_encs_for_others,
auto_keys_share,
lwe_ksk_share,
cr_seed: cr_seed.clone(),
}
self_index,
cr_seed.clone(),
)
}
fn _common_rountine_multi_party_auto_keys_share_gen(
@ -1826,6 +1836,7 @@ where
S: PartialEq + Clone,
M: Clone,
{
assert_eq!(self.parameters().variant(), &ParameterVariant::MultiParty);
assert!(shares.len() > 0);
let parameters = shares[0].parameters().clone();
let cr_seed = shares[0].cr_seed();
@ -3174,7 +3185,7 @@ mod tests {
);
let server_key_evaluation_domain =
NonInteractiveServerKeyEvaluationDomain::<_, _, DefaultSecureRng, NttBackendU64>::from(
seeded_server_key,
&seeded_server_key,
);
let mut ideal_rlwe = vec![0; ring_size];

+ 114
- 7
src/bool/keys.rs

@ -752,7 +752,7 @@ pub(super) mod impl_non_interactive_server_key_eval_domain {
impl<M, Rng, N>
From<
SeededNonInteractiveMultiPartyServerKey<
&SeededNonInteractiveMultiPartyServerKey<
M,
NonInteractiveMultiPartyCrs<Rng::Seed>,
BoolParameters<M::MatElement>,
@ -769,7 +769,7 @@ pub(super) mod impl_non_interactive_server_key_eval_domain {
Rng::Seed: Clone + Copy + Default,
{
fn from(
value: SeededNonInteractiveMultiPartyServerKey<
value: &SeededNonInteractiveMultiPartyServerKey<
M,
NonInteractiveMultiPartyCrs<Rng::Seed>,
BoolParameters<M::MatElement>,
@ -802,7 +802,7 @@ pub(super) mod impl_non_interactive_server_key_eval_domain {
assert!(auto_part_b.dimension() == (d_auto, ring_size));
let mut auto_ct = M::zeros(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| {
@ -862,7 +862,7 @@ pub(super) mod impl_non_interactive_server_key_eval_domain {
.non_interactive_ui_to_s_key_switch_decomposition_count()
.0;
let total_users = *value.ui_to_s_ksks_key_order.iter().max().unwrap();
let ui_to_s_ksks = (0..total_users)
let ui_to_s_ksks = (0..total_users + 1)
.map(|user_index| {
let user_i_seed = value.cr_seed.ui_to_s_ks_seed_for_user_i::<Rng>(user_index);
let mut prng = Rng::new_with_seed(user_i_seed);
@ -963,6 +963,12 @@ mod impl_shoup_non_interactive_server_key_eval_domain {
use super::*;
use crate::{backend::Modulus, pbs::PbsKey};
impl<M> ShoupNonInteractiveServerKeyEvaluationDomain<M> {
pub(in super::super) fn ui_to_s_ksk(&self, user_id: usize) -> &NormalAndShoup<M> {
&self.ui_to_s_ksks[user_id]
}
}
impl<M: Matrix + ToShoup<Modulus = M::MatElement>, R, N>
From<NonInteractiveServerKeyEvaluationDomain<M, BoolParameters<M::MatElement>, R, N>>
for ShoupNonInteractiveServerKeyEvaluationDomain<M>
@ -974,23 +980,55 @@ mod impl_shoup_non_interactive_server_key_eval_domain {
) -> 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| NormalAndShoup::new_with_modulus(m, rlwe_q))
.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| NormalAndShoup::new_with_modulus(m, rlwe_q))
.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,
@ -1006,7 +1044,8 @@ mod impl_shoup_non_interactive_server_key_eval_domain {
type RgswCt = NormalAndShoup<M>;
fn galois_key_for_auto(&self, k: usize) -> &Self::AutoKey {
self.auto_keys.get(&k).unwrap()
let d = self.auto_keys.get(&k).unwrap();
d
}
fn rgsw_ct_lwe_si(&self, si: usize) -> &Self::RgswCt {
&self.rgsw_cts[si]
@ -1084,6 +1123,74 @@ mod shoup_server_key_eval_domain {
}
}
pub struct CommonReferenceSeededNonInteractiveMultiPartyServerKeyShare<M: Matrix, S> {
/// (ak*si + e + \beta ui, ak*si + e)
ni_rgsw_cts: (Vec<M>, Vec<M>),
ui_to_s_ksk: M,
others_ksk_zero_encs: Vec<M>,
auto_keys_share: HashMap<usize, M>,
lwe_ksk_share: M::R,
user_index: usize,
cr_seed: S,
}
mod impl_common_ref_non_interactive_multi_party_server_share {
use super::*;
impl<M: Matrix, S> CommonReferenceSeededNonInteractiveMultiPartyServerKeyShare<M, S> {
pub(in super::super) fn new(
ni_rgsw_cts: (Vec<M>, Vec<M>),
ui_to_s_ksk: M,
others_ksk_zero_encs: Vec<M>,
auto_keys_share: HashMap<usize, M>,
lwe_ksk_share: M::R,
user_index: usize,
cr_seed: S,
) -> Self {
Self {
ni_rgsw_cts,
ui_to_s_ksk,
others_ksk_zero_encs,
auto_keys_share,
lwe_ksk_share,
user_index,
cr_seed,
}
}
pub(in super::super) fn ni_rgsw_cts(&self) -> &(Vec<M>, Vec<M>) {
&self.ni_rgsw_cts
}
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_index
}
pub(in super::super) fn auto_keys_share(&self) -> &HashMap<usize, M> {
&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_index);
if user_i < self.user_index {
&self.others_ksk_zero_encs[user_i]
} else {
&self.others_ksk_zero_encs[user_i - 1]
}
}
}
}
/// Stores normal and shoup representation of Matrix elements (Normal, Shoup)
pub(crate) struct NormalAndShoup<M>(M, M);

+ 7
- 2
src/bool/mod.rs

@ -4,9 +4,14 @@ mod mp_api;
mod ni_mp_api;
mod noise;
pub(crate) mod parameters;
mod sp_api;
pub(crate) use keys::PublicKey;
pub type FheBool = Vec<u64>;
pub use ni_mp_api::*;
pub type ClientKey = keys::ClientKey<[u8; 32], u64>;
pub use mp_api::*;
pub enum ParameterSelector {
MultiPartyLessThanOrEqualTo16,
NonInteractiveMultiPartyLessThanOrEqualTo16,
}

+ 60
- 76
src/bool/mp_api.rs

@ -7,27 +7,32 @@ use crate::{
utils::{Global, WithLocal},
};
use super::{evaluator::*, keys::*, parameters::*};
use super::{evaluator::MultiPartyCrs, keys::*, parameters::*, ClientKey, ParameterSelector};
pub type BoolEvaluator = super::evaluator::BoolEvaluator<
Vec<Vec<u64>>,
NttBackendU64,
ModularOpsU64<CiphertextModulus<u64>>,
ModulusPowerOf2<CiphertextModulus<u64>>,
ShoupServerKeyEvaluationDomain<Vec<Vec<u64>>>,
>;
thread_local! {
static BOOL_EVALUATOR: RefCell<Option<BoolEvaluator<Vec<Vec<u64>>, NttBackendU64, ModularOpsU64<CiphertextModulus<u64>>, ModulusPowerOf2<CiphertextModulus<u64>>, ShoupServerKeyEvaluationDomain<Vec<Vec<u64>>>>>> = RefCell::new(None);
static BOOL_EVALUATOR: RefCell<Option<BoolEvaluator>> = RefCell::new(None);
}
static BOOL_SERVER_KEY: OnceLock<ShoupServerKeyEvaluationDomain<Vec<Vec<u64>>>> = OnceLock::new();
static MULTI_PARTY_CRS: OnceLock<MultiPartyCrs<[u8; 32]>> = OnceLock::new();
pub type ClientKey = super::keys::ClientKey<[u8; 32], u64>;
pub enum ParameterSelector {
MultiPartyLessThanOrEqualTo16,
}
pub fn set_parameter_set(select: ParameterSelector) {
match select {
ParameterSelector::MultiPartyLessThanOrEqualTo16 => {
BOOL_EVALUATOR.with_borrow_mut(|v| *v = Some(BoolEvaluator::new(SMALL_MP_BOOL_PARAMS)));
}
_ => {
panic!("Paramerters not supported")
}
}
}
@ -134,15 +139,7 @@ impl Global for MultiPartyCrs<[u8; 32]> {
}
// BOOL EVALUATOR //
impl WithLocal
for BoolEvaluator<
Vec<Vec<u64>>,
NttBackendU64,
ModularOpsU64<CiphertextModulus<u64>>,
ModulusPowerOf2<CiphertextModulus<u64>>,
ShoupServerKeyEvaluationDomain<Vec<Vec<u64>>>,
>
{
impl WithLocal for BoolEvaluator {
fn with_local<F, R>(func: F) -> R
where
F: Fn(&Self) -> R,
@ -174,74 +171,61 @@ impl Global for RuntimeServerKey {
mod impl_enc_dec {
use crate::{
bool::evaluator::BoolEncoding,
pbs::{sample_extract, PbsInfo},
rgsw::public_key_encrypt_rlwe,
Decryptor, Encryptor, Matrix, MatrixEntity, MultiPartyDecryptor, RowEntity,
Encryptor, Matrix, MatrixEntity, MultiPartyDecryptor, RowEntity,
};
use num_traits::Zero;
use itertools::Itertools;
use num_traits::{ToPrimitive, Zero};
use super::*;
type Mat = Vec<Vec<u64>>;
impl<E> Encryptor<bool, Vec<u64>> for super::super::keys::ClientKey<[u8; 32], E> {
fn encrypt(&self, m: &bool) -> Vec<u64> {
BoolEvaluator::with_local(|e| e.sk_encrypt(*m, self))
}
}
impl<E> Decryptor<bool, Vec<u64>> for super::super::keys::ClientKey<[u8; 32], E> {
fn decrypt(&self, c: &Vec<u64>) -> bool {
BoolEvaluator::with_local(|e| e.sk_decrypt(c, self))
}
}
impl<E> MultiPartyDecryptor<bool, <Mat as Matrix>::R>
for super::super::keys::ClientKey<[u8; 32], E>
{
type DecryptionShare = <Mat as Matrix>::MatElement;
fn gen_decryption_share(&self, c: &<Mat as Matrix>::R) -> Self::DecryptionShare {
BoolEvaluator::with_local(|e| e.multi_party_decryption_share(c, self))
}
fn aggregate_decryption_shares(
&self,
c: &<Mat as Matrix>::R,
shares: &[Self::DecryptionShare],
) -> bool {
BoolEvaluator::with_local(|e| e.multi_party_decrypt(shares, c))
}
}
impl<Rng, ModOp> Encryptor<[bool], Mat> for PublicKey<Mat, Rng, ModOp> {
fn encrypt(&self, m: &[bool]) -> Mat {
impl<Rng, ModOp> Encryptor<[bool], Vec<Mat>> for PublicKey<Mat, Rng, ModOp> {
fn encrypt(&self, m: &[bool]) -> Vec<Mat> {
BoolEvaluator::with_local(|e| {
DefaultSecureRng::with_local_mut(|rng| {
let parameters = e.parameters();
let mut rlwe_out = <Mat as MatrixEntity>::zeros(2, parameters.rlwe_n().0);
assert!(m.len() <= parameters.rlwe_n().0);
let mut message =
vec![<Mat as Matrix>::MatElement::zero(); parameters.rlwe_n().0];
m.iter().enumerate().for_each(|(i, v)| {
if *v {
message[i] = parameters.rlwe_q().true_el()
} else {
message[i] = parameters.rlwe_q().false_el()
}
});
// e.pk_encrypt_batched(self.key(), m)
public_key_encrypt_rlwe::<_, _, _, _, i32, _>(
&mut rlwe_out,
self.key(),
&message,
e.pbs_info().modop_rlweq(),
e.pbs_info().nttop_rlweq(),
rng,
);
rlwe_out
let ring_size = parameters.rlwe_n().0;
let rlwe_count = ((m.len() as f64 / ring_size as f64).ceil())
.to_usize()
.unwrap();
// encrypt `m` into ceil(len(m)/N) RLWE ciphertexts
let rlwes = (0..rlwe_count)
.map(|index| {
let mut message = vec![<Mat as Matrix>::MatElement::zero(); ring_size];
m[(index * ring_size)..std::cmp::min(m.len(), (index + 1) * ring_size)]
.iter()
.enumerate()
.for_each(|(i, v)| {
if *v {
message[i] = parameters.rlwe_q().true_el()
} else {
message[i] = parameters.rlwe_q().false_el()
}
});
// encrypt message
let mut rlwe_out =
<Mat as MatrixEntity>::zeros(2, parameters.rlwe_n().0);
public_key_encrypt_rlwe::<_, _, _, _, i32, _>(
&mut rlwe_out,
self.key(),
&message,
e.pbs_info().modop_rlweq(),
e.pbs_info().nttop_rlweq(),
rng,
);
rlwe_out
})
.collect_vec();
rlwes
})
})
}
@ -250,10 +234,10 @@ mod impl_enc_dec {
impl<Rng, ModOp> Encryptor<bool, <Mat as Matrix>::R> for PublicKey<Mat, Rng, ModOp> {
fn encrypt(&self, m: &bool) -> <Mat as Matrix>::R {
let m = vec![*m];
let rlwe = self.encrypt(m.as_slice());
let rlwe = &self.encrypt(m.as_slice())[0];
BoolEvaluator::with_local(|e| {
let mut lwe = <Mat as Matrix>::R::zeros(e.parameters().rlwe_n().0 + 1);
sample_extract(&mut lwe, &rlwe, e.pbs_info().modop_rlweq(), 0);
sample_extract(&mut lwe, rlwe, e.pbs_info().modop_rlweq(), 0);
lwe
})
}

+ 517
- 39
src/bool/ni_mp_api.rs

@ -1,66 +1,544 @@
use std::{cell::RefCell, sync::OnceLock};
use crate::{
backend::ModulusPowerOf2,
bool::parameters::ParameterVariant,
random::DefaultSecureRng,
utils::{Global, WithLocal},
ModularOpsU64, NttBackendU64,
};
use super::{
evaluator::NonInteractiveMultiPartyCrs,
keys::{
CommonReferenceSeededNonInteractiveMultiPartyServerKeyShare,
NonInteractiveServerKeyEvaluationDomain, SeededNonInteractiveMultiPartyServerKey,
ShoupNonInteractiveServerKeyEvaluationDomain,
},
parameters::{BoolParameters, CiphertextModulus, NON_INTERACTIVE_SMALL_MP_BOOL_PARAMS},
ClientKey, ParameterSelector,
};
pub type BoolEvaluator = super::evaluator::BoolEvaluator<
Vec<Vec<u64>>,
NttBackendU64,
ModularOpsU64<CiphertextModulus<u64>>,
ModulusPowerOf2<CiphertextModulus<u64>>,
ShoupNonInteractiveServerKeyEvaluationDomain<Vec<Vec<u64>>>,
>;
thread_local! {
static BOOL_EVALUATOR: RefCell<Option<BoolEvaluator>> = RefCell::new(None);
}
static BOOL_SERVER_KEY: OnceLock<ShoupNonInteractiveServerKeyEvaluationDomain<Vec<Vec<u64>>>> =
OnceLock::new();
static MULTI_PARTY_CRS: OnceLock<NonInteractiveMultiPartyCrs<[u8; 32]>> = OnceLock::new();
pub fn set_parameter_set(select: ParameterSelector) {
match select {
ParameterSelector::NonInteractiveMultiPartyLessThanOrEqualTo16 => {
BOOL_EVALUATOR.with_borrow_mut(|v| {
*v = Some(BoolEvaluator::new(NON_INTERACTIVE_SMALL_MP_BOOL_PARAMS))
});
}
_ => {
panic!("Paramerters not supported")
}
}
}
pub fn set_common_reference_seed(seed: [u8; 32]) {
BoolEvaluator::with_local(|e| {
assert_eq!(
e.parameters().variant(),
&ParameterVariant::NonInteractiveMultiParty,
"Set parameters do not support Non interactive multi-party"
);
});
assert!(
MULTI_PARTY_CRS
.set(NonInteractiveMultiPartyCrs { seed: seed })
.is_ok(),
"Attempted to set MP SEED twice."
)
}
pub fn gen_client_key() -> ClientKey {
BoolEvaluator::with_local(|e| e.client_key())
}
pub fn gen_server_key_share(
user_id: usize,
total_users: usize,
client_key: &ClientKey,
) -> CommonReferenceSeededNonInteractiveMultiPartyServerKeyShare<
Vec<Vec<u64>>,
NonInteractiveMultiPartyCrs<[u8; 32]>,
> {
BoolEvaluator::with_local(|e| {
let cr_seed = NonInteractiveMultiPartyCrs::global();
e.non_interactive_multi_party_key_share(cr_seed, user_id, total_users, client_key)
})
}
pub fn aggregate_server_key_shares(
shares: &[CommonReferenceSeededNonInteractiveMultiPartyServerKeyShare<
Vec<Vec<u64>>,
NonInteractiveMultiPartyCrs<[u8; 32]>,
>],
) -> SeededNonInteractiveMultiPartyServerKey<
Vec<Vec<u64>>,
NonInteractiveMultiPartyCrs<[u8; 32]>,
BoolParameters<u64>,
> {
BoolEvaluator::with_local(|e| {
let cr_seed = NonInteractiveMultiPartyCrs::global();
e.aggregate_non_interactive_multi_party_key_share(cr_seed, shares.len(), shares)
})
}
impl
SeededNonInteractiveMultiPartyServerKey<
Vec<Vec<u64>>,
NonInteractiveMultiPartyCrs<[u8; 32]>,
BoolParameters<u64>,
>
{
pub fn set_server_key(&self) {
let eval_key = NonInteractiveServerKeyEvaluationDomain::<
_,
BoolParameters<u64>,
DefaultSecureRng,
NttBackendU64,
>::from(self);
assert!(
BOOL_SERVER_KEY
.set(ShoupNonInteractiveServerKeyEvaluationDomain::from(eval_key))
.is_ok(),
"Attempted to set server key twice!"
);
}
}
impl Global for NonInteractiveMultiPartyCrs<[u8; 32]> {
fn global() -> &'static Self {
MULTI_PARTY_CRS
.get()
.expect("Non-interactive multi-party common reference string not set")
}
}
// BOOL EVALUATOR //
impl WithLocal for BoolEvaluator {
fn with_local<F, R>(func: F) -> R
where
F: Fn(&Self) -> R,
{
BOOL_EVALUATOR.with_borrow(|s| func(s.as_ref().expect("Parameters not set")))
}
fn with_local_mut<F, R>(func: F) -> R
where
F: Fn(&mut Self) -> R,
{
BOOL_EVALUATOR.with_borrow_mut(|s| func(s.as_mut().expect("Parameters not set")))
}
fn with_local_mut_mut<F, R>(func: &mut F) -> R
where
F: FnMut(&mut Self) -> R,
{
BOOL_EVALUATOR.with_borrow_mut(|s| func(s.as_mut().expect("Parameters not set")))
}
}
pub(crate) type RuntimeServerKey = ShoupNonInteractiveServerKeyEvaluationDomain<Vec<Vec<u64>>>;
impl Global for RuntimeServerKey {
fn global() -> &'static Self {
BOOL_SERVER_KEY.get().expect("Server key not set!")
}
}
/// Non interactive multi-party specfic encryptor decryptor routines
mod impl_enc_dec {
use crate::{
bool::{
evaluator::{BoolEncoding, BoolEvaluator},
keys::NonInteractiveMultiPartyClientKey,
parameters::CiphertextModulus,
},
pbs::PbsInfo,
random::{DefaultSecureRng, NewWithSeed},
rgsw::secret_key_encrypt_rlwe,
bool::{evaluator::BoolEncoding, keys::NonInteractiveMultiPartyClientKey},
pbs::{sample_extract, PbsInfo, WithShoupRepr},
random::{DefaultSecureRng, NewWithSeed, RandomFillUniformInModulus},
rgsw::{key_switch, secret_key_encrypt_rlwe},
utils::{TryConvertFrom1, WithLocal},
Encryptor, Matrix, RowEntity,
Encryptor, KeySwitchWithId, Matrix, MatrixEntity, MatrixMut, MultiPartyDecryptor,
RowEntity, RowMut,
};
use num_traits::Zero;
use itertools::Itertools;
use num_traits::{ToPrimitive, Zero};
trait SeededCiphertext<M, S> {
fn new_with_seed(data: M, seed: S) -> Self;
}
use super::*;
type Mat = Vec<Vec<u64>>;
impl<K, C> Encryptor<[bool], C> for K
pub(super) struct BatchedFheBools<C> {
pub(super) data: Vec<C>,
}
impl<C: MatrixMut<MatElement = u64>> BatchedFheBools<C>
where
C::R: RowEntity + RowMut,
{
pub(super) fn extract(&self, index: usize) -> C::R {
BoolEvaluator::with_local(|e| {
let ring_size = e.parameters().rlwe_n().0;
let ct_index = index / ring_size;
let coeff_index = index % ring_size;
let mut lwe_out = C::R::zeros(e.parameters().rlwe_n().0 + 1);
sample_extract(
&mut lwe_out,
&self.data[ct_index],
e.pbs_info().modop_rlweq(),
coeff_index,
);
lwe_out
})
}
}
pub(super) struct NonInteractiveBatchedFheBools<C> {
data: Vec<C>,
}
impl<M: MatrixEntity + MatrixMut<MatElement = u64>> From<&(Vec<M::R>, [u8; 32])>
for NonInteractiveBatchedFheBools<M>
where
<M as Matrix>::R: RowMut,
{
fn from(value: &(Vec<M::R>, [u8; 32])) -> Self {
BoolEvaluator::with_local(|e| {
let parameters = e.parameters();
let ring_size = parameters.rlwe_n().0;
let rlwe_q = parameters.rlwe_q();
let mut prng = DefaultSecureRng::new_seeded(value.1);
let rlwes = value
.0
.iter()
.map(|partb| {
let mut rlwe = M::zeros(2, ring_size);
// sample A
RandomFillUniformInModulus::random_fill(
&mut prng,
rlwe_q,
rlwe.get_row_mut(0),
);
// Copy over B
rlwe.get_row_mut(1).copy_from_slice(partb.as_ref());
rlwe
})
.collect_vec();
Self { data: rlwes }
})
}
}
impl<K> Encryptor<[bool], NonInteractiveBatchedFheBools<Mat>> for K
where
K: Encryptor<[bool], (Mat, [u8; 32])>,
{
fn encrypt(&self, m: &[bool]) -> NonInteractiveBatchedFheBools<Mat> {
NonInteractiveBatchedFheBools::from(&K::encrypt(&self, m))
}
}
impl<K> Encryptor<[bool], (Mat, [u8; 32])> for K
where
K: NonInteractiveMultiPartyClientKey,
C: SeededCiphertext<<Mat as Matrix>::R, <DefaultSecureRng as NewWithSeed>::Seed>,
<Mat as Matrix>::R:
TryConvertFrom1<[K::Element], CiphertextModulus<<Mat as Matrix>::MatElement>>,
{
fn encrypt(&self, m: &[bool]) -> C {
fn encrypt(&self, m: &[bool]) -> (Mat, [u8; 32]) {
BoolEvaluator::with_local(|e| {
let parameters = e.parameters();
assert!(m.len() <= parameters.rlwe_n().0);
DefaultSecureRng::with_local_mut(|rng| {
let parameters = e.parameters();
let ring_size = parameters.rlwe_n().0;
let mut message = vec![<Mat as Matrix>::MatElement::zero(); parameters.rlwe_n().0];
m.iter().enumerate().for_each(|(i, v)| {
if *v {
message[i] = parameters.rlwe_q().true_el()
} else {
message[i] = parameters.rlwe_q().false_el()
}
});
let rlwe_count = ((m.len() as f64 / ring_size as f64).ceil())
.to_usize()
.unwrap();
DefaultSecureRng::with_local_mut(|rng| {
let mut seed = <DefaultSecureRng as NewWithSeed>::Seed::default();
rng.fill_bytes(&mut seed);
let mut prng = DefaultSecureRng::new_seeded(seed);
let mut rlwe_out =
<<Mat as Matrix>::R as RowEntity>::zeros(parameters.rlwe_n().0);
let sk_u = self.sk_u_rlwe();
// encrypt `m` into ceil(len(m)/N) RLWE ciphertexts
let rlwes = (0..rlwe_count)
.map(|index| {
let mut message = vec![<Mat as Matrix>::MatElement::zero(); ring_size];
m[(index * ring_size)..std::cmp::min(m.len(), (index + 1) * ring_size)]
.iter()
.enumerate()
.for_each(|(i, v)| {
if *v {
message[i] = parameters.rlwe_q().true_el()
} else {
message[i] = parameters.rlwe_q().false_el()
}
});
secret_key_encrypt_rlwe(
&message,
&mut rlwe_out,
&self.sk_u_rlwe(),
e.pbs_info().modop_rlweq(),
e.pbs_info().nttop_rlweq(),
&mut prng,
rng,
);
// encrypt message
let mut rlwe_out =
<<Mat as Matrix>::R as RowEntity>::zeros(parameters.rlwe_n().0);
C::new_with_seed(rlwe_out, seed)
secret_key_encrypt_rlwe(
&message,
&mut rlwe_out,
&sk_u,
e.pbs_info().modop_rlweq(),
e.pbs_info().nttop_rlweq(),
&mut prng,
rng,
);
rlwe_out
})
.collect_vec();
(rlwes, seed)
})
})
}
}
impl KeySwitchWithId<Mat> for Mat {
fn key_switch(&self, user_id: usize) -> Mat {
BoolEvaluator::with_local(|e| {
let server_key = BOOL_SERVER_KEY.get().unwrap();
let ksk = server_key.ui_to_s_ksk(user_id);
let decomposer = e.ni_ui_to_s_ks_decomposer().as_ref().unwrap();
// perform key switch
key_switch(
self,
ksk.as_ref(),
ksk.shoup_repr(),
decomposer,
e.pbs_info().nttop_rlweq(),
e.pbs_info().modop_rlweq(),
)
})
}
}
impl<C> KeySwitchWithId<BatchedFheBools<C>> for NonInteractiveBatchedFheBools<C>
where
C: KeySwitchWithId<C>,
{
fn key_switch(&self, user_id: usize) -> BatchedFheBools<C> {
let data = self
.data
.iter()
.map(|c| c.key_switch(user_id))
.collect_vec();
BatchedFheBools { data }
}
}
impl<E> MultiPartyDecryptor<bool, <Mat as Matrix>::R>
for super::super::keys::ClientKey<[u8; 32], E>
{
type DecryptionShare = <Mat as Matrix>::MatElement;
fn gen_decryption_share(&self, c: &<Mat as Matrix>::R) -> Self::DecryptionShare {
BoolEvaluator::with_local(|e| e.multi_party_decryption_share(c, self))
}
fn aggregate_decryption_shares(
&self,
c: &<Mat as Matrix>::R,
shares: &[Self::DecryptionShare],
) -> bool {
BoolEvaluator::with_local(|e| e.multi_party_decrypt(shares, c))
}
}
}
#[cfg(test)]
mod tests {
use impl_enc_dec::NonInteractiveBatchedFheBools;
use itertools::{izip, Itertools};
use num_traits::ToPrimitive;
use rand::{thread_rng, RngCore};
use crate::{
backend::Modulus,
bool::{
evaluator::{BoolEncoding, BooleanGates},
keys::SinglePartyClientKey,
},
lwe::decrypt_lwe,
rgsw::decrypt_rlwe,
utils::{Stats, TryConvertFrom1},
ArithmeticOps, Encryptor, KeySwitchWithId, ModInit, MultiPartyDecryptor, NttInit,
VectorOps,
};
use super::*;
#[test]
fn non_interactive_mp_bool_nand() {
set_parameter_set(ParameterSelector::NonInteractiveMultiPartyLessThanOrEqualTo16);
let mut seed = [0u8; 32];
thread_rng().fill_bytes(&mut seed);
set_common_reference_seed(seed);
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 mut ideal_rlwe_sk = vec![0i32; parameters.rlwe_n().0];
cks.iter().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;
});
});
let mut m0 = false;
let mut m1 = true;
let mut ct0 = {
let ct: NonInteractiveBatchedFheBools<_> = cks[0].encrypt(vec![m0].as_slice());
let ct = ct.key_switch(0);
ct.extract(0)
};
let mut ct1 = {
let ct: NonInteractiveBatchedFheBools<_> = cks[1].encrypt(vec![m1].as_slice());
let ct = ct.key_switch(1);
ct.extract(0)
};
for _ in 0..100 {
let ct_out =
BoolEvaluator::with_local_mut(|e| e.xor(&ct0, &ct1, RuntimeServerKey::global()));
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 noisy_m = decrypt_lwe(&ct_out, &ideal_rlwe_sk, &rlwe_q_modop);
let noise = if m_expected {
rlwe_q_modop.sub(&parameters.rlwe_q().true_el(), &noisy_m)
} else {
rlwe_q_modop.sub(&parameters.rlwe_q().false_el(), &noisy_m)
};
println!(
"Noise: {}",
parameters
.rlwe_q()
.map_element_to_i64(&noise)
.abs()
.to_f64()
.unwrap()
.log2()
)
}
assert!(m_out == m_expected, "Expected {m_expected} but got {m_out}");
m1 = m0;
m0 = m_out;
ct1 = ct0;
ct0 = ct_out;
}
}
#[test]
fn trialtest() {
set_parameter_set(ParameterSelector::NonInteractiveMultiPartyLessThanOrEqualTo16);
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 m = vec![false, true];
let ct: NonInteractiveBatchedFheBools<_> = cks[0].encrypt(m.as_slice());
let ct = ct.key_switch(0);
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 mut ideal_rlwe_sk = vec![0i32; parameters.rlwe_n().0];
cks.iter().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;
});
});
let message = m
.iter()
.map(|b| {
if *b {
parameters.rlwe_q().true_el()
} else {
parameters.rlwe_q().false_el()
}
})
.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_more(&Vec::<i64>::try_convert_from(
diff.as_slice(),
parameters.rlwe_q(),
));
println!("Noise: {}", stats.std_dev().abs().log2());
}
}

+ 5
- 1
src/bool/parameters.rs

@ -303,6 +303,10 @@ impl BoolParameters {
});
els
}
pub(crate) fn variant(&self) -> &ParameterVariant {
&self.variant
}
}
#[derive(Clone, Copy, PartialEq)]
@ -506,7 +510,7 @@ pub(crate) const NON_INTERACTIVE_SMALL_MP_BOOL_PARAMS: BoolParameters = Boo
lwe_q: CiphertextModulus::new_non_native(1 << 20),
br_q: 1 << 11,
rlwe_n: PolynomialSize(1 << 11),
lwe_n: LweDimension(10),
lwe_n: LweDimension(600),
lwe_decomposer_params: (DecompostionLogBase(4), DecompositionCount(5)),
rlrg_decomposer_params: (
DecompostionLogBase(11),

+ 25
- 0
src/bool/sp_api.rs

@ -0,0 +1,25 @@
mod impl_enc_dec {
use crate::{Decryptor, Encryptor};
use super::super::keys::SinglePartyClientKey;
impl<K> Encryptor<bool, Vec<u64>> for K
where
K: SinglePartyClientKey,
{
fn encrypt(&self, m: &bool) -> Vec<u64> {
todo!()
// BoolEvaluator::with_local(|e| e.sk_encrypt(*m, self))
}
}
impl<K> Decryptor<bool, Vec<u64>> for K
where
K: SinglePartyClientKey,
{
fn decrypt(&self, c: &Vec<u64>) -> bool {
todo!()
// BoolEvaluator::with_local(|e| e.sk_decrypt(c, self))
}
}
}

+ 8
- 5
src/lib.rs

@ -19,13 +19,12 @@ mod utils;
pub use backend::{
ArithmeticLazyOps, ArithmeticOps, ModInit, ModularOpsU64, ShoupMatrixFMA, VectorOps,
};
pub use bool::{
aggregate_public_key_shares, aggregate_server_key_shares, gen_client_key, gen_mp_keys_phase1,
gen_mp_keys_phase2, set_mp_seed, set_parameter_set, ParameterSelector,
};
// pub use bool::{
// aggregate_public_key_shares, aggregate_server_key_shares, gen_client_key,
// gen_mp_keys_phase1, gen_mp_keys_phase2, set_mp_seed, set_parameter_set,
// ParameterSelector, };
pub use decomposer::{Decomposer, DecomposerIter, DefaultDecomposer};
pub use ntt::{Ntt, NttBackendU64, NttInit};
pub use shortint::FheUint8;
pub trait Matrix: AsRef<[Self::R]> {
type MatElement;
@ -180,3 +179,7 @@ pub trait MultiPartyDecryptor {
fn gen_decryption_share(&self, c: &C) -> Self::DecryptionShare;
fn aggregate_decryption_shares(&self, c: &C, shares: &[Self::DecryptionShare]) -> M;
}
pub trait KeySwitchWithId<C> {
fn key_switch(&self, user_id: usize) -> C;
}

+ 1
- 0
src/pbs.rs

@ -392,6 +392,7 @@ pub(crate) fn sample_extract
M::MatElement: Copy,
{
let ring_size = rlwe_in.dimension().1;
assert!(ring_size + 1 == lwe_out.as_ref().len());
// index..=0
let to = &mut lwe_out.as_mut()[1..];

+ 2
- 1
src/rgsw/keygen.rs

@ -17,8 +17,9 @@ use crate::{
DefaultSecureRng, NewWithSeed, RandomElementInModulus, RandomFill,
RandomFillGaussianInModulus, RandomFillUniformInModulus,
},
rgsw::decompose_r,
utils::{fill_random_ternary_secret_with_hamming_weight, TryConvertFrom1, WithLocal},
Matrix, MatrixEntity, MatrixMut, Row, RowEntity, RowMut, Secret,
Matrix, MatrixEntity, MatrixMut, Row, RowEntity, RowMut, Secret, ShoupMatrixFMA,
};
pub(crate) fn generate_auto_map(ring_size: usize, k: isize) -> (Vec<usize>, Vec<bool>) {

+ 2
- 2
src/rgsw/mod.rs

@ -1061,8 +1061,8 @@ pub(crate) mod tests {
let ring_size = 1 << 11;
let q = generate_prime(logq, ring_size, 1u64 << logq).unwrap();
let p = 1u64 << logp;
let d_rgsw = 5;
let logb = 12;
let d_rgsw = 12;
let logb = 5;
let s = RlweSecret::random((ring_size >> 1) as usize, ring_size as usize);

+ 64
- 0
src/rgsw/runtime.rs

@ -647,3 +647,67 @@ pub(crate) fn rgsw_by_rgsw_inplace<
.iter_rows_mut()
.for_each(|ri| ntt_op.backward(ri.as_mut()));
}
pub(crate) fn key_switch<
M: MatrixMut + MatrixEntity,
ModOp: GetModulus<Element = M::MatElement> + ShoupMatrixFMA<M::R> + VectorOps<Element = M::MatElement>,
NttOp: Ntt<Element = M::MatElement>,
D: Decomposer<Element = M::MatElement>,
>(
rlwe_in: &M,
ksk: &M,
ksk_shoup: &M,
decomposer: &D,
ntt_op: &NttOp,
mod_op: &ModOp,
) -> M
where
<M as Matrix>::R: RowMut + RowEntity,
M::MatElement: Copy,
{
let ring_size = rlwe_in.dimension().1;
assert!(rlwe_in.dimension().0 == 2);
assert!(ksk.dimension() == (decomposer.decomposition_count() * 2, ring_size));
let mut rlwe_out = M::zeros(2, ring_size);
let mut tmp = M::zeros(decomposer.decomposition_count(), ring_size);
let mut tmp_row = M::R::zeros(ring_size);
// key switch RLWE part -A
// negative A
tmp_row.as_mut().copy_from_slice(rlwe_in.get_row_slice(0));
mod_op.elwise_neg_mut(tmp_row.as_mut());
// decompose -A and send to evaluation domain
decompose_r(tmp_row.as_ref(), tmp.as_mut(), decomposer);
tmp.iter_rows_mut()
.for_each(|r| ntt_op.forward_lazy(r.as_mut()));
// RLWE_s(-A u) = B' + B, A' = (decomp(-A) * Ksk(u -> s)) + (B, 0)
let (ksk_part_a, ksk_part_b) = ksk.split_at_row(decomposer.decomposition_count());
let (ksk_part_a_shoup, ksk_part_b_shoup) =
ksk_shoup.split_at_row(decomposer.decomposition_count());
// Part A'
mod_op.shoup_matrix_fma(
rlwe_out.get_row_mut(0),
&ksk_part_a,
&ksk_part_a_shoup,
tmp.as_ref(),
);
// Part B'
mod_op.shoup_matrix_fma(
rlwe_out.get_row_mut(1),
&ksk_part_b,
&ksk_part_b_shoup,
tmp.as_ref(),
);
// back to coefficient domain
rlwe_out
.iter_rows_mut()
.for_each(|r| ntt_op.backward(r.as_mut()));
// B' + B
mod_op.elwise_add_mut(rlwe_out.get_row_mut(1), rlwe_in.get_row_slice(1));
rlwe_out
}

+ 196
- 0
src/shortint/enc_dec.rs

@ -0,0 +1,196 @@
use itertools::Itertools;
use crate::{
bool::BoolEvaluator,
random::{DefaultSecureRng, RandomFillUniformInModulus},
utils::{TryConvertFrom1, WithLocal},
Decryptor, Encryptor, KeySwitchWithId, Matrix, MatrixEntity, MatrixMut, MultiPartyDecryptor,
RowMut,
};
#[derive(Clone)]
pub struct FheUint8<C> {
pub(super) data: Vec<C>,
}
impl<C> FheUint8<C> {
pub(super) fn data(&self) -> &[C] {
&self.data
}
pub(super) fn data_mut(&mut self) -> &mut [C] {
&mut self.data
}
}
pub struct BatchedFheUint8<C> {
data: Vec<C>,
}
impl<M: MatrixEntity + MatrixMut<MatElement = u64>> From<&SeededBatchedFheUint8<M::R, [u8; 32]>>
for BatchedFheUint8<M>
where
<M as Matrix>::R: RowMut,
{
fn from(value: &SeededBatchedFheUint8<M::R, [u8; 32]>) -> Self {
BoolEvaluator::with_local(|e| {
let parameters = e.parameters();
let ring_size = parameters.rlwe_n().0;
let rlwe_q = parameters.rlwe_q();
let mut prng = DefaultSecureRng::new_seeded(value.seed);
let rlwes = value
.data
.iter()
.map(|partb| {
let mut rlwe = M::zeros(2, ring_size);
// sample A
RandomFillUniformInModulus::random_fill(&mut prng, rlwe_q, rlwe.get_row_mut(0));
// Copy over B
rlwe.get_row_mut(1).copy_from_slice(partb.as_ref());
rlwe
})
.collect_vec();
Self { data: rlwes }
})
}
}
pub struct SeededBatchedFheUint8<C, S> {
data: Vec<C>,
seed: S,
}
impl<C, S> SeededBatchedFheUint8<C, S> {
pub fn unseed<M>(&self) -> BatchedFheUint8<M>
where
BatchedFheUint8<M>: for<'a> From<&'a SeededBatchedFheUint8<C, S>>,
M: Matrix<R = C>,
{
BatchedFheUint8::from(self)
}
}
impl<K, C, S> Encryptor<[u8], SeededBatchedFheUint8<C, S>> for K
where
K: Encryptor<[bool], (Vec<C>, S)>,
{
fn encrypt(&self, m: &[u8]) -> SeededBatchedFheUint8<C, S> {
// convert vector of u8s to vector bools
let m = m
.iter()
.flat_map(|v| (0..8).into_iter().map(|i| (((*v) >> i) & 1) == 1))
.collect_vec();
let (cts, seed) = K::encrypt(&self, &m);
dbg!(cts.len());
SeededBatchedFheUint8 { data: cts, seed }
}
}
impl<K, C> Encryptor<[u8], BatchedFheUint8<C>> for K
where
K: Encryptor<[bool], Vec<C>>,
{
fn encrypt(&self, m: &[u8]) -> BatchedFheUint8<C> {
let m = m
.iter()
.flat_map(|v| {
(0..8)
.into_iter()
.map(|i| ((*v >> i) & 1) == 1)
.collect_vec()
})
.collect_vec();
let cts = K::encrypt(&self, &m);
BatchedFheUint8 { data: cts }
}
}
impl<C> KeySwitchWithId<BatchedFheUint8<C>> for BatchedFheUint8<C>
where
C: KeySwitchWithId<C>,
{
fn key_switch(&self, user_id: usize) -> BatchedFheUint8<C> {
let data = self
.data
.iter()
.map(|c| c.key_switch(user_id))
.collect_vec();
BatchedFheUint8 { data }
}
}
impl<C, K> MultiPartyDecryptor<u8, FheUint8<C>> for K
where
K: MultiPartyDecryptor<bool, C>,
<Self as MultiPartyDecryptor<bool, C>>::DecryptionShare: Clone,
{
type DecryptionShare = Vec<<Self as MultiPartyDecryptor<bool, C>>::DecryptionShare>;
fn gen_decryption_share(&self, c: &FheUint8<C>) -> Self::DecryptionShare {
assert!(c.data().len() == 8);
c.data()
.iter()
.map(|bit_c| {
let decryption_share =
MultiPartyDecryptor::<bool, C>::gen_decryption_share(self, bit_c);
decryption_share
})
.collect_vec()
}
fn aggregate_decryption_shares(&self, c: &FheUint8<C>, shares: &[Self::DecryptionShare]) -> u8 {
let mut out = 0u8;
(0..8).into_iter().for_each(|i| {
// Collect bit i^th decryption share of each party
let bit_i_decryption_shares = shares.iter().map(|s| s[i].clone()).collect_vec();
let bit_i = MultiPartyDecryptor::<bool, C>::aggregate_decryption_shares(
self,
&c.data()[i],
&bit_i_decryption_shares,
);
if bit_i {
out += 1 << i;
}
});
out
}
}
impl<C, K> Encryptor<u8, FheUint8<C>> for K
where
K: Encryptor<bool, C>,
{
fn encrypt(&self, m: &u8) -> FheUint8<C> {
let cts = (0..8)
.into_iter()
.map(|i| {
let bit = ((m >> i) & 1) == 1;
K::encrypt(self, &bit)
})
.collect_vec();
FheUint8 { data: cts }
}
}
impl<K, C> Decryptor<u8, FheUint8<C>> for K
where
K: Decryptor<bool, C>,
{
fn decrypt(&self, c: &FheUint8<C>) -> u8 {
assert!(c.data.len() == 8);
let mut out = 0u8;
c.data().iter().enumerate().for_each(|(index, bit_c)| {
let bool = K::decrypt(self, bit_c);
if bool {
out += 1 << index;
}
});
out
}
}

+ 160
- 299
src/shortint/mod.rs

@ -1,110 +1,30 @@
use itertools::Itertools;
use crate::{
bool::{ClientKey, PublicKey},
Decryptor, Encryptor, MultiPartyDecryptor,
bool::{parameters::CiphertextModulus, ClientKey, PublicKey},
random::{DefaultSecureRng, NewWithSeed, RandomFillUniformInModulus},
utils::{TryConvertFrom1, WithLocal},
Decryptor, Encryptor, Matrix, MatrixEntity, MatrixMut, MultiPartyDecryptor, Row, RowMut,
};
mod enc_dec;
mod ops;
mod types;
pub type FheUint8 = types::FheUint8<Vec<u64>>;
impl Encryptor<u8, FheUint8> for ClientKey {
fn encrypt(&self, m: &u8) -> FheUint8 {
let cts = (0..8)
.into_iter()
.map(|i| {
let bit = ((m >> i) & 1) == 1;
Encryptor::<bool, Vec<u64>>::encrypt(self, &bit)
})
.collect_vec();
FheUint8 { data: cts }
}
}
impl Decryptor<u8, FheUint8> for ClientKey {
fn decrypt(&self, c: &FheUint8) -> u8 {
assert!(c.data.len() == 8);
let mut out = 0u8;
c.data().iter().enumerate().for_each(|(index, bit_c)| {
let bool = Decryptor::<bool, Vec<u64>>::decrypt(self, bit_c);
if bool {
out += 1 << index;
}
});
out
}
}
impl<M, R, Mo> Encryptor<u8, FheUint8> for PublicKey<M, R, Mo>
where
PublicKey<M, R, Mo>: Encryptor<bool, Vec<u64>>,
{
fn encrypt(&self, m: &u8) -> FheUint8 {
let cts = (0..8)
.into_iter()
.map(|i| {
let bit = ((m >> i) & 1) == 1;
Encryptor::<bool, Vec<u64>>::encrypt(self, &bit)
})
.collect_vec();
FheUint8 { data: cts }
}
}
impl MultiPartyDecryptor<u8, FheUint8> for ClientKey
where
ClientKey: MultiPartyDecryptor<bool, Vec<u64>>,
{
type DecryptionShare = Vec<<Self as MultiPartyDecryptor<bool, Vec<u64>>>::DecryptionShare>;
fn gen_decryption_share(&self, c: &FheUint8) -> Self::DecryptionShare {
assert!(c.data().len() == 8);
c.data()
.iter()
.map(|bit_c| {
let decryption_share =
MultiPartyDecryptor::<bool, Vec<u64>>::gen_decryption_share(self, bit_c);
decryption_share
})
.collect_vec()
}
fn aggregate_decryption_shares(&self, c: &FheUint8, shares: &[Self::DecryptionShare]) -> u8 {
let mut out = 0u8;
(0..8).into_iter().for_each(|i| {
// Collect bit i^th decryption share of each party
let bit_i_decryption_shares = shares.iter().map(|s| s[i]).collect_vec();
let bit_i = MultiPartyDecryptor::<bool, Vec<u64>>::aggregate_decryption_shares(
self,
&c.data()[i],
&bit_i_decryption_shares,
);
if bit_i {
out += 1 << i;
}
});
out
}
}
pub type FheUint8 = enc_dec::FheUint8<Vec<u64>>;
pub type FheBool = Vec<u64>;
mod frontend {
use super::ops::{
arbitrary_bit_adder, arbitrary_bit_division_for_quotient_and_rem, arbitrary_bit_subtractor,
eight_bit_mul,
};
use crate::{
bool::evaluator::{BoolEvaluator, BooleanGates},
utils::{Global, WithLocal},
};
use crate::utils::{Global, WithLocal};
use super::FheUint8;
use super::*;
mod arithetic {
use crate::bool::{FheBool, RuntimeServerKey};
use crate::bool::{evaluator::BooleanGates, BoolEvaluator, RuntimeServerKey};
use super::*;
use std::ops::{Add, AddAssign, Div, Mul, Rem, Sub};
@ -230,7 +150,7 @@ mod frontend {
mod booleans {
use crate::{
bool::{evaluator::BooleanGates, FheBool, RuntimeServerKey},
bool::{evaluator::BooleanGates, BoolEvaluator, RuntimeServerKey},
shortint::ops::{
arbitrary_bit_comparator, arbitrary_bit_equality, arbitrary_signed_bit_comparator,
},
@ -303,214 +223,155 @@ mod tests {
use num_traits::Euclid;
use crate::{
bool::{
aggregate_public_key_shares, aggregate_server_key_shares, gen_client_key, gen_keys,
gen_mp_keys_phase1, gen_mp_keys_phase2, set_mp_seed, set_parameter_set,
},
shortint::types::FheUint8,
Decryptor, Encryptor, MultiPartyDecryptor,
bool::set_parameter_set, shortint::enc_dec::FheUint8, Decryptor, Encryptor,
MultiPartyDecryptor,
};
#[test]
fn all_uint8_apis() {
set_parameter_set(crate::ParameterSelector::MultiPartyLessThanOrEqualTo16);
let (ck, sk) = gen_keys();
sk.set_server_key();
for i in 144..=255 {
for j in 100..=255 {
let m0 = i;
let m1 = j;
let c0 = ck.encrypt(&m0);
let c1 = ck.encrypt(&m1);
assert!(ck.decrypt(&c0) == m0);
assert!(ck.decrypt(&c1) == m1);
// Arithmetic
{
{
// Add
let mut c_m0_plus_m1 = FheUint8 {
data: c0.data().to_vec(),
};
c_m0_plus_m1 += &c1;
let m0_plus_m1 = ck.decrypt(&c_m0_plus_m1);
assert_eq!(
m0_plus_m1,
m0.wrapping_add(m1),
"Expected {} but got {m0_plus_m1} for {i}+{j}",
m0.wrapping_add(m1)
);
}
{
// Sub
let c_sub = &c0 - &c1;
let m0_sub_m1 = ck.decrypt(&c_sub);
assert_eq!(
m0_sub_m1,
m0.wrapping_sub(m1),
"Expected {} but got {m0_sub_m1} for {i}-{j}",
m0.wrapping_sub(m1)
);
}
{
// Mul
let c_m0m1 = &c0 * &c1;
let m0m1 = ck.decrypt(&c_m0m1);
assert_eq!(
m0m1,
m0.wrapping_mul(m1),
"Expected {} but got {m0m1} for {i}x{j}",
m0.wrapping_mul(m1)
);
}
// Div & Rem
{
let (c_quotient, c_rem) = c0.div_rem(&c1);
let m_quotient = ck.decrypt(&c_quotient);
let m_remainder = ck.decrypt(&c_rem);
if j != 0 {
let (q, r) = i.div_rem_euclid(&j);
assert_eq!(
m_quotient, q,
"Expected {} but got {m_quotient} for {i}/{j}",
q
);
assert_eq!(
m_remainder, r,
"Expected {} but got {m_quotient} for {i}%{j}",
r
);
} else {
assert_eq!(
m_quotient, 255,
"Expected 255 but got {m_quotient}. Case div by zero"
);
assert_eq!(
m_remainder, i,
"Expected {i} but got {m_quotient}. Case div by zero"
)
}
}
}
// Comparisons
{
{
let c_eq = c0.eq(&c1);
let is_eq = ck.decrypt(&c_eq);
assert_eq!(
is_eq,
i == j,
"Expected {} but got {is_eq} for {i}=={j}",
i == j
);
}
{
let c_gt = c0.gt(&c1);
let is_gt = ck.decrypt(&c_gt);
assert_eq!(
is_gt,
i > j,
"Expected {} but got {is_gt} for {i}>{j}",
i > j
);
}
{
let c_lt = c0.lt(&c1);
let is_lt = ck.decrypt(&c_lt);
assert_eq!(
is_lt,
i < j,
"Expected {} but got {is_lt} for {i}<{j}",
i < j
);
}
{
let c_ge = c0.ge(&c1);
let is_ge = ck.decrypt(&c_ge);
assert_eq!(
is_ge,
i >= j,
"Expected {} but got {is_ge} for {i}>={j}",
i >= j
);
}
{
let c_le = c0.le(&c1);
let is_le = ck.decrypt(&c_le);
assert_eq!(
is_le,
i <= j,
"Expected {} but got {is_le} for {i}<={j}",
i <= j
);
}
}
}
}
}
#[test]
fn fheuint8_test_multi_party() {
set_parameter_set(crate::ParameterSelector::MultiPartyLessThanOrEqualTo16);
set_mp_seed([0; 32]);
let parties = 8;
// client keys and public key share
let cks = (0..parties)
.into_iter()
.map(|i| gen_client_key())
.collect_vec();
// round 1: generate pulic key shares
let pk_shares = cks.iter().map(|key| gen_mp_keys_phase1(key)).collect_vec();
let public_key = aggregate_public_key_shares(&pk_shares);
// round 2: generate server key shares
let server_key_shares = cks
.iter()
.map(|key| gen_mp_keys_phase2(key, &public_key))
.collect_vec();
// server aggregates the server key
let server_key = aggregate_server_key_shares(&server_key_shares);
server_key.set_server_key();
// Clients use Pk to encrypt private inputs
let a = 8u8;
let b = 10u8;
let c = 155u8;
let ct_a = public_key.encrypt(&a);
let ct_b = public_key.encrypt(&b);
let ct_c = public_key.encrypt(&c);
let now = std::time::Instant::now();
// server computes
// a*b + c
let mut ct_ab = &ct_a * &ct_b;
ct_ab += &ct_c;
println!("Circuit time: {:?}", now.elapsed());
// decrypt ab and check
// generate decryption shares
let decryption_shares = cks
.iter()
.map(|k| k.gen_decryption_share(&ct_ab))
.collect_vec();
// aggregate and decryption ab
let ab_add_c = cks[0].aggregate_decryption_shares(&ct_ab, &decryption_shares);
assert!(ab_add_c == (a.wrapping_mul(b)).wrapping_add(c));
}
// #[test]
// fn all_uint8_apis() {
// set_parameter_set(crate::ParameterSelector::MultiPartyLessThanOrEqualTo16);
// let (ck, sk) = gen_keys();
// sk.set_server_key();
// for i in 144..=255 {
// for j in 100..=255 {
// let m0 = i;
// let m1 = j;
// let c0 = ck.encrypt(&m0);
// let c1 = ck.encrypt(&m1);
// assert!(ck.decrypt(&c0) == m0);
// assert!(ck.decrypt(&c1) == m1);
// // Arithmetic
// {
// {
// // Add
// let mut c_m0_plus_m1 = FheUint8 {
// data: c0.data().to_vec(),
// };
// c_m0_plus_m1 += &c1;
// let m0_plus_m1 = ck.decrypt(&c_m0_plus_m1);
// assert_eq!(
// m0_plus_m1,
// m0.wrapping_add(m1),
// "Expected {} but got {m0_plus_m1} for {i}+{j}",
// m0.wrapping_add(m1)
// );
// }
// {
// // Sub
// let c_sub = &c0 - &c1;
// let m0_sub_m1 = ck.decrypt(&c_sub);
// assert_eq!(
// m0_sub_m1,
// m0.wrapping_sub(m1),
// "Expected {} but got {m0_sub_m1} for {i}-{j}",
// m0.wrapping_sub(m1)
// );
// }
// {
// // Mul
// let c_m0m1 = &c0 * &c1;
// let m0m1 = ck.decrypt(&c_m0m1);
// assert_eq!(
// m0m1,
// m0.wrapping_mul(m1),
// "Expected {} but got {m0m1} for {i}x{j}",
// m0.wrapping_mul(m1)
// );
// }
// // Div & Rem
// {
// let (c_quotient, c_rem) = c0.div_rem(&c1);
// let m_quotient = ck.decrypt(&c_quotient);
// let m_remainder = ck.decrypt(&c_rem);
// if j != 0 {
// let (q, r) = i.div_rem_euclid(&j);
// assert_eq!(
// m_quotient, q,
// "Expected {} but got {m_quotient} for
// {i}/{j}", q
// );
// assert_eq!(
// m_remainder, r,
// "Expected {} but got {m_quotient} for
// {i}%{j}", r
// );
// } else {
// assert_eq!(
// m_quotient, 255,
// "Expected 255 but got {m_quotient}. Case div
// by zero" );
// assert_eq!(
// m_remainder, i,
// "Expected {i} but got {m_quotient}. Case div
// by zero" )
// }
// }
// }
// // Comparisons
// {
// {
// let c_eq = c0.eq(&c1);
// let is_eq = ck.decrypt(&c_eq);
// assert_eq!(
// is_eq,
// i == j,
// "Expected {} but got {is_eq} for {i}=={j}",
// i == j
// );
// }
// {
// let c_gt = c0.gt(&c1);
// let is_gt = ck.decrypt(&c_gt);
// assert_eq!(
// is_gt,
// i > j,
// "Expected {} but got {is_gt} for {i}>{j}",
// i > j
// );
// }
// {
// let c_lt = c0.lt(&c1);
// let is_lt = ck.decrypt(&c_lt);
// assert_eq!(
// is_lt,
// i < j,
// "Expected {} but got {is_lt} for {i}<{j}",
// i < j
// );
// }
// {
// let c_ge = c0.ge(&c1);
// let is_ge = ck.decrypt(&c_ge);
// assert_eq!(
// is_ge,
// i >= j,
// "Expected {} but got {is_ge} for {i}>={j}",
// i >= j
// );
// }
// {
// let c_le = c0.le(&c1);
// let is_le = ck.decrypt(&c_le);
// assert_eq!(
// is_le,
// i <= j,
// "Expected {} but got {is_le} for {i}<={j}",
// i <= j
// );
// }
// }
// }
// }
// }
}

+ 0
- 14
src/shortint/types.rs

@ -1,14 +0,0 @@
#[derive(Clone)]
pub struct FheUint8<C> {
pub(super) data: Vec<C>,
}
impl<C> FheUint8<C> {
pub(super) fn data(&self) -> &[C] {
&self.data
}
pub(super) fn data_mut(&mut self) -> &mut [C] {
&mut self.data
}
}

Loading…
Cancel
Save