mirror of
https://github.com/arnaucube/fhe-study.git
synced 2026-01-24 04:33:52 +01:00
work on tensor, fix mul by constant(plaintext)
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
# fhe-study
|
||||
Code done while studying some FHE papers, with the idea of doing implementations from scratch.
|
||||
Implementations from scratch done while studying some FHE papers.
|
||||
|
||||
- arithmetic: contains $\mathbb{Z}_q$ and $\mathbb{Z}_q[X]/(X^N+1)$ arithmetic implementations, together with the NTT implementation.
|
||||
- arithmetic: contains $\mathbb{Z}_q$, $R_q=\mathbb{Z}_q[X]/(X^N+1)$ and $R=\mathbb{Z}[X]/(X^N+1)$ arithmetic implementations, together with the NTT implementation.
|
||||
- bfv: https://eprint.iacr.org/2012/144.pdf scheme implementation
|
||||
|
||||
@@ -59,6 +59,7 @@ impl<const N: usize> R<N> {
|
||||
crate::Rq::<Q, N>::from_vec_f64(r)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn mul_div_round<const Q: u64, const N: usize>(
|
||||
v: Vec<i64>,
|
||||
num: u64,
|
||||
@@ -73,6 +74,7 @@ pub fn mul_div_round<const Q: u64, const N: usize>(
|
||||
crate::Rq::<Q, N>::from_vec_f64(r)
|
||||
}
|
||||
|
||||
// TODO rename to make it clear that is not mod q, but mod X^N+1
|
||||
// apply mod (X^N+1)
|
||||
pub fn modulus<const N: usize>(p: &mut Vec<i64>) {
|
||||
if p.len() < N {
|
||||
@@ -84,6 +86,16 @@ pub fn modulus<const N: usize>(p: &mut Vec<i64>) {
|
||||
}
|
||||
p.truncate(N);
|
||||
}
|
||||
pub fn modulus_i128<const N: usize>(p: &mut Vec<i128>) {
|
||||
if p.len() < N {
|
||||
return;
|
||||
}
|
||||
for i in N..p.len() {
|
||||
p[i - N] = p[i - N].clone() - p[i].clone();
|
||||
p[i] = 0;
|
||||
}
|
||||
p.truncate(N);
|
||||
}
|
||||
|
||||
impl<const N: usize> PartialEq for R<N> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
@@ -133,7 +145,7 @@ impl<const N: usize> ops::Mul<&R<N>> for &R<N> {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO with NTT(?)
|
||||
// TODO WIP
|
||||
pub fn naive_poly_mul<const N: usize>(poly1: &R<N>, poly2: &R<N>) -> R<N> {
|
||||
let poly1: Vec<i128> = poly1.0.iter().map(|c| *c as i128).collect();
|
||||
let poly2: Vec<i128> = poly2.0.iter().map(|c| *c as i128).collect();
|
||||
@@ -147,15 +159,35 @@ pub fn naive_poly_mul<const N: usize>(poly1: &R<N>, poly2: &R<N>) -> R<N> {
|
||||
// apply mod (X^N + 1))
|
||||
R::<N>::from_vec(result.iter().map(|c| *c as i64).collect())
|
||||
}
|
||||
|
||||
pub fn naive_mul<const N: usize>(poly1: &R<N>, poly2: &R<N>) -> Vec<i64> {
|
||||
let poly1: Vec<i128> = poly1.0.iter().map(|c| *c as i128).collect();
|
||||
let poly2: Vec<i128> = poly2.0.iter().map(|c| *c as i128).collect();
|
||||
let mut result = vec![0; (N * 2) - 1];
|
||||
let mut result: Vec<i128> = vec![0; (N * 2) - 1];
|
||||
for i in 0..N {
|
||||
for j in 0..N {
|
||||
result[i + j] = result[i + j] + poly1[i] * poly2[j];
|
||||
}
|
||||
}
|
||||
|
||||
modulus_i128::<N>(&mut result);
|
||||
// for c_i in result.iter() {
|
||||
// println!("---");
|
||||
// println!("{:?}", &c_i);
|
||||
// println!("{:?}", *c_i as i64);
|
||||
// println!("{:?}", (*c_i as i64) as i128);
|
||||
// assert_eq!(*c_i, (*c_i as i64) as i128, "{:?}", c_i);
|
||||
// }
|
||||
|
||||
// let q: i128 = 65537;
|
||||
// let result: Vec<i64> = result
|
||||
// .iter()
|
||||
// // .map(|c_i| ((c_i % q + q) % q) as i64)
|
||||
// .map(|c_i| (c_i % q) as i64)
|
||||
// // .map(|c_i| *c_i as i64)
|
||||
// .collect();
|
||||
// result
|
||||
|
||||
result.iter().map(|c| *c as i64).collect()
|
||||
}
|
||||
|
||||
|
||||
@@ -56,6 +56,13 @@ impl<const Q: u64, const N: usize> Rq<Q, N> {
|
||||
crate::R::<N>::from(self)
|
||||
}
|
||||
|
||||
pub fn zero() -> Self {
|
||||
let coeffs = array::from_fn(|_| Zq::zero());
|
||||
Self {
|
||||
coeffs,
|
||||
evals: None,
|
||||
}
|
||||
}
|
||||
pub fn from_vec(coeffs: Vec<Zq<Q>>) -> Self {
|
||||
let mut p = coeffs;
|
||||
modulus::<Q, N>(&mut p);
|
||||
|
||||
265
bfv/src/lib.rs
265
bfv/src/lib.rs
@@ -10,12 +10,13 @@ use rand::Rng;
|
||||
use rand_distr::{Normal, Uniform};
|
||||
use std::ops;
|
||||
|
||||
use arithmetic::{Rq, Zq, R};
|
||||
use arithmetic::{Rq, R};
|
||||
|
||||
// error deviation for the Gaussian(Normal) distribution
|
||||
// sigma=3.2 from: https://eprint.iacr.org/2022/162.pdf page 5
|
||||
const ERR_SIGMA: f64 = 3.2;
|
||||
// const ERR_SIGMA: f64 = 0.0; // TODO WIP
|
||||
// const ERR_SIGMA: f64 = 1.0; // TODO WIP
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct SecretKey<const Q: u64, const N: usize>(Rq<Q, N>);
|
||||
@@ -48,21 +49,23 @@ impl<const Q: u64, const N: usize> RLWE<Q, N> {
|
||||
}
|
||||
fn tensor<const PQ: u64, const T: u64>(a: &Self, b: &Self) -> (Rq<Q, N>, Rq<Q, N>, Rq<Q, 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();
|
||||
|
||||
// tensor (\in R)
|
||||
// tensor (\in R) (2021-204 p.9)
|
||||
use arithmetic::ring::naive_mul;
|
||||
// (here can use *, but want to make it explicit that we're using the naive mul)
|
||||
let c0: Vec<i64> = naive_mul(&a0, &b0);
|
||||
let c1_l: Vec<i64> = naive_mul(&a0, &b1);
|
||||
let c1_r = naive_mul(&a1, &b0);
|
||||
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, module Q, so result is \in R_q
|
||||
// scale down, then reduce module Q, so result is \in R_q
|
||||
let c0: Rq<Q, N> = arithmetic::ring::mul_div_round::<Q, N>(c0, T, Q);
|
||||
let c1: Rq<Q, N> = arithmetic::ring::mul_div_round::<Q, N>(c1, T, Q);
|
||||
let c2: Rq<Q, N> = arithmetic::ring::mul_div_round::<Q, N>(c2, T, Q);
|
||||
@@ -119,76 +122,73 @@ impl<const Q: u64, const N: usize, const T: u64> ops::Add<&Rq<T, N>> for &RLWE<Q
|
||||
BFV::<Q, N, T>::add_const(self, rhs)
|
||||
}
|
||||
}
|
||||
impl<const Q: u64, const N: usize, const T: u64> ops::Mul<&Rq<T, N>> for &RLWE<Q, N> {
|
||||
type Output = RLWE<Q, N>;
|
||||
fn mul(self, rhs: &Rq<T, N>) -> Self::Output {
|
||||
BFV::<Q, N, T>::mul_const(&self, rhs)
|
||||
}
|
||||
}
|
||||
// impl<const Q: u64, const N: usize, const T: u64> ops::Mul<&Rq<T, N>> for &RLWE<Q, N> {
|
||||
// type Output = RLWE<Q, N>;
|
||||
// fn mul(self, rhs: &Rq<T, N>) -> Self::Output {
|
||||
// BFV::<Q, N, T>::mul_const(&self, rhs)
|
||||
// }
|
||||
// }
|
||||
|
||||
pub struct BFV<const Q: u64, const N: usize, const T: u64> {}
|
||||
|
||||
impl<const Q: u64, const N: usize, const T: u64> BFV<Q, N, T> {
|
||||
const DELTA: u64 = Q / T;
|
||||
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>)> {
|
||||
// WIP: review probabilities
|
||||
|
||||
// let Xi_key = Uniform::new(-1_f64, 1_f64);
|
||||
let Xi_key = Uniform::new(0_u64, 2_u64);
|
||||
let Xi_key = Uniform::new(-1_f64, 1_f64);
|
||||
// let Xi_key = Uniform::new(0_u64, 2_u64);
|
||||
// use rand::distributions::Bernoulli;
|
||||
// let Xi_key = Bernoulli::new(0.5)?;
|
||||
let Xi_err = Normal::new(0_f64, ERR_SIGMA)?;
|
||||
// let Xi_err = Normal::new(0_f64, 0.0)?;
|
||||
|
||||
// secret key
|
||||
let s = Rq::<Q, N>::rand_u64(&mut rng, Xi_key)?;
|
||||
let s = Rq::<Q, N>::rand_f64(&mut rng, Xi_key)?;
|
||||
// let s = Rq::<Q, N>::rand_u64(&mut rng, Xi_key)?;
|
||||
|
||||
#[cfg(test)] // sanity check
|
||||
assert!(s.infinity_norm() <= 1, "{:?}", s.coeffs());
|
||||
// #[cfg(test)] // sanity check
|
||||
// assert!(
|
||||
// s.infinity_norm() <= 1,
|
||||
// "s.infinity_norm check failed {:?}",
|
||||
// s.coeffs()
|
||||
// );
|
||||
|
||||
// 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)?;
|
||||
// println!("e{:?}", e.coeffs());
|
||||
let pk: PublicKey<Q, N> = PublicKey((&(-a) * &s) + e, a.clone());
|
||||
Ok((SecretKey(s), pk))
|
||||
}
|
||||
|
||||
pub fn encrypt(mut rng: impl Rng, pk: &PublicKey<Q, N>, m: &Rq<T, N>) -> Result<RLWE<Q, N>> {
|
||||
// let Xi_key = Uniform::new(-1_f64, 1_f64);
|
||||
// let Xi_key = Uniform::new(0_f64, 2_f64);
|
||||
let Xi_key = Uniform::new(0_u64, 2_u64);
|
||||
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 Xi_err = Normal::new(0_f64, 0.0)?;
|
||||
|
||||
let u = Rq::<Q, N>::rand_u64(&mut rng, Xi_key)?;
|
||||
let e_1 = Rq::<Q, N>::rand_f64_abs(&mut rng, Xi_err)?;
|
||||
let e_2 = Rq::<Q, N>::rand_f64_abs(&mut rng, Xi_err)?;
|
||||
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)?;
|
||||
// println!("e_1{:?}", e_1.coeffs());
|
||||
// println!("e_2{:?}", e_2.coeffs());
|
||||
|
||||
// println!("{:?}", &e_1.coeffs());
|
||||
// println!("{:?}", &e_2.coeffs());
|
||||
|
||||
#[cfg(test)] // sanity check
|
||||
assert!(u.infinity_norm() <= 1, "{:?}", u.coeffs());
|
||||
// #[cfg(test)] // sanity check
|
||||
// assert!(
|
||||
// u.infinity_norm() <= 1,
|
||||
// "u.infinity_norm check failed {:?}",
|
||||
// u.coeffs()
|
||||
// );
|
||||
|
||||
// migrate m's coeffs to the bigger modulus Q (from T)
|
||||
let m = m.remodule::<Q>();
|
||||
#[cfg(test)]
|
||||
{
|
||||
// sanity check // TODO rm
|
||||
let m_remod_naive =
|
||||
Rq::<Q, N>::from_vec_u64(m.coeffs().iter().map(|m_i| m_i.0).collect());
|
||||
assert_eq!(m_remod_naive, m);
|
||||
}
|
||||
|
||||
// let c0 = &pk.0 * &u + e_1 + m * Self::DELTA;
|
||||
let c0 = &pk.0 * &u + e_1 + m.mul_div_round(Q, T); // TODO use DELTA?
|
||||
// let c0 = &pk.0 * &u + e_1 + m.mul_div_round(Q, T);
|
||||
let c0 = &pk.0 * &u + e_1 + m * Self::DELTA;
|
||||
// let D: u64 = (Q as f64 / T as f64).floor() as u64;
|
||||
// let c0 = &pk.0 * &u + e_1 + m * D; // TODO use DELTA?
|
||||
let c1 = &pk.1 * &u + e_2;
|
||||
// let c0 = tmp_naive_mul(pk.0, u) + e_1 + m * Self::DELTA;
|
||||
// let c0 = tmp_naive_mul(pk.0, u) + e_1 + m.mul_div_round(Q, T);
|
||||
// let c1 = tmp_naive_mul(pk.1, u)
|
||||
// // &pk.1 * &u
|
||||
// + e_2;
|
||||
Ok(RLWE::<Q, N>(c0, c1))
|
||||
}
|
||||
|
||||
@@ -216,10 +216,13 @@ impl<const Q: u64, const N: usize, const T: u64> BFV<Q, N, T> {
|
||||
let m = m.remodule::<Q>();
|
||||
RLWE::<Q, N>(c.0 + m * Self::DELTA, c.1)
|
||||
}
|
||||
fn mul_const(c: &RLWE<Q, N>, m: &Rq<T, N>) -> RLWE<Q, N> {
|
||||
fn mul_const<const PQ: u64>(rlk: &RLK<PQ, N>, c: &RLWE<Q, N>, m: &Rq<T, N>) -> RLWE<Q, N> {
|
||||
// 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)
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
fn rlk_key<const PQ: u64>(mut rng: impl Rng, s: &SecretKey<Q, N>) -> Result<RLK<PQ, N>> {
|
||||
@@ -232,11 +235,11 @@ impl<const Q: u64, const N: usize, const T: u64> BFV<Q, N, T> {
|
||||
// let rlk_1: Rq<PQ, N> = (&(-a) * &s) - e + (s * s) * P;
|
||||
let P = PQ / Q;
|
||||
|
||||
// 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,
|
||||
a.clone(),
|
||||
);
|
||||
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,
|
||||
// a.clone(),
|
||||
// );
|
||||
// let rlk: RLK<PQ, N> = RLK::<PQ, N>(-(&a * &s + e) + (s * s) * P, a.clone());
|
||||
|
||||
// let pq = P * Q;
|
||||
@@ -292,17 +295,8 @@ impl<const Q: u64, const N: usize, const T: u64> BFV<Q, N, T> {
|
||||
// let c2rlk0: Vec<f64> = (c2.remodule::<PQ>() * rlk.0)
|
||||
use arithmetic::ring::naive_mul;
|
||||
let c2rlk0: Vec<i64> = naive_mul(&c2.to_r(), &rlk.0.to_r());
|
||||
// .coeffs()
|
||||
// .iter()
|
||||
// .map(|e| (*e as f64 / P as f64).round())
|
||||
// .collect();
|
||||
|
||||
let c2rlk1: Vec<i64> = naive_mul(&c2.to_r(), &rlk.1.to_r());
|
||||
// .coeffs()
|
||||
// .iter()
|
||||
// .map(|e| (*e as f64 / P as f64).round())
|
||||
// .collect();
|
||||
//
|
||||
|
||||
let r0: Rq<Q, N> = arithmetic::ring::mul_div_round::<Q, N>(c2rlk0, 1, P);
|
||||
let r1: Rq<Q, N> = arithmetic::ring::mul_div_round::<Q, N>(c2rlk1, 1, P);
|
||||
|
||||
@@ -348,13 +342,13 @@ mod tests {
|
||||
#[test]
|
||||
fn test_addition() -> Result<()> {
|
||||
const Q: u64 = 2u64.pow(16) + 1;
|
||||
// const N: usize = 32;
|
||||
const N: usize = 4;
|
||||
const N: usize = 32;
|
||||
const T: u64 = 4; // plaintext modulus
|
||||
type S = BFV<Q, N, T>;
|
||||
|
||||
let mut rng = rand::thread_rng();
|
||||
|
||||
for _ in 0..1_000 {
|
||||
let (sk, pk) = S::new_key(&mut rng)?;
|
||||
|
||||
let msg_dist = Uniform::new(0_u64, T);
|
||||
@@ -369,15 +363,16 @@ mod tests {
|
||||
let m3_recovered = S::decrypt(&sk, &c3);
|
||||
|
||||
assert_eq!(m1 + m2, m3_recovered);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_constant_add() -> Result<()> {
|
||||
fn test_constant_add_mul() -> Result<()> {
|
||||
const Q: u64 = 2u64.pow(16) + 1;
|
||||
const N: usize = 32;
|
||||
const T: u64 = 4; // plaintext modulus
|
||||
const T: u64 = 8; // plaintext modulus
|
||||
type S = BFV<Q, N, T>;
|
||||
|
||||
let mut rng = rand::thread_rng();
|
||||
@@ -387,25 +382,25 @@ mod tests {
|
||||
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 c3_add = &c1 + &m2_const;
|
||||
// let c3_mul = &c1 * &m2_const;
|
||||
|
||||
let m3_add_recovered = S::decrypt(&sk, &c3_add);
|
||||
// let m3_mul_recovered = S::decrypt(&sk, &c3_mul);
|
||||
|
||||
assert_eq!(m1 + m2_const, m3_add_recovered);
|
||||
//
|
||||
// let mut mul_res = naive_poly_mul::<T>(&m1.coeffs().to_vec(), &m2_const.coeffs().to_vec());
|
||||
// arithmetic::ring::modulus::<T, N>(&mut mul_res);
|
||||
// dbg!(&mul_res);
|
||||
// let mul_res_2 =
|
||||
// naive_poly_mul_2::<T, N>(&m1.coeffs().to_vec(), &m2_const.coeffs().to_vec());
|
||||
// assert_eq!(mul_res, mul_res_2);
|
||||
// let mul_res = PR::<T, N>::from_vec(mul_res);
|
||||
// assert_eq!(mul_res.coeffs(), m3_mul_recovered.coeffs());
|
||||
|
||||
// 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 c3_mul = S::mul_const(&rlk, &c1, &m2_const);
|
||||
|
||||
let m3_mul_recovered = S::decrypt(&sk, &c3_mul);
|
||||
assert_eq!(
|
||||
(m1.to_r() * m2_const.to_r()).to_rq::<T>().coeffs(),
|
||||
m3_mul_recovered.coeffs()
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -413,12 +408,11 @@ mod tests {
|
||||
#[test]
|
||||
fn test_tensor() -> Result<()> {
|
||||
const Q: u64 = 2u64.pow(16) + 1; // q prime, and 2^q + 1 shape
|
||||
const N: usize = 8;
|
||||
const T: u64 = 4; // plaintext modulus
|
||||
const N: usize = 32;
|
||||
const T: u64 = 8; // plaintext modulus
|
||||
|
||||
// const P: u64 = Q;
|
||||
const P: u64 = Q * Q;
|
||||
// const P: u64 = 2_u64.pow(13) * Q + 1;
|
||||
const PQ: u64 = P * Q;
|
||||
|
||||
let mut rng = rand::thread_rng();
|
||||
@@ -447,7 +441,13 @@ mod tests {
|
||||
|
||||
// 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<Q, N> = m3.mul_div_round(T, Q);
|
||||
// let m3: Rq<Q, N> = c_a
|
||||
// + Rq::<Q, N>::from_vec_i64(arithmetic::ring::naive_mul(&c_b.to_r(), &sk.0.to_r()))
|
||||
// + Rq::<Q, N>::from_vec_i64(arithmetic::ring::naive_mul(
|
||||
// &c_c.to_r(),
|
||||
// &R::<N>::from_vec(arithmetic::ring::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 naive = (m1.to_r() * m2.to_r()).to_rq::<T>();
|
||||
@@ -538,7 +538,7 @@ mod tests {
|
||||
let c1 = BFV::<Q, N, T>::encrypt(&mut rng, &pk, &m1)?;
|
||||
let c2 = BFV::<Q, N, T>::encrypt(&mut rng, &pk, &m2)?;
|
||||
|
||||
let c3 = RLWE::<Q, N>::mul::<PQ, T>(&rlk, &c1, &c2);
|
||||
let c3 = RLWE::<Q, N>::mul::<PQ, T>(&rlk, &c1, &c2); // uses relinearize internally
|
||||
|
||||
let m3 = BFV::<Q, N, T>::decrypt(&sk, &c3);
|
||||
|
||||
@@ -551,105 +551,4 @@ mod tests {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_naive_mul() -> Result<()> {
|
||||
const Q: u64 = 2u64.pow(16) + 1; // prime, and 2^q + 1 shape
|
||||
const N: usize = 4;
|
||||
const T: u64 = 4; // plaintext modulus
|
||||
|
||||
let mut rng = rand::thread_rng();
|
||||
let msg_dist = Uniform::new(0_u64, T);
|
||||
// for _ in 0..10_000 {
|
||||
for _ in 0..2 {
|
||||
println!("---");
|
||||
// let a = Rq::<Q, N>::rand_u64(&mut rng, msg_dist)?;
|
||||
// let b = Rq::<Q, N>::rand_u64(&mut rng, msg_dist)?;
|
||||
// let a = Rq::<Q, N>::from_vec_u64(vec![Q - 1, Q - 2, Q - 3, Q - 3]);
|
||||
// let b = Rq::<Q, N>::from_vec_u64(vec![Q - 3, Q - 3, Q - 2, Q - 1]);
|
||||
let a = Rq::<T, N>::rand_u64(&mut rng, msg_dist)?;
|
||||
let b = Rq::<T, N>::rand_u64(&mut rng, msg_dist)?;
|
||||
dbg!(&a);
|
||||
dbg!(&b);
|
||||
let (_, pk) = BFV::<Q, N, T>::new_key(&mut rng)?;
|
||||
let ciph1 = BFV::<Q, N, T>::encrypt(&mut rng, &pk, &a)?;
|
||||
let ciph2 = BFV::<Q, N, T>::encrypt(&mut rng, &pk, &b)?;
|
||||
let a = ciph1.0;
|
||||
let b = ciph2.0;
|
||||
dbg!(&a);
|
||||
dbg!(&b);
|
||||
|
||||
let c0: Vec<i64> = arithmetic::ring::naive_mul(&a.to_r(), &b.to_r());
|
||||
let c0 = Rq::<Q, N>::from_vec_i64(c0);
|
||||
let c1 = tmp_naive_mul(a, b); // naive mul
|
||||
let c2: Rq<Q, N> = a * b; // NTT mul
|
||||
|
||||
println!("{:?}", c0.coeffs());
|
||||
println!("{:?}", c1.coeffs());
|
||||
println!("{:?}", c2.coeffs());
|
||||
assert_eq!(c0, c2);
|
||||
assert_eq!(c1, c2);
|
||||
|
||||
// scale by Delta=Q/T
|
||||
let a = a.mul_div_round(Q, T);
|
||||
let b = b.mul_div_round(Q, T);
|
||||
dbg!(&a);
|
||||
dbg!(&b);
|
||||
|
||||
let c0: Vec<i64> = arithmetic::ring::naive_mul(&a.to_r(), &b.to_r());
|
||||
let c0 = Rq::<Q, N>::from_vec_i64(c0);
|
||||
let c1 = tmp_naive_mul(a, b); // naive mul
|
||||
let c2: Rq<Q, N> = a * b; // NTT mul
|
||||
|
||||
println!("{:?}", c0.coeffs());
|
||||
println!("{:?}", c1.coeffs());
|
||||
println!("{:?}", c2.coeffs());
|
||||
assert_eq!(c0, c2);
|
||||
assert_eq!(c1, c2);
|
||||
|
||||
let c0 = c0.mul_div_round(T, Q);
|
||||
let c1 = c1.mul_div_round(T, Q);
|
||||
let c2 = c2.mul_div_round(T, Q);
|
||||
println!("{:?}", c0.coeffs());
|
||||
println!("{:?}", c1.coeffs());
|
||||
println!("{:?}", c2.coeffs());
|
||||
assert_eq!(c0, c2);
|
||||
assert_eq!(c1, c2);
|
||||
|
||||
/*
|
||||
// now same as before, but multiplying by T/Q
|
||||
let c0: Vec<i64> = arithmetic::ring::naive_mul(&a.to_r(), &b.to_r());
|
||||
let c0: Vec<f64> = c0
|
||||
.iter()
|
||||
.map(|e| ((T as f64 * *e as f64) / Q as f64).round())
|
||||
.collect();
|
||||
let c0 = Rq::<Q, N>::from_vec_f64(c0);
|
||||
dbg!(&c0.coeffs());
|
||||
|
||||
let a = a.mul_div_round(T, Q);
|
||||
let b = b.mul_div_round(T, Q);
|
||||
println!("a{:?}", a.coeffs());
|
||||
println!("b{:?}", b.coeffs());
|
||||
|
||||
let c4: Vec<i64> = arithmetic::ring::naive_mul(&a.to_r(), &b.to_r());
|
||||
let c4 = Rq::<Q, N>::from_vec_i64(c4);
|
||||
let c4 = c4.mul_div_round(T, Q);
|
||||
|
||||
let c1 = tmp_naive_mul(a, b); // naive mul
|
||||
let c1 = c1.mul_div_round(T, Q);
|
||||
let c2 = a * b; // NTT mul
|
||||
let c2 = c2.mul_div_round(T, Q);
|
||||
|
||||
println!("{:?}", c0.coeffs());
|
||||
println!("{:?}", c1.coeffs());
|
||||
println!("{:?}", c2.coeffs());
|
||||
println!("{:?}", c4.coeffs());
|
||||
assert_eq!(c0, c2);
|
||||
assert_eq!(c4, c2);
|
||||
assert_eq!(c1, c2);
|
||||
*/
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user