Browse Source

bfv: get rid of constant generics (reason in previous commit)

rm-const-generics
arnaucube 2 weeks ago
parent
commit
78324a3a8d
2 changed files with 330 additions and 303 deletions
  1. +2
    -2
      arith/src/ring_nq.rs
  2. +328
    -301
      bfv/src/lib.rs

+ 2
- 2
arith/src/ring_nq.rs

@ -22,8 +22,8 @@ use crate::Ring;
/// The implementation assumes that q is prime.
#[derive(Clone)]
pub struct Rq {
pub(crate) q: u64, // TODO think if really needed or it's fine with coeffs[0].q
pub(crate) n: usize,
pub q: u64, // TODO think if really needed or it's fine with coeffs[0].q
pub n: usize,
pub(crate) coeffs: Vec<Zq>,

+ 328
- 301
bfv/src/lib.rs

@ -17,37 +17,38 @@ use arith::{Ring, Rq, R};
const ERR_SIGMA: f64 = 3.2;
#[derive(Clone, Debug)]
pub struct SecretKey<const Q: u64, const N: usize>(Rq<Q, N>);
pub struct SecretKey(Rq);
#[derive(Clone, Debug)]
pub struct PublicKey<const Q: u64, const N: usize>(Rq<Q, N>, Rq<Q, N>);
pub struct PublicKey(Rq, Rq);
/// Relinearization key
#[derive(Clone, Debug)]
pub struct RLK<const PQ: u64, const N: usize>(Rq<PQ, N>, Rq<PQ, N>);
pub struct RLK(Rq, Rq);
// RLWE ciphertext
#[derive(Clone, Debug)]
pub struct RLWE<const Q: u64, const N: usize>(Rq<Q, N>, Rq<Q, N>);
pub struct RLWE(Rq, Rq);
impl<const Q: u64, const N: usize> RLWE<Q, N> {
impl RLWE {
fn add(lhs: Self, rhs: Self) -> Self {
RLWE::<Q, N>(lhs.0 + rhs.0, lhs.1 + rhs.1)
RLWE(lhs.0 + rhs.0, lhs.1 + rhs.1)
}
pub fn remodule<const P: u64>(&self) -> RLWE<P, N> {
let x = self.0.remodule::<P>();
let y = self.1.remodule::<P>();
RLWE::<P, N>(x, y)
pub fn remodule(&self, p: u64) -> RLWE {
let x = self.0.remodule(p);
let y = self.1.remodule(p);
RLWE(x, y)
}
fn tensor<const PQ: u64, const T: u64>(a: &Self, b: &Self) -> (Rq<Q, N>, Rq<Q, N>, Rq<Q, N>) {
fn tensor(t: u64, a: &Self, b: &Self) -> (Rq, Rq, Rq) {
let (q, n) = (a.0.q, a.0.n);
// expand Q->PQ // TODO rm
// get the coefficients in Z, ie. interpret a,b \in R (instead of R_q)
let a0: R<N> = a.0.to_r();
let a1: R<N> = a.1.to_r();
let b0: R<N> = b.0.to_r();
let b1: R<N> = b.1.to_r();
let a0: R = a.0.clone().to_r(); // TODO rm clone()
let a1: R = a.1.clone().to_r();
let b0: R = b.0.clone().to_r();
let b1: R = b.1.clone().to_r();
// tensor (\in R) (2021-204 p.9)
// NOTE: here can use *, but at first versions want to make it explicit
@ -60,44 +61,50 @@ impl RLWE {
let c2: Vec<i64> = naive_mul(&a1, &b1);
// scale down, then reduce module Q, so result is \in R_q
let c0: Rq<Q, N> = arith::ring_n::mul_div_round::<Q, N>(c0, T, Q);
let c1: Rq<Q, N> = arith::ring_n::mul_div_round::<Q, N>(c1, T, Q);
let c2: Rq<Q, N> = arith::ring_n::mul_div_round::<Q, N>(c2, T, Q);
let c0: Rq = arith::ring_n::mul_div_round(q, n, c0, t, q);
let c1: Rq = arith::ring_n::mul_div_round(q, n, c1, t, q);
let c2: Rq = arith::ring_n::mul_div_round(q, n, c2, t, q);
(c0, c1, c2)
}
/// ciphertext multiplication
fn mul<const PQ: u64, const T: u64>(rlk: &RLK<PQ, N>, a: &Self, b: &Self) -> Self {
let (c0, c1, c2) = Self::tensor::<PQ, T>(a, b);
BFV::<Q, N, T>::relinearize_204::<PQ>(&rlk, &c0, &c1, &c2)
fn mul(t: u64, rlk: &RLK, a: &Self, b: &Self) -> Self {
let (c0, c1, c2) = Self::tensor(t, a, b);
BFV::relinearize_204(&rlk, &c0, &c1, &c2)
}
}
// naive mul in the ring Rq, reusing the ring_n::naive_mul and then applying mod(X^N +1)
fn tmp_naive_mul<const Q: u64, const N: usize>(a: Rq<Q, N>, b: Rq<Q, N>) -> Rq<Q, N> {
Rq::<Q, N>::from_vec_i64(arith::ring_n::naive_mul(&a.to_r(), &b.to_r()))
fn tmp_naive_mul(a: Rq, b: Rq) -> Rq {
Rq::from_vec_i64(a.q, a.n, arith::ring_n::naive_mul(&a.to_r(), &b.to_r()))
}
impl<const Q: u64, const N: usize> ops::Add<RLWE<Q, N>> for RLWE<Q, N> {
impl ops::Add<RLWE> for RLWE {
type Output = Self;
fn add(self, rhs: Self) -> Self {
Self::add(self, rhs)
}
}
impl<const Q: u64, const N: usize, const T: u64> ops::Add<&Rq<T, N>> for &RLWE<Q, N> {
type Output = RLWE<Q, N>;
fn add(self, rhs: &Rq<T, N>) -> Self::Output {
BFV::<Q, N, T>::add_const(self, rhs)
impl ops::Add<&Rq> for &RLWE {
type Output = RLWE;
fn add(self, rhs: &Rq) -> Self::Output {
BFV::add_const(self, rhs)
}
}
pub struct BFV<const Q: u64, const N: usize, const T: u64> {}
pub struct Params {
q: u64,
n: usize,
t: u64,
p: u64,
}
pub struct BFV {}
impl<const Q: u64, const N: usize, const T: u64> BFV<Q, N, T> {
const DELTA: u64 = Q / T; // floor
impl BFV {
// const DELTA: u64 = Q / T; // floor
/// generate a new key pair (privK, pubK)
pub fn new_key(mut rng: impl Rng) -> Result<(SecretKey<Q, N>, PublicKey<Q, N>)> {
pub fn new_key(mut rng: impl Rng, params: &Params) -> Result<(SecretKey, PublicKey)> {
// WIP: review probabilities
// let Xi_key = Uniform::new(-1_f64, 1_f64);
@ -105,114 +112,135 @@ impl BFV {
let Xi_err = Normal::new(0_f64, ERR_SIGMA)?;
// secret key
// let mut s = Rq::<Q, N>::rand_f64(&mut rng, Xi_key)?;
let mut s = Rq::<Q, N>::rand_u64(&mut rng, Xi_key)?;
// let mut s = Rq::rand_f64(&mut rng, Xi_key)?;
let mut s = Rq::rand_u64(&mut rng, Xi_key, params.q, params.n)?;
// since s is going to be multiplied by other Rq elements, already
// compute its NTT
s.compute_evals();
// pk = (-a * s + e, a)
let a = Rq::<Q, N>::rand_u64(&mut rng, Uniform::new(0_u64, Q))?;
let e = Rq::<Q, N>::rand_f64(&mut rng, Xi_err)?;
let pk: PublicKey<Q, N> = PublicKey((&(-a) * &s) + e, a.clone());
let a = Rq::rand_u64(&mut rng, Uniform::new(0_u64, params.q), params.q, params.n)?;
let e = Rq::rand_f64(&mut rng, Xi_err, params.q, params.n)?;
let pk: PublicKey = PublicKey(&(&(-a.clone()) * &s) + &e, a.clone()); // TODO rm clones
Ok((SecretKey(s), pk))
}
pub fn encrypt(mut rng: impl Rng, pk: &PublicKey<Q, N>, m: &Rq<T, N>) -> Result<RLWE<Q, N>> {
// note: m is modulus t
pub fn encrypt(mut rng: impl Rng, params: &Params, pk: &PublicKey, m: &Rq) -> Result<RLWE> {
// assert params & inputs
debug_assert_eq!(params.q, pk.0.q);
debug_assert_eq!(params.n, pk.0.n);
debug_assert_eq!(params.t, m.q);
debug_assert_eq!(params.n, m.n);
let Xi_key = Uniform::new(-1_f64, 1_f64);
// let Xi_key = Uniform::new(0_u64, 2_u64);
let Xi_err = Normal::new(0_f64, ERR_SIGMA)?;
let u = Rq::<Q, N>::rand_f64(&mut rng, Xi_key)?;
// let u = Rq::<Q, N>::rand_u64(&mut rng, Xi_key)?;
let e_1 = Rq::<Q, N>::rand_f64(&mut rng, Xi_err)?;
let e_2 = Rq::<Q, N>::rand_f64(&mut rng, Xi_err)?;
let u = Rq::rand_f64(&mut rng, Xi_key, params.q, params.n)?;
// let u = Rq::rand_u64(&mut rng, Xi_key)?;
let e_1 = Rq::rand_f64(&mut rng, Xi_err, params.q, params.n)?;
let e_2 = Rq::rand_f64(&mut rng, Xi_err, params.q, params.n)?;
// migrate m's coeffs to the bigger modulus Q (from T)
let m = m.remodule::<Q>();
let c0 = &pk.0 * &u + e_1 + m * Self::DELTA;
let m = m.remodule(params.q);
let c0 = &pk.0 * &u + e_1 + m * (params.q / params.t); // floor(q/t)=DELTA
let c1 = &pk.1 * &u + e_2;
Ok(RLWE::<Q, N>(c0, c1))
Ok(RLWE(c0, c1))
}
pub fn decrypt(sk: &SecretKey<Q, N>, c: &RLWE<Q, N>) -> Rq<T, N> {
let cs = c.0 + c.1 * sk.0; // done in mod q
pub fn decrypt(params: &Params, sk: &SecretKey, c: &RLWE) -> Rq {
debug_assert_eq!(params.q, sk.0.q);
debug_assert_eq!(params.n, sk.0.n);
debug_assert_eq!(params.q, c.0.q);
debug_assert_eq!(params.n, c.0.n);
let cs: Rq = &c.0 + &(&c.1 * &sk.0); // done in mod q
// same but with naive_mul:
// let c1s = arith::ring_n::naive_mul(&c.1.to_r(), &sk.0.to_r());
// let c1s = Rq::<Q, N>::from_vec_i64(c1s);
// let c1s = Rq::from_vec_i64(c1s);
// let cs = c.0 + c1s;
let r: Rq<Q, N> = cs.mul_div_round(T, Q);
r.remodule::<T>()
let r: Rq = cs.mul_div_round(params.t, params.q);
r.remodule(params.t)
}
fn add_const(c: &RLWE<Q, N>, m: &Rq<T, N>) -> RLWE<Q, N> {
fn add_const(c: &RLWE, m: &Rq) -> RLWE {
let q = c.0.q;
let t = m.q;
// assuming T<Q, move m from Zq<T> to Zq<Q>
let m = m.remodule::<Q>();
RLWE::<Q, N>(c.0 + m * Self::DELTA, c.1)
let m = m.remodule(c.0.q);
// TODO rm clones
RLWE(c.0.clone() + m * (q / t), c.1.clone()) // floor(q/t)=DELTA
}
fn mul_const<const PQ: u64>(rlk: &RLK<PQ, N>, c: &RLWE<Q, N>, m: &Rq<T, N>) -> RLWE<Q, N> {
fn mul_const(rlk: &RLK, c: &RLWE, m: &Rq) -> RLWE {
// let pq = rlk.0.q;
let q = c.0.q;
let n = c.0.n;
let t = m.q;
// assuming T<Q, move m from Zq<T> to Zq<Q>
let m = m.remodule::<Q>();
let m = m.remodule(q);
// encrypt m*Delta without noise, and then perform normal ciphertext multiplication
let md = RLWE::<Q, N>(m * Self::DELTA, Rq::zero());
RLWE::<Q, N>::mul::<PQ, T>(&rlk, &c, &md)
let md = RLWE(m * (q / t), Rq::zero((q, n))); // floor(q/t)=DELTA
RLWE::mul(t, &rlk, &c, &md)
}
fn rlk_key<const PQ: u64>(mut rng: impl Rng, s: &SecretKey<Q, N>) -> Result<RLK<PQ, N>> {
fn rlk_key(mut rng: impl Rng, params: &Params, s: &SecretKey) -> Result<RLK> {
let pq = params.p * params.q;
let n = params.n;
// TODO review using Xi' instead of Xi
let Xi_err = Normal::new(0_f64, ERR_SIGMA)?;
// let Xi_err = Normal::new(0_f64, 0.0)?;
let s = s.0.remodule::<PQ>();
let a = Rq::<PQ, N>::rand_u64(&mut rng, Uniform::new(0_u64, PQ))?;
let e = Rq::<PQ, N>::rand_f64(&mut rng, Xi_err)?;
let P = PQ / Q;
let s = s.0.remodule(pq);
let a = Rq::rand_u64(&mut rng, Uniform::new(0_u64, pq), pq, n)?;
let e = Rq::rand_f64(&mut rng, Xi_err, pq, n)?;
// let rlk: RLK<PQ, N> = RLK::<PQ, N>(-(&a * &s + e) + (s * s) * P, a.clone());
let rlk: RLK<PQ, N> = RLK::<PQ, N>(
-(tmp_naive_mul(a, s) + e) + tmp_naive_mul(s, s) * P,
// TODO rm clones
let rlk: RLK = RLK(
-(tmp_naive_mul(a.clone(), s.clone()) + e)
+ tmp_naive_mul(s.clone(), s.clone()) * params.p,
a.clone(),
);
Ok(rlk)
}
fn relinearize<const PQ: u64>(
rlk: &RLK<PQ, N>,
c0: &Rq<Q, N>,
c1: &Rq<Q, N>,
c2: &Rq<Q, N>,
) -> RLWE<Q, N> {
let P = PQ / Q;
fn relinearize(rlk: &RLK, c0: &Rq, c1: &Rq, c2: &Rq) -> RLWE {
let pq = rlk.0.q;
let q = c0.q;
let p = pq / q;
let n = c0.n;
let c2rlk0: Vec<f64> = (c2.to_r() * rlk.0.to_r())
let c2rlk0: Vec<f64> = (c2.clone().to_r() * rlk.0.clone().to_r())
.coeffs()
.iter()
.map(|e| (*e as f64 / P as f64).round())
.map(|e| (*e as f64 / p as f64).round())
.collect();
let c2rlk1: Vec<f64> = (c2.to_r() * rlk.1.to_r())
let c2rlk1: Vec<f64> = (c2.clone().to_r() * rlk.1.clone().to_r()) // TODO rm clones
.coeffs()
.iter()
.map(|e| (*e as f64 / P as f64).round())
.map(|e| (*e as f64 / p as f64).round())
.collect();
let r0 = Rq::<Q, N>::from_vec_f64(c2rlk0);
let r1 = Rq::<Q, N>::from_vec_f64(c2rlk1);
let r0 = Rq::from_vec_f64(q, n, c2rlk0);
let r1 = Rq::from_vec_f64(q, n, c2rlk1);
let res = RLWE::<Q, N>(c0 + &r0, c1 + &r1);
let res = RLWE(c0 + &r0, c1 + &r1);
res
}
fn relinearize_204<const PQ: u64>(
rlk: &RLK<PQ, N>,
c0: &Rq<Q, N>,
c1: &Rq<Q, N>,
c2: &Rq<Q, N>,
) -> RLWE<Q, N> {
let P = PQ / Q;
fn relinearize_204(rlk: &RLK, c0: &Rq, c1: &Rq, c2: &Rq) -> RLWE {
let pq = rlk.0.q;
let q = c0.q;
let p = pq / q;
let n = c0.n;
// TODO (in debug) check that all Ns match
// let c2rlk0: Rq<PQ, N> = c2.remodule::<PQ>() * rlk.0.remodule::<PQ>();
// let c2rlk1: Rq<PQ, N> = c2.remodule::<PQ>() * rlk.1.remodule::<PQ>();
@ -220,12 +248,12 @@ impl BFV {
// let r1: Rq<Q, N> = c2rlk1.mul_div_round(1, P).remodule::<Q>();
use arith::ring_n::naive_mul;
let c2rlk0: Vec<i64> = naive_mul(&c2.to_r(), &rlk.0.to_r());
let c2rlk1: Vec<i64> = naive_mul(&c2.to_r(), &rlk.1.to_r());
let r0: Rq<Q, N> = arith::ring_n::mul_div_round::<Q, N>(c2rlk0, 1, P);
let r1: Rq<Q, N> = arith::ring_n::mul_div_round::<Q, N>(c2rlk1, 1, P);
let c2rlk0: Vec<i64> = naive_mul(&c2.clone().to_r(), &rlk.0.clone().to_r()); // TODO rm clones
let c2rlk1: Vec<i64> = naive_mul(&c2.clone().to_r(), &rlk.1.clone().to_r());
let r0: Rq = arith::ring_n::mul_div_round(q, n, c2rlk0, 1, p);
let r1: Rq = arith::ring_n::mul_div_round(q, n, c2rlk1, 1, p);
let res = RLWE::<Q, N>(c0 + &r0, c1 + &r1);
let res = RLWE(c0 + &r0, c1 + &r1);
res
}
}
@ -239,21 +267,23 @@ mod tests {
#[test]
fn test_encrypt_decrypt() -> Result<()> {
const Q: u64 = 2u64.pow(16) + 1;
const N: usize = 512;
const T: u64 = 32; // plaintext modulus
type S = BFV<Q, N, T>;
let params = Params {
q: 2u64.pow(16) + 1, // q prime, and 2^q + 1 shape
n: 512,
t: 32, // plaintext modulus
p: 0, // unused in this test
};
let mut rng = rand::thread_rng();
for _ in 0..100 {
let (sk, pk) = S::new_key(&mut rng)?;
let (sk, pk) = BFV::new_key(&mut rng, &params)?;
let msg_dist = Uniform::new(0_u64, T);
let m = Rq::<T, N>::rand_u64(&mut rng, msg_dist)?;
let msg_dist = Uniform::new(0_u64, params.t);
let m = Rq::rand_u64(&mut rng, msg_dist, params.t, params.n)?;
let c = S::encrypt(&mut rng, &pk, &m)?;
let m_recovered = S::decrypt(&sk, &c);
let c = BFV::encrypt(&mut rng, &params, &pk, &m)?;
let m_recovered = BFV::decrypt(&params, &sk, &c);
assert_eq!(m, m_recovered);
}
@ -263,26 +293,28 @@ mod tests {
#[test]
fn test_addition() -> Result<()> {
const Q: u64 = 2u64.pow(16) + 1;
const N: usize = 128;
const T: u64 = 32; // plaintext modulus
type S = BFV<Q, N, T>;
let params = Params {
q: 2u64.pow(16) + 1, // q prime, and 2^q + 1 shape
n: 128,
t: 32, // plaintext modulus
p: 0, // unused in this test
};
let mut rng = rand::thread_rng();
for _ in 0..100 {
let (sk, pk) = S::new_key(&mut rng)?;
let (sk, pk) = BFV::new_key(&mut rng, &params)?;
let msg_dist = Uniform::new(0_u64, T);
let m1 = Rq::<T, N>::rand_u64(&mut rng, msg_dist)?;
let m2 = Rq::<T, N>::rand_u64(&mut rng, msg_dist)?;
let msg_dist = Uniform::new(0_u64, params.t);
let m1 = Rq::rand_u64(&mut rng, msg_dist, params.t, params.n)?;
let m2 = Rq::rand_u64(&mut rng, msg_dist, params.t, params.n)?;
let c1 = S::encrypt(&mut rng, &pk, &m1)?;
let c2 = S::encrypt(&mut rng, &pk, &m2)?;
let c1 = BFV::encrypt(&mut rng, &params, &pk, &m1)?;
let c2 = BFV::encrypt(&mut rng, &params, &pk, &m2)?;
let c3 = c1 + c2;
let m3_recovered = S::decrypt(&sk, &c3);
let m3_recovered = BFV::decrypt(&params, &sk, &c3);
assert_eq!(m1 + m2, m3_recovered);
}
@ -292,211 +324,210 @@ mod tests {
#[test]
fn test_constant_add_mul() -> Result<()> {
const Q: u64 = 2u64.pow(16) + 1;
const N: usize = 16;
const T: u64 = 8; // plaintext modulus
type S = BFV<Q, N, T>;
let q: u64 = 2u64.pow(16) + 1; // q prime, and 2^q + 1 shape
let params = Params {
q,
n: 16,
t: 8, // plaintext modulus
p: q * q,
};
let mut rng = rand::thread_rng();
let (sk, pk) = S::new_key(&mut rng)?;
let (sk, pk) = BFV::new_key(&mut rng, &params)?;
let msg_dist = Uniform::new(0_u64, T);
let m1 = Rq::<T, N>::rand_u64(&mut rng, msg_dist)?;
let m2_const = Rq::<T, N>::rand_u64(&mut rng, msg_dist)?;
let c1 = S::encrypt(&mut rng, &pk, &m1)?;
let msg_dist = Uniform::new(0_u64, params.t);
let m1 = Rq::rand_u64(&mut rng, msg_dist, params.t, params.n)?;
let m2_const = Rq::rand_u64(&mut rng, msg_dist, params.t, params.n)?;
let c1 = BFV::encrypt(&mut rng, &params, &pk, &m1)?;
let c3_add = &c1 + &m2_const;
let m3_add_recovered = S::decrypt(&sk, &c3_add);
assert_eq!(m1 + m2_const, m3_add_recovered);
let m3_add_recovered = BFV::decrypt(&params, &sk, &c3_add);
assert_eq!(&m1 + &m2_const, m3_add_recovered);
// test multiplication of a ciphertext by a constant
const P: u64 = Q * Q;
const PQ: u64 = P * Q;
let rlk = BFV::<Q, N, T>::rlk_key::<PQ>(&mut rng, &sk)?;
let rlk = BFV::rlk_key(&mut rng, &params, &sk)?;
let c3_mul = S::mul_const(&rlk, &c1, &m2_const);
let c3_mul = BFV::mul_const(&rlk, &c1, &m2_const);
let m3_mul_recovered = S::decrypt(&sk, &c3_mul);
let m3_mul_recovered = BFV::decrypt(&params, &sk, &c3_mul);
assert_eq!(
(m1.to_r() * m2_const.to_r()).to_rq::<T>().coeffs(),
(m1.to_r() * m2_const.to_r()).to_rq(params.t).coeffs(),
m3_mul_recovered.coeffs()
);
Ok(())
}
// TMP WIP
#[test]
#[ignore]
fn test_params() -> Result<()> {
const Q: u64 = 2u64.pow(16) + 1; // q prime, and 2^q + 1 shape
const N: usize = 32;
const T: u64 = 8; // plaintext modulus
const P: u64 = Q * Q;
const PQ: u64 = P * Q;
const DELTA: u64 = Q / T; // floor
let mut rng = rand::thread_rng();
let Xi_key = Uniform::new(0_f64, 1_f64);
let Xi_err = Normal::new(0_f64, ERR_SIGMA)?;
let s = Rq::<Q, N>::rand_f64(&mut rng, Xi_key)?;
let e = Rq::<Q, N>::rand_f64(&mut rng, Xi_err)?;
let u = Rq::<Q, N>::rand_f64(&mut rng, Xi_key)?;
let e_0 = Rq::<Q, N>::rand_f64(&mut rng, Xi_err)?;
let e_1 = Rq::<Q, N>::rand_f64(&mut rng, Xi_err)?;
let m = Rq::<Q, N>::rand_u64(&mut rng, Uniform::new(0_u64, T))?;
// v_fresh
let v: Rq<Q, N> = u * e + e_1 * s + e_0;
let q: f64 = Q as f64;
let t: f64 = T as f64;
let n: f64 = N as f64;
let delta: f64 = DELTA as f64;
// r_t(q)/t should be equal to q/t-Δ
assert_eq!(
// r_t(q)/t, where r_t(q)=q mod t
(q % t) / t,
// Δt/Q = q - r_t(Q)/Q, so r_t(Q)=q - Δt
(q / t) - delta
);
let rt: f64 = (q % t) / t;
dbg!(&rt);
dbg!(v.infinity_norm());
let bound: f64 = (q / (2_f64 * t)) - (rt / 2_f64);
dbg!(bound);
assert!((v.infinity_norm() as f64) < bound);
let max_v_infnorm = bound - 1.0;
// addition noise
let v_add: Rq<Q, N> = v + v + u * rt;
let v_add: Rq<Q, N> = v_add + v_add + u * rt;
assert!((v_add.infinity_norm() as f64) < bound);
// multiplication noise
let (_, pk) = BFV::<Q, N, T>::new_key(&mut rng)?;
let c = BFV::<Q, N, T>::encrypt(&mut rng, &pk, &m.remodule::<T>())?;
let b_key: f64 = 1_f64;
// ef: expansion factor
let ef: f64 = 2.0 * n.sqrt();
let bound: f64 = ((ef * t) / 2.0)
* ((2.0 * max_v_infnorm * max_v_infnorm) / q
+ (4.0 + ef * b_key) * (max_v_infnorm + max_v_infnorm)
+ rt * (ef * b_key + 5.0))
+ (1.0 + ef * b_key + ef * ef * b_key * b_key) / 2.0;
dbg!(&bound);
let k: Vec<f64> = (c.0 + c.1 * s - m * delta - v)
.coeffs()
.iter()
.map(|e_i| e_i.0 as f64 / q)
.collect();
let k = Rq::<Q, N>::from_vec_f64(k);
let v_tensor_0 = (v * v)
.coeffs()
.iter()
.map(|e_i| (e_i.0 as f64 * t) / q)
.collect::<Vec<f64>>();
let v_tensor_0 = Rq::<Q, N>::from_vec_f64(v_tensor_0);
let v_tensor_1 = ((m * v) + (m * v))
.coeffs()
.iter()
.map(|e_i| (e_i.0 as f64 * t * delta) / q)
.collect::<Vec<f64>>();
let v_tensor_1 = Rq::<Q, N>::from_vec_f64(v_tensor_1);
let v_tensor_2: Rq<Q, N> = (v * k + v * k) * t;
let rm: f64 = (ef * t) / 2.0;
let rm: Rq<Q, N> = Rq::<Q, N>::from_vec_f64(vec![rm; N]);
let v_tensor_3: Rq<Q, N> = (m * k
+ m * k
+ rm
+ Rq::from_vec_f64(
((m * m) * DELTA)
.coeffs()
.iter()
.map(|e_i| e_i.0 as f64 / q)
.collect::<Vec<f64>>(),
))
* rt;
let v_tensor = v_tensor_0 + v_tensor_1 + v_tensor_2 - v_tensor_3;
let v_r = (1.0 + ef * b_key + ef * ef * b_key * b_key) / 2.0;
let v_mult_norm = v_tensor.infinity_norm() as f64 + v_r;
dbg!(&v_mult_norm);
dbg!(&bound);
assert!(v_mult_norm < bound);
// let m1 = Rq::<T, N>::zero();
// let m2 = Rq::<T, N>::zero();
// let (_, pk) = BFV::<Q, N, T>::new_key(&mut rng)?;
// let c1 = BFV::<Q, N, T>::encrypt(&mut rng, &pk, &m1)?;
// let c2 = BFV::<Q, N, T>::encrypt(&mut rng, &pk, &m2)?;
// let (c_a, c_b, c_c) = RLWE::<Q, N>::tensor::<PQ, T>(&c1, &c2);
// dbg!(&c_a.infinity_norm());
// dbg!(&c_b.infinity_norm());
// dbg!(&c_c.infinity_norm());
// assert!((c_a.infinity_norm() as f64) < bound);
// assert!((c_b.infinity_norm() as f64) < bound);
// assert!((c_c.infinity_norm() as f64) < bound);
// WIP
Ok(())
}
/*
// TMP WIP
#[test]
#[ignore]
fn test_params() -> Result<()> {
const Q: u64 = 2u64.pow(16) + 1; // q prime, and 2^q + 1 shape
const N: usize = 32;
const T: u64 = 8; // plaintext modulus
const P: u64 = Q * Q;
const PQ: u64 = P * Q;
const DELTA: u64 = Q / T; // floor
let mut rng = rand::thread_rng();
let Xi_key = Uniform::new(0_f64, 1_f64);
let Xi_err = Normal::new(0_f64, ERR_SIGMA)?;
let s = Rq::rand_f64(&mut rng, Xi_key)?;
let e = Rq::rand_f64(&mut rng, Xi_err)?;
let u = Rq::rand_f64(&mut rng, Xi_key)?;
let e_0 = Rq::rand_f64(&mut rng, Xi_err)?;
let e_1 = Rq::rand_f64(&mut rng, Xi_err)?;
let m = Rq::rand_u64(&mut rng, Uniform::new(0_u64, T))?;
// v_fresh
let v: Rq<Q, N> = u * e + e_1 * s + e_0;
let q: f64 = Q as f64;
let t: f64 = T as f64;
let n: f64 = N as f64;
let delta: f64 = DELTA as f64;
// r_t(q)/t should be equal to q/t-Δ
assert_eq!(
// r_t(q)/t, where r_t(q)=q mod t
(q % t) / t,
// Δt/Q = q - r_t(Q)/Q, so r_t(Q)=q - Δt
(q / t) - delta
);
let rt: f64 = (q % t) / t;
dbg!(&rt);
dbg!(v.infinity_norm());
let bound: f64 = (q / (2_f64 * t)) - (rt / 2_f64);
dbg!(bound);
assert!((v.infinity_norm() as f64) < bound);
let max_v_infnorm = bound - 1.0;
// addition noise
let v_add: Rq<Q, N> = v + v + u * rt;
let v_add: Rq<Q, N> = v_add + v_add + u * rt;
assert!((v_add.infinity_norm() as f64) < bound);
// multiplication noise
let (_, pk) = BFV::<Q, N, T>::new_key(&mut rng)?;
let c = BFV::<Q, N, T>::encrypt(&mut rng, &pk, &m.remodule::<T>())?;
let b_key: f64 = 1_f64;
// ef: expansion factor
let ef: f64 = 2.0 * n.sqrt();
let bound: f64 = ((ef * t) / 2.0)
* ((2.0 * max_v_infnorm * max_v_infnorm) / q
+ (4.0 + ef * b_key) * (max_v_infnorm + max_v_infnorm)
+ rt * (ef * b_key + 5.0))
+ (1.0 + ef * b_key + ef * ef * b_key * b_key) / 2.0;
dbg!(&bound);
let k: Vec<f64> = (c.0 + c.1 * s - m * delta - v)
.coeffs()
.iter()
.map(|e_i| e_i.0 as f64 / q)
.collect();
let k = Rq::from_vec_f64(k);
let v_tensor_0 = (v * v)
.coeffs()
.iter()
.map(|e_i| (e_i.0 as f64 * t) / q)
.collect::<Vec<f64>>();
let v_tensor_0 = Rq::from_vec_f64(v_tensor_0);
let v_tensor_1 = ((m * v) + (m * v))
.coeffs()
.iter()
.map(|e_i| (e_i.0 as f64 * t * delta) / q)
.collect::<Vec<f64>>();
let v_tensor_1 = Rq::from_vec_f64(v_tensor_1);
let v_tensor_2: Rq<Q, N> = (v * k + v * k) * t;
let rm: f64 = (ef * t) / 2.0;
let rm: Rq<Q, N> = Rq::from_vec_f64(vec![rm; N]);
let v_tensor_3: Rq<Q, N> = (m * k
+ m * k
+ rm
+ Rq::from_vec_f64(
((m * m) * DELTA)
.coeffs()
.iter()
.map(|e_i| e_i.0 as f64 / q)
.collect::<Vec<f64>>(),
))
* rt;
let v_tensor = v_tensor_0 + v_tensor_1 + v_tensor_2 - v_tensor_3;
let v_r = (1.0 + ef * b_key + ef * ef * b_key * b_key) / 2.0;
let v_mult_norm = v_tensor.infinity_norm() as f64 + v_r;
dbg!(&v_mult_norm);
dbg!(&bound);
assert!(v_mult_norm < bound);
// let m1 = Rq::<T, N>::zero();
// let m2 = Rq::<T, N>::zero();
// let (_, pk) = BFV::<Q, N, T>::new_key(&mut rng)?;
// let c1 = BFV::<Q, N, T>::encrypt(&mut rng, &pk, &m1)?;
// let c2 = BFV::<Q, N, T>::encrypt(&mut rng, &pk, &m2)?;
// let (c_a, c_b, c_c) = RLWE::tensor::<PQ, T>(&c1, &c2);
// dbg!(&c_a.infinity_norm());
// dbg!(&c_b.infinity_norm());
// dbg!(&c_c.infinity_norm());
// assert!((c_a.infinity_norm() as f64) < bound);
// assert!((c_b.infinity_norm() as f64) < bound);
// assert!((c_c.infinity_norm() as f64) < bound);
// WIP
Ok(())
}
*/
#[test]
fn test_tensor() -> Result<()> {
const Q: u64 = 2u64.pow(16) + 1; // q prime, and 2^q + 1 shape
const N: usize = 16;
const T: u64 = 2; // plaintext modulus
// const P: u64 = Q;
const P: u64 = Q * Q;
const PQ: u64 = P * Q;
let q: u64 = 2u64.pow(16) + 1; // q prime, and 2^q + 1 shape
let params = Params {
q,
n: 16,
t: 2, // plaintext modulus
p: q * q,
};
let mut rng = rand::thread_rng();
let msg_dist = Uniform::new(0_u64, T);
let msg_dist = Uniform::new(0_u64, params.t);
for _ in 0..1_000 {
let m1 = Rq::<T, N>::rand_u64(&mut rng, msg_dist)?;
let m2 = Rq::<T, N>::rand_u64(&mut rng, msg_dist)?;
let m1 = Rq::rand_u64(&mut rng, msg_dist, params.t, params.n)?;
let m2 = Rq::rand_u64(&mut rng, msg_dist, params.t, params.n)?;
test_tensor_opt::<Q, N, T, PQ>(&mut rng, m1, m2)?;
test_tensor_opt(&mut rng, &params, m1, m2)?;
}
Ok(())
}
fn test_tensor_opt<const Q: u64, const N: usize, const T: u64, const PQ: u64>(
mut rng: impl Rng,
m1: Rq<T, N>,
m2: Rq<T, N>,
) -> Result<()> {
let (sk, pk) = BFV::<Q, N, T>::new_key(&mut rng)?;
fn test_tensor_opt(mut rng: impl Rng, params: &Params, m1: Rq, m2: Rq) -> Result<()> {
let (sk, pk) = BFV::new_key(&mut rng, &params)?;
let c1 = BFV::<Q, N, T>::encrypt(&mut rng, &pk, &m1)?;
let c2 = BFV::<Q, N, T>::encrypt(&mut rng, &pk, &m2)?;
let c1 = BFV::encrypt(&mut rng, &params, &pk, &m1)?;
let c2 = BFV::encrypt(&mut rng, &params, &pk, &m2)?;
let (c_a, c_b, c_c) = RLWE::<Q, N>::tensor::<PQ, T>(&c1, &c2);
// let (c_a, c_b, c_c) = RLWE::<Q, N>::tensor_new::<PQ, T>(&c1, &c2);
let (c_a, c_b, c_c) = RLWE::tensor(params.t, &c1, &c2);
// let (c_a, c_b, c_c) = RLWE::tensor_new::<PQ, T>(&c1, &c2);
// decrypt non-relinearized mul result
let m3: Rq<Q, N> = c_a + c_b * sk.0 + c_c * sk.0 * sk.0;
let m3: Rq = c_a + &c_b * &sk.0 + &c_c * &(&sk.0 * &sk.0);
// let m3: Rq<Q, N> = c_a
// + Rq::<Q, N>::from_vec_i64(arith::ring_n::naive_mul(&c_b.to_r(), &sk.0.to_r()))
// + Rq::<Q, N>::from_vec_i64(arith::ring_n::naive_mul(
// + Rq::from_vec_i64(arith::ring_n::naive_mul(&c_b.to_r(), &sk.0.to_r()))
// + Rq::from_vec_i64(arith::ring_n::naive_mul(
// &c_c.to_r(),
// &R::<N>::from_vec(arith::ring_n::naive_mul(&sk.0.to_r(), &sk.0.to_r())),
// ));
let m3: Rq<Q, N> = m3.mul_div_round(T, Q); // descale
let m3 = m3.remodule::<T>();
let m3: Rq = m3.mul_div_round(params.t, params.q); // descale
let m3 = m3.remodule(params.t);
let naive = (m1.to_r() * m2.to_r()).to_rq::<T>();
let naive = (m1.clone().to_r() * m2.clone().to_r()).to_rq(params.t); // TODO rm clones
assert_eq!(
m3.coeffs().to_vec(),
naive.coeffs().to_vec(),
@ -510,44 +541,40 @@ mod tests {
#[test]
fn test_mul_relin() -> Result<()> {
const Q: u64 = 2u64.pow(16) + 1;
const N: usize = 16;
const T: u64 = 2; // plaintext modulus
type S = BFV<Q, N, T>;
const P: u64 = Q * Q;
const PQ: u64 = P * Q;
let q: u64 = 2u64.pow(16) + 1; // q prime, and 2^q + 1 shape
let params = Params {
q,
n: 16,
t: 2, // plaintext modulus
p: q * q,
};
let mut rng = rand::thread_rng();
let msg_dist = Uniform::new(0_u64, T);
let msg_dist = Uniform::new(0_u64, params.t);
for _ in 0..1_000 {
let m1 = Rq::<T, N>::rand_u64(&mut rng, msg_dist)?;
let m2 = Rq::<T, N>::rand_u64(&mut rng, msg_dist)?;
let m1 = Rq::rand_u64(&mut rng, msg_dist, params.t, params.n)?;
let m2 = Rq::rand_u64(&mut rng, msg_dist, params.t, params.n)?;
test_mul_relin_opt::<Q, N, T, PQ>(&mut rng, m1, m2)?;
test_mul_relin_opt(&mut rng, &params, m1, m2)?;
}
Ok(())
}
fn test_mul_relin_opt<const Q: u64, const N: usize, const T: u64, const PQ: u64>(
mut rng: impl Rng,
m1: Rq<T, N>,
m2: Rq<T, N>,
) -> Result<()> {
let (sk, pk) = BFV::<Q, N, T>::new_key(&mut rng)?;
fn test_mul_relin_opt(mut rng: impl Rng, params: &Params, m1: Rq, m2: Rq) -> Result<()> {
let (sk, pk) = BFV::new_key(&mut rng, &params)?;
let rlk = BFV::<Q, N, T>::rlk_key::<PQ>(&mut rng, &sk)?;
let rlk = BFV::rlk_key(&mut rng, &params, &sk)?;
let c1 = BFV::<Q, N, T>::encrypt(&mut rng, &pk, &m1)?;
let c2 = BFV::<Q, N, T>::encrypt(&mut rng, &pk, &m2)?;
let c1 = BFV::encrypt(&mut rng, &params, &pk, &m1)?;
let c2 = BFV::encrypt(&mut rng, &params, &pk, &m2)?;
let c3 = RLWE::<Q, N>::mul::<PQ, T>(&rlk, &c1, &c2); // uses relinearize internally
let c3 = RLWE::mul(params.t, &rlk, &c1, &c2); // uses relinearize internally
let m3 = BFV::<Q, N, T>::decrypt(&sk, &c3);
let m3 = BFV::decrypt(&params, &sk, &c3);
let naive = (m1.to_r() * m2.to_r()).to_rq::<T>();
let naive = (m1.clone().to_r() * m2.clone().to_r()).to_rq(params.t); // TODO rm clones
assert_eq!(
m3.coeffs().to_vec(),
naive.coeffs().to_vec(),

Loading…
Cancel
Save