bfv: get rid of constant generics (reason in previous commit)
arnaucube git@arnaucube.com
Mon, 11 Aug 2025 19:35:48 +0000
2 files changed,
316 insertions(+),
289 deletions(-)
M
arith/src/ring_nq.rs
→
arith/src/ring_nq.rs
@@ -22,8 +22,8 @@ /// PolynomialRing element, where the PolynomialRing is R = Z_q[X]/(X^n +1)
/// 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>,
M
bfv/src/lib.rs
→
bfv/src/lib.rs
@@ -17,37 +17,38 @@ // sigma=3.2 from: https://eprint.iacr.org/2022/162.pdf page 5
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 @@ let c1: Vec<i64> = itertools::zip_eq(c1_l, c1_r).map(|(l, r)| l + r).collect();
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 @@ let Xi_key = Uniform::new(0_u64, 2_u64);
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 @@ // let r0: Rq<Q, N> = c2rlk0.mul_div_round(1, P).remodule::<Q>();
// 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 @@ use super::*;
#[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, ¶ms)?; - 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, ¶ms, &pk, &m)?; + let m_recovered = BFV::decrypt(¶ms, &sk, &c); assert_eq!(m, m_recovered); }@@ -263,26 +293,28 @@ }
#[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, ¶ms)?; - 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, ¶ms, &pk, &m1)?; + let c2 = BFV::encrypt(&mut rng, ¶ms, &pk, &m2)?; let c3 = c1 + c2; - let m3_recovered = S::decrypt(&sk, &c3); + let m3_recovered = BFV::decrypt(¶ms, &sk, &c3); assert_eq!(m1 + m2, m3_recovered); }@@ -292,211 +324,210 @@ }
#[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, ¶ms)?; - 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, ¶ms, &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(¶ms, &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, ¶ms, &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(¶ms, &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 + /* + // 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 + const P: u64 = Q * Q; + const PQ: u64 = P * Q; + const DELTA: u64 = Q / T; // floor - let mut rng = rand::thread_rng(); + 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 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))?; + 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; + // 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; + 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); + // 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; + 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); + // 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); + // 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 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 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 + // 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(()) - } + 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, ¶ms, 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, ¶ms)?; - 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, ¶ms, &pk, &m1)?; + let c2 = BFV::encrypt(&mut rng, ¶ms, &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 @@ }
#[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, ¶ms, 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, ¶ms)?; - let rlk = BFV::<Q, N, T>::rlk_key::<PQ>(&mut rng, &sk)?; + let rlk = BFV::rlk_key(&mut rng, ¶ms, &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, ¶ms, &pk, &m1)?; + let c2 = BFV::encrypt(&mut rng, ¶ms, &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(¶ms, &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(),