use core::f64;
use std::cell::RefCell;
use std::cmp::min;
use std::ops::Range;
use feanor_math::homomorphism::CanHomFrom;
use feanor_math::homomorphism::Homomorphism;
use feanor_math::integer::BigIntRingBase;
use feanor_math::primitive_int::*;
use feanor_math::group::*;
use feanor_math::ring::*;
use feanor_math::algorithms::matmul::ComputeInnerProduct;
use crate::bgv::noise_estimator::*;
use crate::boo::Boo;
use crate::circuit::evaluator::DefaultCircuitEvaluator;
use crate::circuit::*;
use crate::number_ring::galois::*;
use crate::gadget_product::digits::*;
use crate::number_ring::galois::CyclotomicGaloisGroupOps;
use crate::ZZi64;
use super::noise_estimator::AlwaysZeroNoiseEstimator;
use super::*;
pub fn level_digits(a: &[usize], b: &[usize], k: usize) -> Option<(Vec<usize>, Vec<usize>)> {
let len = a.len();
assert!(len > 0);
assert_eq!(len, b.len());
assert!(a.iter().zip(b.iter()).all(|(a, b)| b <= a));
assert!(b.iter().sum::<usize>() >= k);
(0..=*a.iter().max().unwrap()).filter_map(|max_result| {
let mut c = (0..len).map(|_| 0).collect::<Vec<_>>();
let mut current_sum_c = 0;
for i in 0..len {
let to_remove = b[i].saturating_sub(max_result);
if to_remove + current_sum_c > k {
return None;
}
c[i] += to_remove;
current_sum_c += to_remove;
}
while current_sum_c < k {
let entry_to_decrease = (0..len).filter(|i| b[*i] - c[*i] != 0).min_by_key(|i| b[*i] - c[*i]).unwrap();
let decrease_by = min(b[entry_to_decrease] - c[entry_to_decrease], k - current_sum_c);
c[entry_to_decrease] += decrease_by;
current_sum_c += decrease_by;
}
return Some((max_result, c));
}).filter_map(|(max_result, c)| {
let mut d = (0..len).map(|_| 0).collect::<Vec<_>>();
let mut current_sum_d = 0;
for i in 0..len {
if b[i] - c[i] == 0 {
continue;
}
let max_d = min(a[i] + c[i] - b[i], max_result + c[i] - b[i]);
d[i] = max_d;
current_sum_d += max_d;
if current_sum_d >= max_result {
return Some((c, d));
}
}
while current_sum_d < max_result {
let i = (0..len).max_by_key(|i| min(a[*i] + c[*i] - b[*i] - d[*i], max_result + c[*i] - b[*i] - d[*i])).unwrap();
let add_d = min(a[i] + c[i] - b[i] - d[i], max_result + c[i] - b[i] - d[i]);
if add_d == 0 {
return None;
}
d[i] += add_d;
current_sum_d += add_d;
}
return Some((c, d));
}).min_by_key(|(c, d)| (0..len).filter(|i| b[*i] + d[*i] - c[*i] != 0).count())
}
pub struct ModulusAwareCiphertext<Params: BGVInstantiation, Strategy: ?Sized + BGVModswitchStrategy<Params>> {
pub data: Ciphertext<Params>,
pub dropped_rns_factor_indices: Box<RNSFactorIndexList>,
pub info: Strategy::CiphertextInfo,
pub sk: SecretKeyDistribution
}
pub trait BGVModswitchStrategy<Params: BGVInstantiation> {
type CiphertextInfo;
fn evaluate_circuit<R>(
&self,
circuit: &PlaintextCircuit<R::Type>,
ring: R,
P: &PlaintextRing<Params>,
C_master: &CiphertextRing<Params>,
inputs: &[ModulusAwareCiphertext<Params, Self>],
rk: Option<&RelinKey<Params>>,
gks: &[(GaloisGroupEl, KeySwitchKey<Params>)],
key_switches: &mut usize,
debug_sk: Option<&SecretKey<Params>>
) -> Vec<ModulusAwareCiphertext<Params, Self>>
where R: RingStore,
R::Type: AsBGVPlaintext<Params>;
fn info_for_fresh_encryption(&self, P: &PlaintextRing<Params>, C: &CiphertextRing<Params>, sk: SecretKeyDistribution) -> Self::CiphertextInfo;
fn clone_info(&self, info: &Self::CiphertextInfo) -> Self::CiphertextInfo;
fn print_info(&self, P: &PlaintextRing<Params>, C_master: &CiphertextRing<Params>, ct: &ModulusAwareCiphertext<Params, Self>);
fn clone_ct(&self, P: &PlaintextRing<Params>, C_master: &CiphertextRing<Params>, ct: &ModulusAwareCiphertext<Params, Self>) -> ModulusAwareCiphertext<Params, Self> {
let C = Params::mod_switch_down_C(C_master, &ct.dropped_rns_factor_indices);
ModulusAwareCiphertext {
data: Params::clone_ct(P, &C, &ct.data),
info: self.clone_info(&ct.info),
dropped_rns_factor_indices: ct.dropped_rns_factor_indices.clone(),
sk: ct.sk
}
}
}
pub trait AsBGVPlaintext<Params: BGVInstantiation>: RingBase + CanHomFrom<BigIntRingBase> {
fn hom_add_to(
&self,
P: &PlaintextRing<Params>,
C: &CiphertextRing<Params>,
dropped_factors: &RNSFactorIndexList,
m: &Self::Element,
ct: Ciphertext<Params>
) -> Ciphertext<Params>;
fn hom_add_to_noise<N: BGVNoiseEstimator<Params>>(
&self,
estimator: &N,
P: &PlaintextRing<Params>,
C: &CiphertextRing<Params>,
dropped_factors: &RNSFactorIndexList,
m: &Self::Element,
ct_info: &N::CiphertextDescriptor,
implicit_scale: &El<PlaintextZnRing<Params>>
) -> N::CiphertextDescriptor;
fn hom_mul_to(
&self,
P: &PlaintextRing<Params>,
C: &CiphertextRing<Params>,
dropped_factors: &RNSFactorIndexList,
m: &Self::Element,
ct: Ciphertext<Params>
) -> Ciphertext<Params>;
fn hom_mul_to_noise<N: BGVNoiseEstimator<Params>>(
&self,
estimator: &N,
P: &PlaintextRing<Params>,
C: &CiphertextRing<Params>,
dropped_factors: &RNSFactorIndexList,
m: &Self::Element,
ct_info: &N::CiphertextDescriptor,
implicit_scale: &El<PlaintextZnRing<Params>>
) -> N::CiphertextDescriptor;
fn hom_inner_product<I>(
&self,
P: &PlaintextRing<Params>,
C: &CiphertextRing<Params>,
dropped_factors: &RNSFactorIndexList,
data: I
) -> Ciphertext<Params>
where I: Iterator<Item = (Self::Element, Ciphertext<Params>)>
{
data.fold(Params::transparent_zero(P, C), |current, (lhs, rhs)| {
Params::hom_add(P, C, current, self.hom_mul_to(P, C, dropped_factors, &lhs, rhs))
})
}
fn hom_inner_product_ref<'a, I>(
&self,
P: &PlaintextRing<Params>,
C: &CiphertextRing<Params>,
dropped_factors: &RNSFactorIndexList,
data: I
) -> Ciphertext<Params>
where I: Iterator<Item = (&'a Self::Element, &'a Ciphertext<Params>)>,
Params: 'a,
Self: 'a
{
self.hom_inner_product(P, C, dropped_factors, data.map(|(lhs, rhs)| (self.clone_el(lhs), Params::clone_ct(P, C, rhs))))
}
fn hom_inner_product_noise<'a, 'b, N: BGVNoiseEstimator<Params>, I>(
&self,
estimator: &N,
P: &PlaintextRing<Params>,
C: &CiphertextRing<Params>,
dropped_factors: &RNSFactorIndexList,
data: I
) -> N::CiphertextDescriptor
where I: Iterator<Item = (&'a Self::Element, &'b N::CiphertextDescriptor)>,
Self: 'a,
N::CiphertextDescriptor: 'b
{
data.fold(estimator.transparent_zero(), |current, (lhs, rhs)| estimator.hom_add(
P,
C,
¤t,
&P.base_ring().one(),
&self.hom_mul_to_noise(estimator, P, C, dropped_factors, lhs, &rhs, &P.base_ring().one()),
&P.base_ring().one()
))
}
fn apply_galois_action_plain(
&self,
P: &PlaintextRing<Params>,
x: &Self::Element,
gs: &[GaloisGroupEl]
) -> Vec<Self::Element>;
}
pub fn drop_rns_factors_balanced(key_digits: &RNSGadgetVectorDigitIndices, drop_prime_count: usize) -> Box<RNSFactorIndexList> {
assert!(drop_prime_count < key_digits.rns_base_len());
let mut drop_from_digit = (0..key_digits.len()).map(|_| 0).collect::<Vec<_>>();
let effective_len = |range: Range<usize>| range.end - range.start;
for _ in 0..drop_prime_count {
let largest_digit_idx = (0..key_digits.len()).max_by_key(|i| effective_len(key_digits.at(*i)) - drop_from_digit[*i]).unwrap();
drop_from_digit[largest_digit_idx] += 1;
}
let result = RNSFactorIndexList::from((0..key_digits.len()).flat_map(|i| key_digits.at(i).start..(key_digits.at(i).start + drop_from_digit[i])), key_digits.rns_base_len());
return result;
}
pub struct DefaultModswitchStrategy<Params: BGVInstantiation, N: BGVNoiseEstimator<Params>, const LOG: bool> {
params: PhantomData<Params>,
noise_estimator: N
}
impl<Params: BGVInstantiation> DefaultModswitchStrategy<Params, AlwaysZeroNoiseEstimator, false> {
pub fn never_modswitch() -> Self {
Self {
params: PhantomData,
noise_estimator: AlwaysZeroNoiseEstimator
}
}
}
enum PlainOrCiphertext<'a, Params: BGVInstantiation, Strategy: BGVModswitchStrategy<Params>, R: ?Sized + RingBase> {
Plaintext(Coefficient<R>),
PlaintextRef(&'a Coefficient<R>),
CiphertextRef(&'a ModulusAwareCiphertext<Params, Strategy>),
Ciphertext(ModulusAwareCiphertext<Params, Strategy>)
}
impl<'a, Params: BGVInstantiation, Strategy: BGVModswitchStrategy<Params>, R: ?Sized + RingBase> PlainOrCiphertext<'a, Params, Strategy, R> {
fn as_ciphertext_ref<'b>(&'b self) -> Result<&'b ModulusAwareCiphertext<Params, Strategy>, &'b Coefficient<R>> {
match self {
PlainOrCiphertext::Plaintext(x) => Err(x),
PlainOrCiphertext::PlaintextRef(x) => Err(x),
PlainOrCiphertext::Ciphertext(x) => Ok(x),
PlainOrCiphertext::CiphertextRef(x) => Ok(x)
}
}
fn as_ciphertext<S: RingStore<Type = R>>(self, P: &PlaintextRing<Params>, C_master: &CiphertextRing<Params>, ring: S, strategy: &Strategy) -> Result<(CiphertextRing<Params>, ModulusAwareCiphertext<Params, Strategy>), Coefficient<R>> {
match self {
PlainOrCiphertext::Plaintext(x) => Err(x),
PlainOrCiphertext::PlaintextRef(x) => Err(x.clone(ring)),
PlainOrCiphertext::CiphertextRef(x) => {
let Cx = Params::mod_switch_down_C(C_master, &x.dropped_rns_factor_indices);
let x = ModulusAwareCiphertext {
data: Params::clone_ct(P, &Cx, &x.data),
dropped_rns_factor_indices: x.dropped_rns_factor_indices.clone(),
info: strategy.clone_info(&x.info),
sk: x.sk
};
Ok((Cx, x))
},
PlainOrCiphertext::Ciphertext(x) => {
let Cx = Params::mod_switch_down_C(C_master, &x.dropped_rns_factor_indices);
Ok((Cx, x))
}
}
}
}
impl<Params: BGVInstantiation> AsBGVPlaintext<Params> for StaticRingBase<i64> {
fn hom_add_to(
&self,
P: &PlaintextRing<Params>,
C: &CiphertextRing<Params>,
_dropped_factors: &RNSFactorIndexList,
m: &Self::Element,
ct: Ciphertext<Params>
) -> Ciphertext<Params> {
Params::hom_add_plain_encoded(P, C, &C.inclusion().compose(C.base_ring().can_hom(&ZZi64).unwrap()).map(*m), ct)
}
fn hom_add_to_noise<N: BGVNoiseEstimator<Params>>(
&self,
estimator: &N,
P: &PlaintextRing<Params>,
C: &CiphertextRing<Params>,
_dropped_factors: &RNSFactorIndexList,
m: &Self::Element,
ct_info: &N::CiphertextDescriptor,
implicit_scale: &El<PlaintextZnRing<Params>>
) -> N::CiphertextDescriptor {
estimator.hom_add_plain_encoded(P, C, &C.inclusion().compose(C.base_ring().can_hom(&ZZi64).unwrap()).map(*m), ct_info, implicit_scale)
}
fn hom_mul_to(
&self,
P: &PlaintextRing<Params>,
C: &CiphertextRing<Params>,
_dropped_factors: &RNSFactorIndexList,
m: &Self::Element,
ct: Ciphertext<Params>
) -> Ciphertext<Params> {
Params::hom_mul_plain_int(P, C, &int_cast(*m, ZZbig, ZZi64), ct)
}
fn hom_mul_to_noise<N: BGVNoiseEstimator<Params>>(
&self,
estimator: &N,
P: &PlaintextRing<Params>,
C: &CiphertextRing<Params>,
_dropped_factors: &RNSFactorIndexList,
m: &Self::Element,
ct_info: &N::CiphertextDescriptor,
implicit_scale: &El<PlaintextZnRing<Params>>
) -> N::CiphertextDescriptor {
estimator.hom_mul_plain_int(P, C, &int_cast(*m, ZZbig, ZZi64), ct_info, implicit_scale)
}
fn apply_galois_action_plain(
&self,
_P: &PlaintextRing<Params>,
x: &Self::Element,
gs: &[GaloisGroupEl]
) -> Vec<Self::Element> {
gs.iter().map(|_| self.clone_el(x)).collect()
}
#[instrument(skip_all)]
fn hom_inner_product_ref<'a, I>(
&self,
P: &PlaintextRing<Params>,
C: &CiphertextRing<Params>,
_dropped_factors: &RNSFactorIndexList,
data: I
) -> Ciphertext<Params>
where I: Iterator<Item = (&'a Self::Element, &'a Ciphertext<Params>)>,
Params: 'a,
Self: 'a
{
let h = C.inclusion().compose(C.base_ring().can_hom(&ZZi64).unwrap());
let mut c0 = C.zero();
let mut c1 = C.zero();
for (l, r) in data {
c0 = h.fma_map(&r.c0, l, c0);
c1 = h.fma_map(&r.c1, l, c1);
}
return Ciphertext {
implicit_scale: P.base_ring().one(),
c0: c0,
c1: c1
}
}
}
impl<Params: BGVInstantiation> AsBGVPlaintext<Params> for BigIntRingBase {
fn hom_add_to(
&self,
P: &PlaintextRing<Params>,
C: &CiphertextRing<Params>,
_dropped_factors: &RNSFactorIndexList,
m: &Self::Element,
ct: Ciphertext<Params>
) -> Ciphertext<Params> {
Params::hom_add_plain_encoded(P, C, &C.inclusion().compose(C.base_ring().can_hom(&ZZbig).unwrap()).map_ref(m), ct)
}
fn hom_add_to_noise<N: BGVNoiseEstimator<Params>>(
&self,
estimator: &N,
P: &PlaintextRing<Params>,
C: &CiphertextRing<Params>,
_dropped_factors: &RNSFactorIndexList,
m: &Self::Element,
ct_info: &N::CiphertextDescriptor,
implicit_scale: &El<PlaintextZnRing<Params>>
) -> N::CiphertextDescriptor {
estimator.hom_add_plain_encoded(P, C, &C.inclusion().compose(C.base_ring().can_hom(&ZZbig).unwrap()).map_ref(m), ct_info, implicit_scale)
}
fn hom_mul_to(
&self,
P: &PlaintextRing<Params>,
C: &CiphertextRing<Params>,
_dropped_factors: &RNSFactorIndexList,
m: &Self::Element,
ct: Ciphertext<Params>
) -> Ciphertext<Params> {
Params::hom_mul_plain_int(P, C, m, ct)
}
fn hom_mul_to_noise<N: BGVNoiseEstimator<Params>>(
&self,
estimator: &N,
P: &PlaintextRing<Params>,
C: &CiphertextRing<Params>,
_dropped_factors: &RNSFactorIndexList,
m: &Self::Element,
ct_info: &N::CiphertextDescriptor,
implicit_scale: &El<PlaintextZnRing<Params>>
) -> N::CiphertextDescriptor {
estimator.hom_mul_plain_int(P, C, m, ct_info, implicit_scale)
}
#[instrument(skip_all)]
fn hom_inner_product_ref<'a, I>(
&self,
P: &PlaintextRing<Params>,
C: &CiphertextRing<Params>,
_dropped_factors: &RNSFactorIndexList,
data: I
) -> Ciphertext<Params>
where I: Iterator<Item = (&'a Self::Element, &'a Ciphertext<Params>)>,
Params: 'a,
Self: 'a
{
let h = C.inclusion().compose(C.base_ring().can_hom(&ZZbig).unwrap());
let mut c0 = C.zero();
let mut c1 = C.zero();
for (l, r) in data {
c0 = h.fma_map(&r.c0, l, c0);
c1 = h.fma_map(&r.c1, l, c1);
}
return Ciphertext {
implicit_scale: P.base_ring().one(),
c0: c0,
c1: c1
}
}
fn apply_galois_action_plain(
&self,
_P: &PlaintextRing<Params>,
x: &Self::Element,
gs: &[GaloisGroupEl]
) -> Vec<Self::Element> {
gs.iter().map(|_| self.clone_el(x)).collect()
}
}
impl<Params> AsBGVPlaintext<Params> for NumberRingQuotientByIntBase<NumberRing<Params>, Zn>
where Params: BGVInstantiation<PlaintextRing = NumberRingQuotientByIntBase<NumberRing<Params>, Zn>>
{
fn hom_add_to(
&self,
P: &PlaintextRing<Params>,
C: &CiphertextRing<Params>,
_dropped_factors: &RNSFactorIndexList,
m: &Self::Element,
ct: Ciphertext<Params>
) -> Ciphertext<Params> {
Params::hom_add_plain(P, C, m, ct)
}
fn hom_add_to_noise<N: BGVNoiseEstimator<Params>>(
&self,
estimator: &N,
P: &PlaintextRing<Params>,
C: &CiphertextRing<Params>,
_dropped_factors: &RNSFactorIndexList,
m: &Self::Element,
ct_info: &N::CiphertextDescriptor,
implicit_scale: &El<PlaintextZnRing<Params>>
) -> N::CiphertextDescriptor {
estimator.hom_add_plain(P, C, m, ct_info, implicit_scale)
}
fn hom_mul_to(
&self,
P: &PlaintextRing<Params>,
C: &CiphertextRing<Params>,
_dropped_factors: &RNSFactorIndexList,
m: &Self::Element,
ct: Ciphertext<Params>
) -> Ciphertext<Params> {
Params::hom_mul_plain(P, C, m, ct)
}
fn hom_mul_to_noise<N: BGVNoiseEstimator<Params>>(
&self,
estimator: &N,
P: &PlaintextRing<Params>,
C: &CiphertextRing<Params>,
_dropped_factors: &RNSFactorIndexList,
m: &Self::Element,
ct_info: &N::CiphertextDescriptor,
implicit_scale: &El<PlaintextZnRing<Params>>
) -> N::CiphertextDescriptor {
estimator.hom_mul_plain(P, C, m, ct_info, implicit_scale)
}
fn apply_galois_action_plain(
&self,
_P: &PlaintextRing<Params>,
x: &Self::Element,
gs: &[GaloisGroupEl]
) -> Vec<Self::Element> {
self.apply_galois_action_many(x, gs)
}
}
impl<Params: BGVInstantiation, A: Allocator + Clone> AsBGVPlaintext<Params> for ManagedDoubleRNSRingBase<NumberRing<Params>, A>
where CiphertextRing<Params>: RingStore<Type = ManagedDoubleRNSRingBase<NumberRing<Params>, A>>
{
fn hom_add_to(
&self,
P: &PlaintextRing<Params>,
C: &CiphertextRing<Params>,
dropped_factors: &RNSFactorIndexList,
m: &Self::Element,
ct: Ciphertext<Params>
) -> Ciphertext<Params> {
Params::hom_add_plain_encoded(P, C, &C.get_ring().drop_rns_factor_element(self, dropped_factors, m), ct)
}
fn hom_add_to_noise<N: BGVNoiseEstimator<Params>>(
&self,
estimator: &N,
P: &PlaintextRing<Params>,
C: &CiphertextRing<Params>,
dropped_factors: &RNSFactorIndexList,
m: &Self::Element,
ct_info: &N::CiphertextDescriptor,
implicit_scale: &El<PlaintextZnRing<Params>>
) -> N::CiphertextDescriptor {
estimator.hom_add_plain_encoded(P, C, &C.get_ring().drop_rns_factor_element(self, dropped_factors, m), ct_info, implicit_scale)
}
fn hom_mul_to(
&self,
P: &PlaintextRing<Params>,
C: &CiphertextRing<Params>,
dropped_factors: &RNSFactorIndexList,
m: &Self::Element,
ct: Ciphertext<Params>
) -> Ciphertext<Params> {
Params::hom_mul_plain_encoded(P, C, &C.get_ring().drop_rns_factor_element(self, dropped_factors, m), ct)
}
fn hom_mul_to_noise<N: BGVNoiseEstimator<Params>>(
&self,
estimator: &N,
P: &PlaintextRing<Params>,
C: &CiphertextRing<Params>,
dropped_factors: &RNSFactorIndexList,
m: &Self::Element,
ct_info: &N::CiphertextDescriptor,
implicit_scale: &El<PlaintextZnRing<Params>>
) -> N::CiphertextDescriptor {
estimator.hom_mul_plain_encoded(P, C, &C.get_ring().drop_rns_factor_element(self, dropped_factors, m), ct_info, implicit_scale)
}
#[instrument(skip_all)]
fn hom_inner_product<I>(
&self,
P: &PlaintextRing<Params>,
C: &CiphertextRing<Params>,
dropped_factors: &RNSFactorIndexList,
data: I
) -> Ciphertext<Params>
where I: Iterator<Item = (Self::Element, Ciphertext<Params>)>
{
let mut lhs = Vec::new();
let mut rhs_c0 = Vec::new();
let mut rhs_c1 = Vec::new();
let mut first_implicit_scale = None;
for (l, r) in data {
if first_implicit_scale.is_none() {
first_implicit_scale = Some(P.base_ring().clone_el(&r.implicit_scale));
} else {
assert!(P.base_ring().eq_el(first_implicit_scale.as_ref().unwrap(), &r.implicit_scale));
}
lhs.push(C.get_ring().drop_rns_factor_element(self, dropped_factors, &l));
rhs_c0.push(r.c0);
rhs_c1.push(r.c1);
}
return Ciphertext {
implicit_scale: first_implicit_scale.unwrap_or(P.base_ring().one()),
c0: <_ as ComputeInnerProduct>::inner_product_ref_fst(C.get_ring(), lhs.iter().zip(rhs_c0.into_iter())),
c1: <_ as ComputeInnerProduct>::inner_product(C.get_ring(), lhs.into_iter().zip(rhs_c1.into_iter())),
};
}
#[instrument(skip_all)]
fn hom_inner_product_ref<'a, I>(
&self,
P: &PlaintextRing<Params>,
C: &CiphertextRing<Params>,
dropped_factors: &RNSFactorIndexList,
data: I
) -> Ciphertext<Params>
where I: Iterator<Item = (&'a Self::Element, &'a Ciphertext<Params>)>,
Params: 'a,
Self: 'a
{
let mut lhs = Vec::new();
let mut rhs_c0 = Vec::new();
let mut rhs_c1 = Vec::new();
for (l, r) in data {
lhs.push(C.get_ring().drop_rns_factor_element(self, dropped_factors, l));
rhs_c0.push(&r.c0);
rhs_c1.push(&r.c1);
}
return Ciphertext {
implicit_scale: P.base_ring().one(),
c0: <_ as ComputeInnerProduct>::inner_product_ref(C.get_ring(), rhs_c0.into_iter().zip(lhs.iter())),
c1: <_ as ComputeInnerProduct>::inner_product_ref_fst(C.get_ring(), rhs_c1.into_iter().zip(lhs.into_iter())),
};
}
fn apply_galois_action_plain(
&self,
_P: &PlaintextRing<Params>,
x: &Self::Element,
gs: &[GaloisGroupEl]
) -> Vec<Self::Element> {
self.apply_galois_action_many(x, gs)
}
}
impl<Params: BGVInstantiation, A: Allocator + Clone> AsBGVPlaintext<Params> for DoubleRNSRingBase<NumberRing<Params>, A>
where CiphertextRing<Params>: RingStore<Type = DoubleRNSRingBase<NumberRing<Params>, A>>
{
fn hom_add_to(
&self,
P: &PlaintextRing<Params>,
C: &CiphertextRing<Params>,
dropped_factors: &RNSFactorIndexList,
m: &Self::Element,
ct: Ciphertext<Params>
) -> Ciphertext<Params> {
Params::hom_add_plain_encoded(P, C, &C.get_ring().drop_rns_factor_element(self, dropped_factors, m), ct)
}
fn hom_add_to_noise<N: BGVNoiseEstimator<Params>>(
&self,
estimator: &N,
P: &PlaintextRing<Params>,
C: &CiphertextRing<Params>,
dropped_factors: &RNSFactorIndexList,
m: &Self::Element,
ct_info: &N::CiphertextDescriptor,
implicit_scale: &El<PlaintextZnRing<Params>>
) -> N::CiphertextDescriptor {
estimator.hom_add_plain_encoded(P, C, &C.get_ring().drop_rns_factor_element(self, dropped_factors, m), ct_info, implicit_scale)
}
fn hom_mul_to(
&self,
P: &PlaintextRing<Params>,
C: &CiphertextRing<Params>,
dropped_factors: &RNSFactorIndexList,
m: &Self::Element,
ct: Ciphertext<Params>
) -> Ciphertext<Params> {
Params::hom_mul_plain_encoded(P, C, &C.get_ring().drop_rns_factor_element(self, dropped_factors, m), ct)
}
fn hom_mul_to_noise<N: BGVNoiseEstimator<Params>>(
&self,
estimator: &N,
P: &PlaintextRing<Params>,
C: &CiphertextRing<Params>,
dropped_factors: &RNSFactorIndexList,
m: &Self::Element,
ct_info: &N::CiphertextDescriptor,
implicit_scale: &El<PlaintextZnRing<Params>>
) -> N::CiphertextDescriptor {
estimator.hom_mul_plain_encoded(P, C, &C.get_ring().drop_rns_factor_element(self, dropped_factors, m), ct_info, implicit_scale)
}
#[instrument(skip_all)]
fn hom_inner_product<I>(
&self,
P: &PlaintextRing<Params>,
C: &CiphertextRing<Params>,
dropped_factors: &RNSFactorIndexList,
data: I
) -> Ciphertext<Params>
where I: Iterator<Item = (Self::Element, Ciphertext<Params>)>
{
let mut lhs = Vec::new();
let mut rhs_c0 = Vec::new();
let mut rhs_c1 = Vec::new();
let mut first_implicit_scale = None;
for (l, r) in data {
if first_implicit_scale.is_none() {
first_implicit_scale = Some(P.base_ring().clone_el(&r.implicit_scale));
} else {
assert!(P.base_ring().eq_el(first_implicit_scale.as_ref().unwrap(), &r.implicit_scale));
}
lhs.push(C.get_ring().drop_rns_factor_element(self, dropped_factors, &l));
rhs_c0.push(r.c0);
rhs_c1.push(r.c1);
}
return Ciphertext {
implicit_scale: first_implicit_scale.unwrap_or(P.base_ring().one()),
c0: <_ as ComputeInnerProduct>::inner_product_ref_fst(C.get_ring(), lhs.iter().zip(rhs_c0.into_iter())),
c1: <_ as ComputeInnerProduct>::inner_product(C.get_ring(), lhs.into_iter().zip(rhs_c1.into_iter())),
};
}
#[instrument(skip_all)]
fn hom_inner_product_ref<'a, I>(
&self,
P: &PlaintextRing<Params>,
C: &CiphertextRing<Params>,
dropped_factors: &RNSFactorIndexList,
data: I
) -> Ciphertext<Params>
where I: Iterator<Item = (&'a Self::Element, &'a Ciphertext<Params>)>,
Params: 'a,
Self: 'a
{
let mut lhs = Vec::new();
let mut rhs_c0 = Vec::new();
let mut rhs_c1 = Vec::new();
for (l, r) in data {
lhs.push(C.get_ring().drop_rns_factor_element(self, dropped_factors, l));
rhs_c0.push(&r.c0);
rhs_c1.push(&r.c1);
}
return Ciphertext {
implicit_scale: P.base_ring().one(),
c0: <_ as ComputeInnerProduct>::inner_product_ref(C.get_ring(), rhs_c0.into_iter().zip(lhs.iter())),
c1: <_ as ComputeInnerProduct>::inner_product_ref_fst(C.get_ring(), rhs_c1.into_iter().zip(lhs.into_iter())),
};
}
fn apply_galois_action_plain(
&self,
_P: &PlaintextRing<Params>,
x: &Self::Element,
gs: &[GaloisGroupEl]
) -> Vec<Self::Element> {
self.apply_galois_action_many(x, gs)
}
}
#[instrument(skip_all)]
pub fn compute_optimal_special_modulus<C: BGFVCiphertextRing>(
C_master: &C,
dropped_factors_input: &RNSFactorIndexList,
drop_additional_count: usize,
key_switch_key_digits: &RNSGadgetVectorDigitIndices
) -> (Box<RNSFactorIndexList>, Box<RNSFactorIndexList>) {
let a = key_switch_key_digits.iter().map(|digit| digit.end - digit.start).collect::<Vec<_>>();
let b = key_switch_key_digits.iter().map(|digit| digit.end - digit.start - dropped_factors_input.num_within(&digit)).collect::<Vec<_>>();
if let Some((c, d)) = level_digits(&a, &b, drop_additional_count) {
let B_additional = key_switch_key_digits.iter().enumerate().flat_map(|(digit_idx, digit)| digit.filter(|i| !dropped_factors_input.contains(*i)).take(c[digit_idx]));
let B_final = RNSFactorIndexList::from(dropped_factors_input.iter().copied().chain(B_additional).collect::<Vec<_>>(), C_master.base_ring().len());
let B_special = RNSFactorIndexList::from(key_switch_key_digits.iter().enumerate().flat_map(|(digit_idx, digit)| digit.filter(|i| B_final.contains(*i)).take(d[digit_idx])).collect::<Vec<_>>(), C_master.base_ring().len());
assert_eq!(B_final.len(), dropped_factors_input.len() + drop_additional_count);
return (B_final, B_special);
} else {
let additional_drop = drop_rns_factors_balanced(&key_switch_key_digits.remove_indices(dropped_factors_input), drop_additional_count);
let B_final = additional_drop.pullback(dropped_factors_input);
let B_special = B_final.clone();
assert_eq!(B_final.len(), dropped_factors_input.len() + drop_additional_count);
return (B_final, B_special);
}
}
impl<Params: BGVInstantiation, N: BGVNoiseEstimator<Params>, const LOG: bool> DefaultModswitchStrategy<Params, N, LOG> {
pub fn new(noise_estimator: N) -> Self {
Self {
params: PhantomData,
noise_estimator: noise_estimator
}
}
pub fn from_noise_level(&self, noise_level: N::CiphertextDescriptor) -> <Self as BGVModswitchStrategy<Params>>::CiphertextInfo {
noise_level
}
fn mod_switch_down(
&self,
P: &PlaintextRing<Params>,
C_target: &CiphertextRing<Params>,
C_master: &CiphertextRing<Params>,
dropped_factors_target: &RNSFactorIndexList,
x: ModulusAwareCiphertext<Params, Self>,
context: &str,
debug_sk: Option<&SecretKey<Params>>
) -> ModulusAwareCiphertext<Params, Self> {
let used_sk = x.sk;
let Cx = Params::mod_switch_down_C(C_master, &x.dropped_rns_factor_indices);
let drop_x = dropped_factors_target.pushforward(&x.dropped_rns_factor_indices);
if drop_x.len() == 0 {
return x;
}
let x_noise_budget = if let Some(sk) = debug_sk {
let sk_x = Params::mod_switch_sk(&Cx, C_master, sk);
Some(Params::noise_budget(P, &Cx, &x.data, &sk_x))
} else { None };
let result = ModulusAwareCiphertext {
data: Params::mod_switch_ct(P, &C_target, &Cx, x.data),
info: self.noise_estimator.mod_switch_down_ct(&P, &C_target, &Cx, &drop_x, &x.info),
dropped_rns_factor_indices: dropped_factors_target.to_owned(),
sk: used_sk
};
if LOG && drop_x.len() > 0 {
println!("{}: Dropping RNS factors {} of operand, estimated noise budget {}/{} -> {}/{}",
context,
drop_x,
-self.noise_estimator.estimate_log2_relative_noise_level(P, &Cx, &x.info).round(),
ZZbig.abs_log2_ceil(Cx.base_ring().modulus()).unwrap(),
-self.noise_estimator.estimate_log2_relative_noise_level(P, C_target, &result.info).round(),
ZZbig.abs_log2_ceil(C_target.base_ring().modulus()).unwrap(),
);
if let Some(sk) = debug_sk {
let sk_target = Params::mod_switch_sk(C_target, C_master, sk);
println!(" actual noise budget: {} -> {}", x_noise_budget.unwrap(), Params::noise_budget(P, C_target, &result.data, &sk_target));
}
}
return result;
}
fn mod_switch_down_ref<'a>(
&self,
P: &PlaintextRing<Params>,
C_target: &CiphertextRing<Params>,
C_master: &CiphertextRing<Params>,
dropped_factors_target: &RNSFactorIndexList,
x: &'a ModulusAwareCiphertext<Params, Self>,
context: &str,
debug_sk: Option<&SecretKey<Params>>
) -> Boo<'a, ModulusAwareCiphertext<Params, Self>> {
let used_sk = x.sk;
let Cx = Params::mod_switch_down_C(C_master, &x.dropped_rns_factor_indices);
let drop_x = dropped_factors_target.pushforward(&x.dropped_rns_factor_indices);
if drop_x.len() == 0 {
return Boo::Borrowed(x);
}
let result = ModulusAwareCiphertext {
data: Params::mod_switch_ct(P, &C_target, &Cx, Params::clone_ct(P, &Cx, &x.data)),
info: self.noise_estimator.mod_switch_down_ct(&P, &C_target, &Cx, &drop_x, &x.info),
dropped_rns_factor_indices: dropped_factors_target.to_owned(),
sk: used_sk
};
if LOG && drop_x.len() > 0 {
println!("{}: Dropping RNS factors {} of operand, estimated noise budget {}/{} -> {}/{}",
context,
drop_x,
-self.noise_estimator.estimate_log2_relative_noise_level(P, &Cx, &x.info).round(),
ZZbig.abs_log2_ceil(Cx.base_ring().modulus()).unwrap(),
-self.noise_estimator.estimate_log2_relative_noise_level(P, C_target, &result.info).round(),
ZZbig.abs_log2_ceil(C_target.base_ring().modulus()).unwrap(),
);
if let Some(sk) = debug_sk {
let sk_target = Params::mod_switch_sk(C_target, C_master, sk);
let sk_x = Params::mod_switch_sk(&Cx, C_master, sk);
println!(" actual noise budget: {} -> {}", Params::noise_budget(P, &Cx, &x.data, &sk_x), Params::noise_budget(P, C_target, &result.data, &sk_target));
}
}
return Boo::Owned(result);
}
#[instrument(skip_all)]
fn compute_optimal_mul_modswitch(
&self,
P: &PlaintextRing<Params>,
C_master: &CiphertextRing<Params>,
noise_x: &N::CiphertextDescriptor,
dropped_factors_x: &RNSFactorIndexList,
noise_y: &N::CiphertextDescriptor,
dropped_factors_y: &RNSFactorIndexList,
rk_digits: &RNSGadgetVectorDigitIndices,
used_sk: SecretKeyDistribution
) -> (/* total_drop = */ Box<RNSFactorIndexList>, /* special_modulus = */ Box<RNSFactorIndexList>) {
let Cx = Params::mod_switch_down_C(C_master, dropped_factors_x);
let Cy = Params::mod_switch_down_C(C_master, dropped_factors_y);
let base_drop = dropped_factors_x.union(&dropped_factors_y);
let compute_result_noise = |num_to_drop: usize| {
let (total_drop, special_modulus) = compute_optimal_special_modulus(C_master.get_ring(), &base_drop, num_to_drop, rk_digits);
let total_drop_without_special = total_drop.subtract(&special_modulus);
let C_target = Params::mod_switch_down_C(C_master, &total_drop);
let C_special = Params::mod_switch_down_C(C_master, &total_drop_without_special);
let rk_digits_after_total_drop = rk_digits.remove_indices(&total_drop_without_special);
let expected_noise = self.noise_estimator.estimate_log2_relative_noise_level(
P,
&C_target,
&self.noise_estimator.hom_mul(
P,
&C_target,
&C_special,
&total_drop.pushforward(&total_drop_without_special),
&self.noise_estimator.mod_switch_down_ct(&P, &C_target, &Cx, &total_drop.pushforward(dropped_factors_x), noise_x),
&self.noise_estimator.mod_switch_down_ct(&P, &C_target, &Cy, &total_drop.pushforward(dropped_factors_y), noise_y),
KeySwitchKeyDescriptor {
digits: &rk_digits_after_total_drop,
new_sk: used_sk,
sigma: 3.2
}
)
);
return ((total_drop, special_modulus), expected_noise);
};
return (0..(C_master.base_ring().len() - base_drop.len())).map(compute_result_noise).min_by(|(_, l), (_, r)| f64::total_cmp(l, r)).unwrap().0;
}
#[instrument(skip_all)]
fn add_inner_prod<'a, R>(
&self,
P: &PlaintextRing<Params>,
C_master: &CiphertextRing<Params>,
x: PlainOrCiphertext<'a, Params, Self, R::Type>,
coeffs: &[&Coefficient<R::Type>],
ys: &[&PlainOrCiphertext<'a, Params, Self, R::Type>],
ring: R,
debug_sk: Option<&SecretKey<Params>>
) -> PlainOrCiphertext<'a, Params, Self, R::Type>
where R: RingStore + Copy,
R::Type: AsBGVPlaintext<Params>
{
assert_eq!(coeffs.len(), ys.len());
let mut constant = Coefficient::Zero;
let mut int_products: Vec<(i32, &ModulusAwareCiphertext<Params, Self>)> = Vec::new();
let mut main_products: Vec<(&El<R>, &ModulusAwareCiphertext<Params, Self>)> = Vec::new();
let mut total_drop = RNSFactorIndexList::empty();
let mut min_dropped_len = usize::MAX;
let mut update_total_drop = |ct: &ModulusAwareCiphertext<Params, Self>| {
total_drop = total_drop.union(&ct.dropped_rns_factor_indices);
min_dropped_len = min(min_dropped_len, ct.dropped_rns_factor_indices.len());
};
let mut used_sk = SecretKeyDistribution::Zero;
for (lhs, rhs) in coeffs.iter().copied().zip(ys.iter().copied()).chain([(&Coefficient::One, &x)].into_iter()) {
match rhs.as_ciphertext_ref() {
Err(y) => constant = constant.add(lhs.clone(ring).mul(y.clone(ring), ring), ring),
Ok(y) => if !lhs.is_zero() {
update_total_drop(y);
used_sk = assert_sk_distr_match(used_sk, y.sk);
match lhs {
Coefficient::Zero => unreachable!(),
Coefficient::One => int_products.push((1, y)),
Coefficient::NegOne => int_products.push((-1, y)),
Coefficient::Integer(c) => int_products.push((*c, y)),
Coefficient::Other(c) => main_products.push((c, y)),
}
}
}
}
if int_products.len() == 0 && main_products.len() == 0 {
return PlainOrCiphertext::Plaintext(constant);
}
assert!(min_dropped_len <= total_drop.len());
let C_target = Params::mod_switch_down_C(C_master, &total_drop);
let int_products: Vec<(i32, Boo<ModulusAwareCiphertext<Params, Self>>)> = int_products.iter().map(|(lhs, rhs)| (
*lhs,
self.mod_switch_down_ref(P, &C_target, C_master, &total_drop, rhs, "HomInnerProduct", debug_sk)
)).collect();
let main_products: Vec<(&El<R>, Boo<ModulusAwareCiphertext<Params, Self>>)> = main_products.iter().map(|(lhs, rhs)| (
*lhs,
self.mod_switch_down_ref(P, &C_target, C_master, &total_drop, rhs, "HomInnerProduct", debug_sk)
)).collect();
let Zt = P.base_ring();
let ZZ: &_ = Zt.integer_ring();
let output_implicit_scale = int_products.iter().filter_map(|(c, ct)| Zt.invert(&Zt.int_hom().map(*c)).map(|c| (c, ct)))
.map(|(c, ct)| (self.noise_estimator.estimate_log2_relative_noise_level(P, &C_target, &ct.info), Zt.mul_ref_fst(&ct.data.implicit_scale, c))
).max_by(|(l, _), (r, _)| f64::total_cmp(l, r)).map(|(_, scale)| scale).unwrap_or(P.base_ring().one());
let int_products: Vec<(El<BigIntRing>, Boo<ModulusAwareCiphertext<Params, Self>>)> = int_products.into_iter().map(|(lhs, rhs)| {
let lhs = int_cast(Zt.smallest_lift(Zt.mul(Zt.int_hom().map(lhs), Zt.checked_div(&output_implicit_scale, &rhs.data.implicit_scale).unwrap())), ZZbig, ZZ);
return (lhs, rhs);
}).collect();
let ZZbig_to_ring = ring.can_hom(&ZZbig).unwrap();
let main_products: Vec<(Boo<El<R>>, Boo<ModulusAwareCiphertext<Params, Self>>)> = main_products.into_iter().map(|(lhs, rhs)| {
let factor = Zt.smallest_lift(Zt.checked_div(&output_implicit_scale, &rhs.data.implicit_scale).unwrap());
if !ZZ.is_one(&factor) {
let mut lhs = ring.clone_el(lhs);
ZZbig_to_ring.mul_assign_map(&mut lhs, int_cast(factor, ZZbig, ZZ));
return (Boo::Owned(lhs), rhs);
} else {
return (Boo::Borrowed(lhs), rhs);
}
}).collect();
let int_product_noise = ZZbig.get_ring().hom_inner_product_noise(&self.noise_estimator, P, &C_target, &total_drop, int_products.iter().map(|(lhs, rhs)| (lhs, &rhs.info)));
let mut int_product_part = ZZbig.get_ring().hom_inner_product_ref(P, &C_target, &total_drop, int_products.iter().map(|(lhs, rhs)| (lhs, &rhs.data)));
int_product_part.implicit_scale = P.base_ring().clone_el(&output_implicit_scale);
let main_product_noise = ring.get_ring().hom_inner_product_noise(&self.noise_estimator, P, &C_target, &total_drop, main_products.iter().map(|(lhs, rhs)| (&**lhs, &rhs.info)));
let mut main_product_part = ring.get_ring().hom_inner_product_ref(P, &C_target, &total_drop, main_products.iter().map(|(lhs, rhs)| (&**lhs, &rhs.data)));
main_product_part.implicit_scale = P.base_ring().clone_el(&output_implicit_scale);
let product_info = self.noise_estimator.hom_add(P, &C_target, &int_product_noise, &P.base_ring().one(), &main_product_noise, &P.base_ring().one());
let product_data = Params::hom_add(P, &C_target, int_product_part, main_product_part);
let res_data = match constant {
Coefficient::Zero => product_data,
Coefficient::One => Params::hom_add_plain_encoded(P, &C_target, &C_target.one(), product_data),
Coefficient::NegOne => Params::hom_add_plain_encoded(P, &C_target, &C_target.neg_one(), product_data),
Coefficient::Integer(c) => Params::hom_add_plain_encoded(P, &C_target, &C_target.int_hom().map(c), product_data),
Coefficient::Other(m) => ring.get_ring().hom_add_to(P, &C_target, &total_drop, &m, product_data),
};
return PlainOrCiphertext::Ciphertext(ModulusAwareCiphertext {
data: res_data,
info: product_info,
dropped_rns_factor_indices: total_drop,
sk: used_sk
});
}
#[instrument(skip_all)]
fn mul<'a, R>(
&self,
P: &PlaintextRing<Params>,
C_master: &CiphertextRing<Params>,
x: PlainOrCiphertext<'a, Params, Self, R::Type>,
y: PlainOrCiphertext<'a, Params, Self, R::Type>,
ring: R,
rk: Option<&RelinKey<Params>>,
key_switches: &RefCell<&mut usize>,
debug_sk: Option<&SecretKey<Params>>
) -> PlainOrCiphertext<'a, Params, Self, R::Type>
where R: RingStore + Copy,
R::Type: AsBGVPlaintext<Params>
{
match (x.as_ciphertext(P, C_master, ring, self), y.as_ciphertext(P, C_master, ring, self)) {
(Err(x), Err(y)) => PlainOrCiphertext::Plaintext(x.mul(y, ring)),
(Ok((Cx, x)), Err(y)) | (Err(y), Ok((Cx, x))) => PlainOrCiphertext::Ciphertext({
let used_sk = x.sk;
let total_drop = x.dropped_rns_factor_indices.clone();
let C_target = &Cx;
let (res_info, res_data) = match y {
Coefficient::Zero => unreachable!(),
Coefficient::One => (x.info, x.data),
Coefficient::NegOne => (x.info, Params::hom_mul_plain_int(P, &C_target, &ZZbig.neg_one(), x.data)),
Coefficient::Integer(c) => (
StaticRing::<i64>::RING.get_ring().hom_mul_to_noise(&self.noise_estimator, P, &C_target, &total_drop, &(c as i64), &x.info, &x.data.implicit_scale),
StaticRing::<i64>::RING.get_ring().hom_mul_to(P, &C_target, &total_drop, &(c as i64), Params::clone_ct(P, &Cx, &x.data)),
),
Coefficient::Other(m) => (
ring.get_ring().hom_mul_to_noise(&self.noise_estimator, P, &C_target, &total_drop, &m, &x.info, &x.data.implicit_scale),
ring.get_ring().hom_mul_to(P, &C_target, &total_drop, &m, Params::clone_ct(P, &Cx, &x.data)),
),
};
ModulusAwareCiphertext {
data: res_data,
info: res_info,
dropped_rns_factor_indices: total_drop,
sk: used_sk
}
}),
(Ok((_, x)), Ok((_, y))) => PlainOrCiphertext::Ciphertext({
let used_sk = assert_sk_distr_match(x.sk, y.sk);
assert!(x.dropped_rns_factor_indices.len() < C_master.base_ring().len());
assert!(y.dropped_rns_factor_indices.len() < C_master.base_ring().len());
**key_switches.borrow_mut() += 1;
let rk = rk.unwrap();
let (total_drop, special_modulus) = self.compute_optimal_mul_modswitch(P, C_master, &x.info, &x.dropped_rns_factor_indices, &y.info, &y.dropped_rns_factor_indices, rk.gadget_vector_digits(), used_sk);
let total_drop_without_special = total_drop.subtract(&special_modulus);
let C_special = Params::mod_switch_down_C(&C_master, &total_drop_without_special);
let C_target = Params::mod_switch_down_C(C_master, &total_drop);
let rk_modswitch = Params::mod_switch_down_rk(&C_special, C_master, &rk);
debug_assert!(total_drop.len() >= x.dropped_rns_factor_indices.len());
debug_assert!(total_drop.len() >= y.dropped_rns_factor_indices.len());
let x_modswitched = self.mod_switch_down(P, &C_target, C_master, &total_drop, x, "HomMul", debug_sk);
let y_modswitched = self.mod_switch_down(P, &C_target, C_master, &total_drop, y, "HomMul", debug_sk);
if LOG {
println!(
"Using a special modulus of {} RNS factors and a gadget vector of {} digits (largest has {} RNS factors) for relinearization",
special_modulus.len(),
rk_modswitch.gadget_vector_digits().len(),
rk_modswitch.gadget_vector_digits().iter().map(|digit| digit.end - digit.start).max().unwrap()
);
}
let res_data = Params::hom_mul(
P,
&C_target,
&C_special,
x_modswitched.data,
y_modswitched.data,
&rk_modswitch
);
let res_info = self.noise_estimator.hom_mul(
P,
&C_target,
&C_special,
&total_drop.pushforward(&total_drop_without_special),
&x_modswitched.info,
&y_modswitched.info,
KeySwitchKeyDescriptor {
digits: rk_modswitch.gadget_vector_digits(),
new_sk: used_sk,
sigma: 3.2
}
);
if LOG {
println!("HomMul: Result has estimated noise budget {}/{}",
-self.noise_estimator.estimate_log2_relative_noise_level(P, &C_target, &res_info).round(),
ZZbig.abs_log2_ceil(C_target.base_ring().modulus()).unwrap()
);
if let Some(sk) = debug_sk {
let sk_target = Params::mod_switch_sk(&C_target, C_master, sk);
println!(" actual noise budget: {}", Params::noise_budget(P, &C_target, &res_data, &sk_target));
}
}
ModulusAwareCiphertext {
dropped_rns_factor_indices: total_drop,
info: res_info,
data: res_data,
sk: used_sk
}
})
}
}
#[instrument(skip_all)]
fn square<'a, R>(
&self,
P: &PlaintextRing<Params>,
C_master: &CiphertextRing<Params>,
x: PlainOrCiphertext<'a, Params, Self, R::Type>,
ring: R,
rk: Option<&RelinKey<Params>>,
key_switches: &RefCell<&mut usize>,
debug_sk: Option<&SecretKey<Params>>
) -> PlainOrCiphertext<'a, Params, Self, R::Type>
where R: RingStore + Copy,
R::Type: AsBGVPlaintext<Params>
{
match x.as_ciphertext(P, C_master, ring, self) {
Err(x) => PlainOrCiphertext::Plaintext(x.clone(ring).mul(x, ring)),
Ok((_, x)) => PlainOrCiphertext::Ciphertext({
let used_sk = x.sk;
assert!(x.dropped_rns_factor_indices.len() < C_master.base_ring().len());
**key_switches.borrow_mut() += 1;
let rk = rk.unwrap();
let (total_drop, special_modulus) = self.compute_optimal_mul_modswitch(P, C_master, &x.info, &x.dropped_rns_factor_indices, &x.info, &x.dropped_rns_factor_indices, rk.gadget_vector_digits(), used_sk);
let total_drop_without_special = total_drop.subtract(&special_modulus);
let C_special = Params::mod_switch_down_C(&C_master, &total_drop_without_special);
let C_target = Params::mod_switch_down_C(C_master, &total_drop);
let rk_modswitch = Params::mod_switch_down_rk(&C_special, C_master, &rk);
debug_assert!(total_drop.len() >= x.dropped_rns_factor_indices.len());
let x_modswitched = self.mod_switch_down(P, &C_target, C_master, &total_drop, x, "HomSquare", debug_sk);
if LOG {
println!(
"Using a special modulus of {} RNS factors and a gadget vector of {} digits (largest has {} RNS factors) for relinearization",
special_modulus.len(),
rk_modswitch.gadget_vector_digits().len(),
rk_modswitch.gadget_vector_digits().iter().map(|digit| digit.end - digit.start).max().unwrap()
);
}
let res_info = self.noise_estimator.hom_mul(
P,
&C_target,
&C_special,
&total_drop.pushforward(&total_drop_without_special),
&x_modswitched.info,
&x_modswitched.info,
KeySwitchKeyDescriptor {
digits: rk_modswitch.gadget_vector_digits(),
new_sk: used_sk,
sigma: 3.2
}
);
let res_data = Params::hom_square(
P,
&C_target,
&C_special,
x_modswitched.data,
&rk_modswitch
);
if LOG {
println!("HomSquare: Result has estimated noise budget {}/{}",
-self.noise_estimator.estimate_log2_relative_noise_level(P, &C_target, &res_info).round(),
ZZbig.abs_log2_ceil(C_target.base_ring().modulus()).unwrap()
);
if let Some(sk) = debug_sk {
let sk_target = Params::mod_switch_sk(&C_target, C_master, sk);
println!(" actual noise budget: {}", Params::noise_budget(P, &C_target, &res_data, &sk_target));
}
}
ModulusAwareCiphertext {
dropped_rns_factor_indices: total_drop,
info: res_info,
data: res_data,
sk: used_sk
}
})
}
}
#[instrument(skip_all)]
fn gal_many<'a, R>(
&self,
P: &PlaintextRing<Params>,
C_master: &CiphertextRing<Params>,
x: PlainOrCiphertext<'a, Params, Self, R::Type>,
ring: R,
gs: &[GaloisGroupEl],
gks: &[(GaloisGroupEl, KeySwitchKey<Params>)],
key_switches: &RefCell<&mut usize>,
_debug_sk: Option<&SecretKey<Params>>
) -> Vec<PlainOrCiphertext<'a, Params, Self, R::Type>>
where R: RingStore + Copy,
R::Type: AsBGVPlaintext<Params>
{
match x.as_ciphertext(P, C_master, ring, self) {
Ok((Cx, x)) => {
let used_sk = x.sk;
assert!(x.dropped_rns_factor_indices.len() < C_master.base_ring().len());
**key_switches.borrow_mut() += gs.len();
let get_gk = |g| if let Some(res) = gks.iter().filter(|(provided_g, _)| C_master.acting_galois_group().eq_el(g, provided_g)).next() {
res
} else {
panic!("Galois key for {} not found", C_master.acting_galois_group().representative(g))
};
let gk_digits = get_gk(&gs[0]).1.gadget_vector_digits();
assert!(gs.iter().all(|g| get_gk(g).1.gadget_vector_digits() == gk_digits), "when using `gal_many()`, all Galois keys must have the same digits");
let (total_drop, special_modulus) = compute_optimal_special_modulus(C_master.get_ring(), &x.dropped_rns_factor_indices, 0, gk_digits);
assert!(total_drop.len() < C_master.base_ring().len());
let C_target = Params::mod_switch_down_C(&Cx, &total_drop.pushforward(&x.dropped_rns_factor_indices));
let total_drop_without_special = total_drop.subtract(&special_modulus);
let C_special = Params::mod_switch_down_C(&C_master, &total_drop_without_special);
let gks_mod_switched = gs.iter().map(|g| Params::mod_switch_down_gk(&C_special, C_master, &get_gk(g).1)).collect::<Vec<_>>();
if LOG {
println!(
"Using a special modulus of {} RNS factors and a gadget vector of {} digits (largest has {} RNS factors) for Galois key switching",
special_modulus.len(),
gk_digits.remove_indices(&total_drop_without_special).len(),
gk_digits.remove_indices(&total_drop_without_special).iter().map(|digit| digit.end - digit.start).max().unwrap()
);
}
let result = if gs.len() == 1 {
vec![Params::hom_galois(P, &C_target, &C_special, x.data, &gs[0], gks_mod_switched.at(0))]
} else {
Params::hom_galois_many(P, &C_target, &C_special, x.data, gs, gks_mod_switched.as_fn())
};
result.into_iter().zip(gs.into_iter()).zip(gks_mod_switched.iter()).map(|((res, g), gk)| PlainOrCiphertext::Ciphertext(ModulusAwareCiphertext {
dropped_rns_factor_indices: total_drop.clone(),
info: self.noise_estimator.hom_galois(
&P,
&C_target,
&C_special,
&total_drop.pushforward(&total_drop_without_special),
&x.info,
g,
KeySwitchKeyDescriptor {
digits: gk.gadget_vector_digits(),
new_sk: used_sk,
sigma: 3.2
}
),
sk: used_sk,
data: res
})).collect()
},
Err(Coefficient::Other(x)) => ring.get_ring().apply_galois_action_plain(P, &x, gs).into_iter().map(|x| PlainOrCiphertext::Plaintext(Coefficient::Other(x))).collect(),
Err(x) => gs.iter().map(|_| PlainOrCiphertext::Plaintext(x.clone(ring))).collect()
}
}
}
impl<Params: BGVInstantiation, N: BGVNoiseEstimator<Params>, const LOG: bool> BGVModswitchStrategy<Params> for DefaultModswitchStrategy<Params, N, LOG> {
type CiphertextInfo = N::CiphertextDescriptor;
#[instrument(skip_all)]
fn evaluate_circuit<R>(
&self,
circuit: &PlaintextCircuit<R::Type>,
ring: R,
P: &PlaintextRing<Params>,
C_master: &CiphertextRing<Params>,
inputs: &[ModulusAwareCiphertext<Params, Self>],
rk: Option<&RelinKey<Params>>,
gks: &[(GaloisGroupEl, KeySwitchKey<Params>)],
key_switches: &mut usize,
mut debug_sk: Option<&SecretKey<Params>>
) -> Vec<ModulusAwareCiphertext<Params, Self>>
where R: RingStore,
R::Type: AsBGVPlaintext<Params>
{
if !LOG {
debug_sk = None;
}
let key_switches_refcell = std::cell::RefCell::new(key_switches);
let result = circuit.evaluate_generic(
&inputs.iter().map(PlainOrCiphertext::CiphertextRef).collect::<Vec<_>>(),
DefaultCircuitEvaluator::new(
|m| PlainOrCiphertext::PlaintextRef(m),
|_, _, _| unreachable!(),
).with_mul(
|x, y| self.mul(P, C_master, x, y, &ring, rk, &key_switches_refcell, debug_sk),
).with_square(
|x| self.square(P, C_master, x, &ring, rk, &key_switches_refcell, debug_sk),
).with_gal(
|x, gs| self.gal_many(P, C_master, x, &ring, gs, gks, &key_switches_refcell, debug_sk)
).with_inner_product(
|x, cs, ys| self.add_inner_prod(P, C_master, x, cs, ys, &ring, debug_sk)
)
);
return result.into_iter().map(|res| match res {
PlainOrCiphertext::Ciphertext(x) => x,
PlainOrCiphertext::CiphertextRef(x) => {
let Cx = Params::mod_switch_down_C(C_master, &x.dropped_rns_factor_indices);
ModulusAwareCiphertext {
data: Params::clone_ct(&P, &Cx, &x.data),
dropped_rns_factor_indices: x.dropped_rns_factor_indices.clone(),
info: self.clone_info(&x.info),
sk: x.sk
}
},
PlainOrCiphertext::Plaintext(x) => {
let x = x.to_ring_el(&ring);
let res_info = ring.get_ring().hom_add_to_noise(&self.noise_estimator, P, C_master, &RNSFactorIndexList::empty(), &x, &self.noise_estimator.transparent_zero(), &P.base_ring().one());
let res_data = ring.get_ring().hom_add_to(P, C_master, &RNSFactorIndexList::empty(), &x, Params::transparent_zero(P, C_master));
ModulusAwareCiphertext {
data: res_data,
dropped_rns_factor_indices: RNSFactorIndexList::empty(),
info: res_info,
sk: SecretKeyDistribution::Zero
}
},
PlainOrCiphertext::PlaintextRef(x) => {
let x = x.clone(&ring).to_ring_el(&ring);
let res_info = ring.get_ring().hom_add_to_noise(&self.noise_estimator, P, C_master, &RNSFactorIndexList::empty(), &x, &self.noise_estimator.transparent_zero(), &P.base_ring().one());
let res_data = ring.get_ring().hom_add_to(P, C_master, &RNSFactorIndexList::empty(), &x, Params::transparent_zero(P, C_master));
ModulusAwareCiphertext {
data: res_data,
dropped_rns_factor_indices: RNSFactorIndexList::empty(),
info: res_info,
sk: SecretKeyDistribution::Zero
}
}
}).collect();
}
fn info_for_fresh_encryption(&self, P: &PlaintextRing<Params>, C: &CiphertextRing<Params>, sk: SecretKeyDistribution) -> <Self as BGVModswitchStrategy<Params>>::CiphertextInfo {
self.from_noise_level(self.noise_estimator.enc_sym_zero(P, C, sk))
}
fn clone_info(&self, info: &Self::CiphertextInfo) -> Self::CiphertextInfo {
self.noise_estimator.clone_critical_quantity_level(info)
}
fn print_info(&self, P: &PlaintextRing<Params>, C_master: &CiphertextRing<Params>, ct: &ModulusAwareCiphertext<Params, Self>) {
let Clocal = Params::mod_switch_down_C(C_master, &ct.dropped_rns_factor_indices);
println!("estimated noise: {}", self.noise_estimator.estimate_log2_relative_noise_level(P, &Clocal, &ct.info));
}
}
#[cfg(test)]
use crate::bgv::noise_estimator::NaiveBGVNoiseEstimator;
#[test]
fn test_default_modswitch_strategy_mul() {
let mut rng = rand::rng();
let params = Pow2BGV::new(1 << 8);
let P = params.create_plaintext_ring(int_cast(257, ZZbig, ZZi64));
let C = params.create_ciphertext_ring(500..520);
let sk = Pow2BGV::gen_sk(&C, &mut rng, SecretKeyDistribution::UniformTernary);
let rk = Pow2BGV::gen_rk(&P, &C, &mut rng, &sk, &RNSGadgetVectorDigitIndices::select_digits(3, C.base_ring().len()));
let input = P.int_hom().map(2);
let ctxt = Pow2BGV::enc_sym(&P, &C, &mut rng, &input, &sk);
let modswitch_strategy: DefaultModswitchStrategy<Pow2BGV, _, true> = DefaultModswitchStrategy::new(NaiveBGVNoiseEstimator);
let pow8_circuit = PlaintextCircuit::mul(ZZi64)
.compose(PlaintextCircuit::mul(ZZi64).output_twice(ZZi64), ZZi64)
.compose(PlaintextCircuit::mul(ZZi64).output_twice(ZZi64), ZZi64)
.compose(PlaintextCircuit::identity(1, ZZi64).output_twice(ZZi64), ZZi64);
let res = modswitch_strategy.evaluate_circuit(
&pow8_circuit,
ZZi64,
&P,
&C,
&[ModulusAwareCiphertext {
dropped_rns_factor_indices: RNSFactorIndexList::empty(),
info: modswitch_strategy.info_for_fresh_encryption(&P, &C, SecretKeyDistribution::UniformTernary),
data: ctxt,
sk: SecretKeyDistribution::UniformTernary
}],
Some(&rk),
&[],
&mut 0,
Some(&sk)
).into_iter().next().unwrap();
let res_C = Pow2BGV::mod_switch_down_C(&C, &res.dropped_rns_factor_indices);
let res_sk = Pow2BGV::mod_switch_sk(&res_C, &C, &sk);
let res_noise = Pow2BGV::noise_budget(&P, &res_C, &res.data, &res_sk);
println!("Actual output noise budget is {}", res_noise);
assert_el_eq!(&P, &P.neg_one(), Pow2BGV::dec(&P, &res_C, res.data, &res_sk));
}
#[test]
fn test_never_modswitch_strategy() {
let mut rng = rand::rng();
let params = Pow2BGV::new(1 << 8);
let P = params.create_plaintext_ring(int_cast(257, ZZbig, ZZi64));
let C = params.create_ciphertext_ring(500..520);
let sk = Pow2BGV::gen_sk(&C, &mut rng, SecretKeyDistribution::UniformTernary);
let rk = Pow2BGV::gen_rk(&P, &C, &mut rng, &sk, &RNSGadgetVectorDigitIndices::select_digits(3, C.base_ring().len()));
let input = P.int_hom().map(2);
let ctxt = Pow2BGV::enc_sym(&P, &C, &mut rng, &input, &sk);
{
let modswitch_strategy = DefaultModswitchStrategy::never_modswitch();
let pow4_circuit = PlaintextCircuit::mul(ZZi64)
.compose(PlaintextCircuit::square(ZZi64).output_twice(ZZi64), ZZi64);
let res = modswitch_strategy.evaluate_circuit(
&pow4_circuit,
ZZi64,
&P,
&C,
&[ModulusAwareCiphertext {
dropped_rns_factor_indices: RNSFactorIndexList::empty(),
info: modswitch_strategy.info_for_fresh_encryption(&P, &C, SecretKeyDistribution::UniformTernary),
data: Pow2BGV::clone_ct(&P, &C, &ctxt),
sk: SecretKeyDistribution::UniformTernary
}],
Some(&rk),
&[],
&mut 0,
None
).into_iter().next().unwrap();
let res_C = Pow2BGV::mod_switch_down_C(&C, &res.dropped_rns_factor_indices);
let res_sk = Pow2BGV::mod_switch_sk(&res_C, &C, &sk);
let res_noise = Pow2BGV::noise_budget(&P, &res_C, &res.data, &res_sk);
println!("Actual output noise budget is {}", res_noise);
assert_el_eq!(&P, &P.int_hom().map(16), Pow2BGV::dec(&P, &res_C, res.data, &res_sk));
}
{
let modswitch_strategy = DefaultModswitchStrategy::never_modswitch();
let pow8_circuit = PlaintextCircuit::mul(ZZi64)
.compose(PlaintextCircuit::mul(ZZi64).output_twice(ZZi64), ZZi64)
.compose(PlaintextCircuit::mul(ZZi64).output_twice(ZZi64), ZZi64)
.compose(PlaintextCircuit::identity(1, ZZi64).output_twice(ZZi64), ZZi64);
let res = modswitch_strategy.evaluate_circuit(
&pow8_circuit,
ZZi64,
&P,
&C,
&[ModulusAwareCiphertext {
dropped_rns_factor_indices: RNSFactorIndexList::empty(),
info: modswitch_strategy.info_for_fresh_encryption(&P, &C, SecretKeyDistribution::UniformTernary),
data: Pow2BGV::clone_ct(&P, &C, &ctxt),
sk: SecretKeyDistribution::UniformTernary
}],
Some(&rk),
&[],
&mut 0,
None
).into_iter().next().unwrap();
let res_C = Pow2BGV::mod_switch_down_C(&C, &res.dropped_rns_factor_indices);
let res_sk = Pow2BGV::mod_switch_sk(&res_C, &C, &sk);
let res_noise = Pow2BGV::noise_budget(&P, &res_C, &res.data, &res_sk);
assert_eq!(0, res_noise);
}
}
#[test]
fn test_level_digits() {
let a = [2, 2, 6, 6];
let b = [2, 2, 3, 3];
let k = 2;
let (c, d) = level_digits(&a, &b, k).unwrap();
println!("{:?}, {:?}", c, d);
assert!((0..4).all(|i| c[i] <= b[i]));
assert!((0..4).all(|i| b[i] - c[i] + d[i] <= a[i]));
assert!((0..4).all(|i| b[i] - c[i] + d[i] <= d.iter().copied().sum()));
assert!((0..4).filter(|i| b[*i] - c[*i] + d[*i] != 0).count() <= 3);
let a = [3, 3, 3, 3];
let b = [3, 3, 3, 3];
let k = 3;
let (c, d) = level_digits(&a, &b, k).unwrap();
println!("{:?}, {:?}", c, d);
assert!((0..4).all(|i| c[i] <= b[i]));
assert!((0..4).all(|i| b[i] - c[i] + d[i] <= a[i]));
assert!((0..4).all(|i| b[i] - c[i] + d[i] <= d.iter().copied().sum()));
assert!((0..4).filter(|i| b[*i] - c[*i] + d[*i] != 0).count() <= 4);
let a = [3, 3, 3, 3];
let b = [3, 3, 3, 3];
let k = 4;
let (c, d) = level_digits(&a, &b, k).unwrap();
println!("{:?}, {:?}", c, d);
assert!((0..4).all(|i| c[i] <= b[i]));
assert!((0..4).all(|i| b[i] - c[i] + d[i] <= a[i]));
assert!((0..4).all(|i| b[i] - c[i] + d[i] <= d.iter().copied().sum()));
assert!((0..4).filter(|i| b[*i] - c[*i] + d[*i] != 0).count() <= 4);
let a = [2, 4, 4, 4];
let b = [2, 2, 2, 2];
let k = 1;
let (c, d) = level_digits(&a, &b, k).unwrap();
println!("{:?}, {:?}", c, d);
assert!((0..4).all(|i| c[i] <= b[i]));
assert!((0..4).all(|i| b[i] - c[i] + d[i] <= a[i]));
assert!((0..4).all(|i| b[i] - c[i] + d[i] <= d.iter().copied().sum()));
assert!((0..4).filter(|i| b[*i] - c[*i] + d[*i] != 0).count() <= 4);
let a = [2, 3, 3, 4];
let b = [1, 2, 3, 4];
let k = 1;
assert!(level_digits(&a, &b, k).is_none());
let a = [3, 3, 3, 4];
let b = [1, 2, 3, 4];
let k = 1;
let (c, d) = level_digits(&a, &b, k).unwrap();
println!("{:?}, {:?}", c, d);
assert!((0..4).all(|i| c[i] <= b[i]));
assert!((0..4).all(|i| b[i] - c[i] + d[i] <= a[i]));
assert!((0..4).all(|i| b[i] - c[i] + d[i] <= d.iter().copied().sum()));
assert!((0..4).filter(|i| b[*i] - c[*i] + d[*i] != 0).count() <= 4);
let a = [3, 4, 5, 5];
let b = [1, 2, 3, 4];
let k = 1;
let (c, d) = level_digits(&a, &b, k).unwrap();
println!("{:?}, {:?}", c, d);
assert!((0..4).all(|i| c[i] <= b[i]));
assert!((0..4).all(|i| b[i] - c[i] + d[i] <= a[i]));
assert!((0..4).all(|i| b[i] - c[i] + d[i] <= d.iter().copied().sum()));
assert!((0..4).filter(|i| b[*i] - c[*i] + d[*i] != 0).count() <= 3);
}