Browse Source

group ring params under a single struct

rm-const-generics
arnaucube 2 weeks ago
parent
commit
9e90f094a9
10 changed files with 295 additions and 261 deletions
  1. +1
    -1
      arith/src/lib.rs
  2. +14
    -10
      arith/src/ntt.rs
  3. +11
    -4
      arith/src/ring.rs
  4. +6
    -3
      arith/src/ring_n.rs
  5. +135
    -145
      arith/src/ring_nq.rs
  6. +4
    -1
      arith/src/ring_torus.rs
  7. +4
    -1
      arith/src/torus.rs
  8. +3
    -3
      arith/src/tuple_ring.rs
  9. +87
    -75
      bfv/src/lib.rs
  10. +30
    -18
      ckks/src/lib.rs

+ 1
- 1
arith/src/lib.rs

@ -25,7 +25,7 @@ pub use matrix::Matrix;
// pub use torus::T64;
pub use zq::Zq;
pub use ring::Ring;
pub use ring::{Ring, RingParam};
pub use ring_n::R;
pub use ring_nq::Rq;
// pub use ring_torus::Tn;

+ 14
- 10
arith/src/ntt.rs

@ -6,7 +6,11 @@
//! generics; but once using real-world parameters, the stack could not handle
//! it, so moved to use Vec instead of fixed-sized arrays, and adapted the NTT
//! implementation to that too.
use crate::{ring::Ring, ring_nq::Rq, zq::Zq};
use crate::{
ring::{Ring, RingParam},
ring_nq::Rq,
zq::Zq,
};
use std::collections::HashMap;
@ -50,7 +54,7 @@ impl NTT {
/// https://eprint.iacr.org/2017/727.pdf, also some notes at section 3.1 of
/// https://github.com/arnaucube/math/blob/master/notes_ntt.pdf
pub fn ntt(a: &Rq) -> Rq {
let (q, n) = (a.q, a.n);
let (q, n) = (a.param.q, a.param.n);
let (roots_of_unity, _, _) = roots(q, n);
let mut t = n / 2;
@ -73,8 +77,7 @@ impl NTT {
}
// Rq::from_vec((a.q, n), r)
Rq {
q,
n,
param: RingParam { q, n },
coeffs: r,
evals: None,
}
@ -84,7 +87,7 @@ impl NTT {
/// https://eprint.iacr.org/2017/727.pdf, also some notes at section 3.2 of
/// https://github.com/arnaucube/math/blob/master/notes_ntt.pdf
pub fn intt(a: &Rq) -> Rq {
let (q, n) = (a.q, a.n);
let (q, n) = (a.param.q, a.param.n);
let (_, roots_of_unity_inv, n_inv) = roots(q, n);
let mut t = 1;
@ -110,8 +113,7 @@ impl NTT {
}
// Rq::from_vec((a.q, n), r)
Rq {
q,
n,
param: RingParam { q, n },
coeffs: r,
evals: None,
}
@ -202,9 +204,10 @@ mod tests {
fn test_ntt() -> Result<()> {
let q: u64 = 2u64.pow(16) + 1;
let n: usize = 4;
let param = RingParam { q, n };
let a: Vec<u64> = vec![1u64, 2, 3, 4];
let a: Rq = Rq::from_vec_u64(q, n, a);
let a: Rq = Rq::from_vec_u64(&param, a);
let a_ntt = NTT::ntt(&a);
@ -224,13 +227,14 @@ mod tests {
fn test_ntt_loop() -> Result<()> {
let q: u64 = 2u64.pow(16) + 1;
let n: usize = 512;
let param = RingParam { q, n };
use rand::distributions::Uniform;
let mut rng = rand::thread_rng();
let dist = Uniform::new(0_f64, q as f64);
for _ in 0..10_000 {
let a: Rq = Rq::rand(&mut rng, dist, (q, n));
for _ in 0..1000 {
let a: Rq = Rq::rand(&mut rng, dist, &param);
let a_ntt = NTT::ntt(&a);
let a_intt = NTT::intt(&a_ntt);
assert_eq!(a, a_intt);

+ 11
- 4
arith/src/ring.rs

@ -3,6 +3,12 @@ use std::fmt::Debug;
use std::iter::Sum;
use std::ops::{Add, AddAssign, Mul, Neg, Sub, SubAssign};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct RingParam {
pub q: u64, // TODO think if really needed or it's fine with coeffs[0].q
pub n: usize,
}
/// Represents a ring element. Currently implemented by ring_nq.rs#Rq and
/// ring_torus.rs#Tn. Is not a 'pure algebraic ring', but more a custom trait
/// definition which includes methods like `mod_switch`.
@ -27,17 +33,18 @@ pub trait Ring:
{
/// C defines the coefficient type
type C: Debug + Clone;
type Params: Debug+Clone+Copy;
// type Param: Debug+Clone+Copy;
// const Q: u64;
// const N: usize;
fn param(&self) -> RingParam;
fn coeffs(&self) -> Vec<Self::C>;
fn zero(params: Self::Params) -> Self;
fn zero(param: &RingParam) -> Self;
// note/wip/warning: dist (0,q) with f64, will output more '0=q' elements than other values
fn rand(rng: impl Rng, dist: impl Distribution<f64>, params: Self::Params) -> Self;
fn rand(rng: impl Rng, dist: impl Distribution<f64>, param: &RingParam) -> Self;
fn from_vec(params: Self::Params, coeffs: Vec<Self::C>) -> Self;
fn from_vec(param: &RingParam, coeffs: Vec<Self::C>) -> Self;
fn decompose(&self, beta: u32, l: u32) -> Vec<Self>;

+ 6
- 3
arith/src/ring_n.rs

@ -23,7 +23,7 @@ pub struct R {
// impl<const N: usize> Ring for R<N> {
impl R {
// type C = i64;
// type Params = usize; // n
// type Param = usize; // n
// const Q: u64 = i64::MAX as u64; // WIP
// const N: usize = N;
@ -87,7 +87,10 @@ impl R {
impl From<crate::ring_nq::Rq> for R {
fn from(rq: crate::ring_nq::Rq) -> Self {
Self::from_vec_u64(rq.n, rq.coeffs().to_vec().iter().map(|e| e.v).collect())
Self::from_vec_u64(
rq.param.n,
rq.coeffs().to_vec().iter().map(|e| e.v).collect(),
)
}
}
@ -150,7 +153,7 @@ pub fn mul_div_round(q: u64, n: usize, v: Vec, num: u64, den: u64) -> crate
.map(|e| ((num as f64 * *e as f64) / den as f64).round())
.collect();
// dbg!(&r);
crate::Rq::from_vec_f64(q, n, r)
crate::Rq::from_vec_f64(&crate::ring::RingParam { q, n }, r)
}
// TODO rename to make it clear that is not mod q, but mod X^N+1

+ 135
- 145
arith/src/ring_nq.rs

@ -13,7 +13,7 @@ use std::ops::{Add, AddAssign, Mul, Neg, Sub, SubAssign};
use crate::ntt::NTT;
use crate::zq::{modulus_u64, Zq};
use crate::Ring;
use crate::{Ring, RingParam};
// NOTE: currently using fixed-size arrays, but pending to see if with
// real-world parameters the stack can keep up; if not will move everything to
@ -22,8 +22,7 @@ use crate::Ring;
/// The implementation assumes that q is prime.
#[derive(Clone)]
pub struct Rq {
pub q: u64, // TODO think if really needed or it's fine with coeffs[0].q
pub n: usize,
pub param: RingParam,
pub(crate) coeffs: Vec<Zq>,
@ -34,42 +33,41 @@ pub struct Rq {
impl Ring for Rq {
type C = Zq;
type Params = (u64, usize);
// type Param = (u64, usize);
// type Param = Param;
fn param(&self) -> RingParam {
self.param
}
fn coeffs(&self) -> Vec<Self::C> {
self.coeffs.to_vec()
}
// fn zero(q: u64, n: usize) -> Self {
fn zero(param: (u64, usize)) -> Self {
let (q, n) = param;
// fn zero(param: (u64, usize)) -> Self {
fn zero(param: &RingParam) -> Self {
Self {
q,
n,
coeffs: vec![Zq::zero(q); n],
param: param.clone(),
coeffs: vec![Zq::zero(param.q); param.n],
evals: None,
}
}
fn rand(mut rng: impl Rng, dist: impl Distribution<f64>, params: Self::Params) -> Self {
fn rand(mut rng: impl Rng, dist: impl Distribution<f64>, param: &RingParam) -> Self {
// let coeffs: [Zq<Q>; N] = array::from_fn(|_| Zq::from_u64(dist.sample(&mut rng)));
// let coeffs: [Zq<Q>; N] = array::from_fn(|_| Self::C::rand(&mut rng, &dist));
let (q, n) = params;
Self {
q,
n,
coeffs: std::iter::repeat_with(|| Self::C::rand(&mut rng, &dist, q))
.take(n)
param: param.clone(),
coeffs: std::iter::repeat_with(|| Self::C::rand(&mut rng, &dist, param.q))
.take(param.n)
.collect(),
evals: None,
}
}
fn from_vec(params: Self::Params, coeffs: Vec<Zq>) -> Self {
let (q, n) = params;
fn from_vec(param: &RingParam, coeffs: Vec<Zq>) -> Self {
let mut p = coeffs;
modulus(q, n, &mut p);
modulus(param.q, param.n, &mut p);
Self {
q,
n,
param: param.clone(),
coeffs: p,
evals: None,
}
@ -85,7 +83,7 @@ impl Ring for Rq {
.collect();
// convert it to Rq<Q,N>
r.iter()
.map(|a_i| Self::from_vec((self.q, self.n), a_i.clone()))
.map(|a_i| Self::from_vec(&self.param, a_i.clone()))
.collect()
}
@ -93,16 +91,26 @@ impl Ring for Rq {
// if Q<P, it just 'renames' the modulus parameter to P
// if Q>=P, it crops to mod P
fn remodule(&self, p: u64) -> Rq {
Rq::from_vec_u64(p, self.n, self.coeffs().iter().map(|m_i| m_i.v).collect())
let param = RingParam {
q: p,
n: self.param.n,
};
// Rq::from_vec_u64(p, self.n, self.coeffs().iter().map(|m_i| m_i.v).collect())
Rq::from_vec_u64(&param, self.coeffs().iter().map(|m_i| m_i.v).collect())
}
/// perform the mod switch operation from Q to Q', where Q2=Q'
// fn mod_switch<const P: u64, const M: usize>(&self) -> impl Ring {
fn mod_switch(&self, p: u64) -> Rq {
let param = RingParam {
q: p,
n: self.param.n,
};
// assert_eq!(N, M); // sanity check
Rq {
q: p,
n: self.n,
param,
// q: p,
// n: self.n,
// coeffs: array::from_fn(|i| self.coeffs[i].mod_switch::<P>()),
coeffs: self.coeffs.iter().map(|c_i| c_i.mod_switch(p)).collect(),
evals: None,
@ -118,7 +126,7 @@ impl Ring for Rq {
.iter()
.map(|e| ((num as f64 * e.v as f64) / den as f64).round())
.collect();
Rq::from_vec_f64(self.q, self.n, r)
Rq::from_vec_f64(&self.param, r)
}
}
@ -128,7 +136,8 @@ impl From<(u64, crate::ring_n::R)> for Rq {
assert_eq!(r.n, r.coeffs.len());
Self::from_vec(
(q, r.n),
&RingParam { q, n: r.n },
// (q, r.n),
r.coeffs()
.iter()
.map(|e| Zq::from_f64(q, *e as f64))
@ -170,22 +179,24 @@ impl Rq {
// }
// }
// this method is mostly for tests
pub fn from_vec_u64(q: u64, n: usize, coeffs: Vec<u64>) -> Self {
let coeffs_mod_q: Vec<Zq> = coeffs.iter().map(|c| Zq::from_u64(q, *c)).collect();
Self::from_vec((q, n), coeffs_mod_q)
pub fn from_vec_u64(param: &RingParam, coeffs: Vec<u64>) -> Self {
let coeffs_mod_q: Vec<Zq> = coeffs.iter().map(|c| Zq::from_u64(param.q, *c)).collect();
Self::from_vec(param, coeffs_mod_q)
}
pub fn from_vec_f64(q: u64, n: usize, coeffs: Vec<f64>) -> Self {
let coeffs_mod_q: Vec<Zq> = coeffs.iter().map(|c| Zq::from_f64(q, *c)).collect();
Self::from_vec((q, n), coeffs_mod_q)
pub fn from_vec_f64(param: &RingParam, coeffs: Vec<f64>) -> Self {
let coeffs_mod_q: Vec<Zq> = coeffs.iter().map(|c| Zq::from_f64(param.q, *c)).collect();
Self::from_vec(param, coeffs_mod_q)
}
pub fn from_vec_i64(q: u64, n: usize, coeffs: Vec<i64>) -> Self {
let coeffs_mod_q: Vec<Zq> = coeffs.iter().map(|c| Zq::from_f64(q, *c as f64)).collect();
Self::from_vec((q, n), coeffs_mod_q)
pub fn from_vec_i64(param: &RingParam, coeffs: Vec<i64>) -> Self {
let coeffs_mod_q: Vec<Zq> = coeffs
.iter()
.map(|c| Zq::from_f64(param.q, *c as f64))
.collect();
Self::from_vec(param, coeffs_mod_q)
}
pub fn new(q: u64, n: usize, coeffs: Vec<Zq>, evals: Option<Vec<Zq>>) -> Self {
pub fn new(param: &RingParam, coeffs: Vec<Zq>, evals: Option<Vec<Zq>>) -> Self {
Self {
q,
n,
param: *param,
coeffs,
evals,
}
@ -194,15 +205,17 @@ impl Rq {
pub fn rand_abs(
mut rng: impl Rng,
dist: impl Distribution<f64>,
q: u64,
n: usize,
param: &RingParam,
// q: u64,
// n: usize,
) -> Result<Self> {
// let coeffs: [Zq<Q>; N] = array::from_fn(|_| Zq::from_f64(dist.sample(&mut rng).abs()));
Ok(Self {
q,
n,
coeffs: std::iter::repeat_with(|| Zq::from_f64(q, dist.sample(&mut rng).abs()))
.take(n)
param: *param,
// q,
// n,
coeffs: std::iter::repeat_with(|| Zq::from_f64(param.q, dist.sample(&mut rng).abs()))
.take(param.n)
.collect(),
evals: None,
})
@ -210,15 +223,17 @@ impl Rq {
pub fn rand_f64_abs(
mut rng: impl Rng,
dist: impl Distribution<f64>,
q: u64,
n: usize,
param: &RingParam,
// q: u64,
// n: usize,
) -> Result<Self> {
// let coeffs: [Zq<Q>; N] = array::from_fn(|_| Zq::from_f64(dist.sample(&mut rng).abs()));
Ok(Self {
q,
n,
coeffs: std::iter::repeat_with(|| Zq::from_f64(q, dist.sample(&mut rng).abs()))
.take(n)
param: *param,
// q,
// n,
coeffs: std::iter::repeat_with(|| Zq::from_f64(param.q, dist.sample(&mut rng).abs()))
.take(param.n)
.collect(),
evals: None,
})
@ -226,15 +241,13 @@ impl Rq {
pub fn rand_f64(
mut rng: impl Rng,
dist: impl Distribution<f64>,
q: u64,
n: usize,
param: &RingParam,
) -> Result<Self> {
// let coeffs: [Zq<Q>; N] = array::from_fn(|_| Zq::from_f64(dist.sample(&mut rng)));
Ok(Self {
q,
n,
coeffs: std::iter::repeat_with(|| Zq::from_f64(q, dist.sample(&mut rng)))
.take(n)
param: *param,
coeffs: std::iter::repeat_with(|| Zq::from_f64(param.q, dist.sample(&mut rng)))
.take(param.n)
.collect(),
evals: None,
})
@ -242,15 +255,13 @@ impl Rq {
pub fn rand_u64(
mut rng: impl Rng,
dist: impl Distribution<u64>,
q: u64,
n: usize,
param: &RingParam,
) -> Result<Self> {
// let coeffs: [Zq<Q>; N] = array::from_fn(|_| Zq::from_u64(dist.sample(&mut rng)));
Ok(Self {
q,
n,
coeffs: std::iter::repeat_with(|| Zq::from_u64(q, dist.sample(&mut rng)))
.take(n)
param: *param,
coeffs: std::iter::repeat_with(|| Zq::from_u64(param.q, dist.sample(&mut rng)))
.take(param.n)
.collect(),
evals: None,
})
@ -259,15 +270,13 @@ impl Rq {
pub fn rand_bin(
mut rng: impl Rng,
dist: impl Distribution<bool>,
q: u64,
n: usize,
param: &RingParam,
) -> Result<Self> {
// let coeffs: [Zq<Q>; N] = array::from_fn(|_| Zq::from_bool(dist.sample(&mut rng)));
Ok(Rq {
q,
n,
coeffs: std::iter::repeat_with(|| Zq::from_bool(q, dist.sample(&mut rng)))
.take(n)
param: *param,
coeffs: std::iter::repeat_with(|| Zq::from_bool(param.q, dist.sample(&mut rng)))
.take(param.n)
.collect(),
evals: None,
})
@ -280,10 +289,9 @@ impl Rq {
// }
// applies mod(T) to all coefficients of self
pub fn coeffs_mod<const T: u64>(&self, q: u64, n: usize, t: u64) -> Self {
pub fn coeffs_mod(&self, param: &RingParam, t: u64) -> Self {
Rq::from_vec_u64(
q,
n,
param,
self.coeffs()
.iter()
.map(|m_i| modulus_u64(t, m_i.v))
@ -297,18 +305,16 @@ impl Rq {
}
pub fn mul_by_zq(&self, s: &Zq) -> Self {
Self {
q: self.q,
n: self.n,
param: self.param,
// coeffs: array::from_fn(|i| self.coeffs[i] * *s),
coeffs: self.coeffs.iter().map(|c_i| *c_i * *s).collect(),
evals: None,
}
}
pub fn mul_by_u64(&self, s: u64) -> Self {
let s = Zq::from_u64(self.q, s);
let s = Zq::from_u64(self.param.q, s);
Self {
q: self.q,
n: self.n,
param: self.param,
// coeffs: array::from_fn(|i| self.coeffs[i] * s),
coeffs: self.coeffs.iter().map(|&e| e * s).collect(),
evals: None,
@ -316,13 +322,12 @@ impl Rq {
}
pub fn mul_by_f64(&self, s: f64) -> Self {
Self {
q: self.q,
n: self.n,
param: self.param,
// coeffs: array::from_fn(|i| Zq::from_f64(self.coeffs[i].0 as f64 * s)),
coeffs: self
.coeffs
.iter()
.map(|c_i| Zq::from_f64(self.q, c_i.v as f64 * s))
.map(|c_i| Zq::from_f64(self.param.q, c_i.v as f64 * s))
.collect(),
evals: None,
}
@ -339,7 +344,7 @@ impl Rq {
.iter()
.map(|e| (e.v as f64 / s as f64).round())
.collect();
Rq::from_vec_f64(self.q, self.n, r)
Rq::from_vec_f64(&self.param, r)
}
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
@ -374,9 +379,9 @@ impl Rq {
}
f.write_str(" mod Z_")?;
f.write_str(self.q.to_string().as_str())?;
f.write_str(self.param.q.to_string().as_str())?;
f.write_str("/(X^")?;
f.write_str(self.n.to_string().as_str())?;
f.write_str(self.param.n.to_string().as_str())?;
f.write_str("+1)")?;
Ok(())
}
@ -385,8 +390,8 @@ impl Rq {
self.coeffs()
.iter()
.map(|x| {
if x.v > (self.q / 2) {
self.q - x.v
if x.v > (self.param.q / 2) {
self.param.q - x.v
} else {
x.v
}
@ -394,7 +399,7 @@ impl Rq {
.fold(0, |a, b| a.max(b))
}
pub fn mod_centered_q(&self) -> crate::ring_n::R {
self.clone().to_r().mod_centered_q(self.q)
self.clone().to_r().mod_centered_q(self.param.q)
}
}
pub fn matrix_vec_product(m: &Vec<Vec<Zq>>, v: &Vec<Zq>) -> Result<Vec<Zq>> {
@ -437,18 +442,16 @@ pub fn transpose(m: &[Vec]) -> Vec> {
impl PartialEq for Rq {
fn eq(&self, other: &Self) -> bool {
self.coeffs == other.coeffs && self.q == other.q && self.n == other.n
self.coeffs == other.coeffs && self.param == other.param
}
}
impl Add<Rq> for Rq {
type Output = Self;
fn add(self, rhs: Self) -> Self {
assert_eq!(self.q, rhs.q);
assert_eq!(self.n, rhs.n);
assert_eq!(self.param, rhs.param);
Self {
q: self.q,
n: self.n,
param: self.param,
// coeffs: array::from_fn(|i| self.coeffs[i] + rhs.coeffs[i]),
coeffs: zip_eq(self.coeffs, rhs.coeffs)
.map(|(l, r)| l + r)
@ -471,11 +474,9 @@ impl Add<&Rq> for &Rq {
type Output = Rq;
fn add(self, rhs: &Rq) -> Self::Output {
assert_eq!(self.q, rhs.q);
assert_eq!(self.n, rhs.n);
assert_eq!(self.param, rhs.param);
Rq {
q: self.q,
n: self.n,
param: self.param,
// coeffs: array::from_fn(|i| self.coeffs[i] + rhs.coeffs[i]),
coeffs: zip_eq(self.coeffs.clone(), rhs.coeffs.clone())
.map(|(l, r)| l + r)
@ -486,9 +487,8 @@ impl Add<&Rq> for &Rq {
}
impl AddAssign for Rq {
fn add_assign(&mut self, rhs: Self) {
debug_assert_eq!(self.q, rhs.q);
debug_assert_eq!(self.n, rhs.n);
for i in 0..self.n {
debug_assert_eq!(self.param, rhs.param);
for i in 0..self.param.n {
self.coeffs[i] += rhs.coeffs[i];
}
}
@ -513,11 +513,9 @@ impl Sub for Rq {
type Output = Self;
fn sub(self, rhs: Self) -> Self {
assert_eq!(self.q, rhs.q);
assert_eq!(self.n, rhs.n);
assert_eq!(self.param, rhs.param);
Self {
q: self.q,
n: self.n,
param: self.param,
// coeffs: array::from_fn(|i| self.coeffs[i] - rhs.coeffs[i]),
coeffs: zip_eq(self.coeffs, rhs.coeffs)
.map(|(l, r)| l - r)
@ -530,11 +528,9 @@ impl Sub<&Rq> for &Rq {
type Output = Rq;
fn sub(self, rhs: &Rq) -> Self::Output {
assert_eq!(self.q, rhs.q); // TODO replace all those with debug_assert_eq
debug_assert_eq!(self.n, rhs.n);
debug_assert_eq!(self.param, rhs.param);
Rq {
q: self.q,
n: self.n,
param: self.param,
// coeffs: array::from_fn(|i| self.coeffs[i] - rhs.coeffs[i]),
coeffs: zip_eq(self.coeffs.clone(), rhs.coeffs.clone())
.map(|(l, r)| l - r)
@ -545,9 +541,8 @@ impl Sub<&Rq> for &Rq {
}
impl SubAssign for Rq {
fn sub_assign(&mut self, rhs: Self) {
debug_assert_eq!(self.q, rhs.q);
debug_assert_eq!(self.n, rhs.n);
for i in 0..self.n {
debug_assert_eq!(self.param, rhs.param);
for i in 0..self.param.n {
self.coeffs[i] -= rhs.coeffs[i];
}
}
@ -619,8 +614,7 @@ impl Neg for Rq {
fn neg(self) -> Self::Output {
Self {
q: self.q,
n: self.n,
param: self.param,
// coeffs: array::from_fn(|i| -self.coeffs[i]),
// coeffs: self.coeffs.iter().map(|c_i| -c_i).collect(),
coeffs: self.coeffs.iter().map(|c_i| -*c_i).collect(),
@ -631,9 +625,8 @@ impl Neg for Rq {
// note: this assumes that Q is prime
fn mul_mut(lhs: &mut Rq, rhs: &mut Rq) -> Rq {
assert_eq!(lhs.q, rhs.q);
assert_eq!(lhs.n, rhs.n);
let (q, n) = (lhs.q, lhs.n);
assert_eq!(lhs.param, rhs.param);
// let (q, n) = (lhs.q, lhs.n);
// reuse evaluations if already computed
if !lhs.evals.is_some() {
@ -647,18 +640,16 @@ fn mul_mut(lhs: &mut Rq, rhs: &mut Rq) -> Rq {
// let c_ntt: [Zq<Q>; N] = array::from_fn(|i| lhs_evals[i] * rhs_evals[i]);
let c_ntt: Rq = Rq::from_vec(
(q, n),
&lhs.param,
zip_eq(lhs_evals, rhs_evals).map(|(l, r)| l * r).collect(),
);
let c = NTT::intt(&c_ntt);
Rq::new(q, n, c.coeffs, Some(c_ntt.coeffs))
Rq::new(&lhs.param, c.coeffs, Some(c_ntt.coeffs))
}
// note: this assumes that Q is prime
// TODO impl karatsuba for non-prime Q
fn mul(lhs: &Rq, rhs: &Rq) -> Rq {
assert_eq!(lhs.q, rhs.q);
assert_eq!(lhs.n, rhs.n);
let (q, n) = (lhs.q, lhs.n);
assert_eq!(lhs.param, rhs.param);
// reuse evaluations if already computed
let lhs_evals: Vec<Zq> = if lhs.evals.is_some() {
@ -674,11 +665,11 @@ fn mul(lhs: &Rq, rhs: &Rq) -> Rq {
// let c_ntt: [Zq<Q>; N] = array::from_fn(|i| lhs_evals[i] * rhs_evals[i]);
let c_ntt: Rq = Rq::from_vec(
(q, n),
&lhs.param,
zip_eq(lhs_evals, rhs_evals).map(|(l, r)| l * r).collect(),
);
let c = NTT::intt(&c_ntt);
Rq::new(q, n, c.coeffs, Some(c_ntt.coeffs))
Rq::new(&lhs.param, c.coeffs, Some(c_ntt.coeffs))
}
impl fmt::Display for Rq {
@ -701,31 +692,30 @@ mod tests {
#[test]
fn test_polynomial_ring() {
// the test values used are generated with SageMath
let q: u64 = 7;
let n: usize = 3;
let param = RingParam { q: 7, n: 3 };
// p = 1x + 2x^2 + 3x^3 + 4 x^4 + 5 x^5 in R=Z_q[X]/(X^n +1)
let p = Rq::from_vec_u64(q, n, vec![0u64, 1, 2, 3, 4, 5]);
let p = Rq::from_vec_u64(&param, vec![0u64, 1, 2, 3, 4, 5]);
assert_eq!(p.to_string(), "4*x^2 + 4*x + 4 mod Z_7/(X^3+1)");
// try with coefficients bigger than Q
let p = Rq::from_vec_u64(q, n, vec![0u64, 1, q + 2, 3, 4, 5]);
let p = Rq::from_vec_u64(&param, vec![0u64, 1, param.q + 2, 3, 4, 5]);
assert_eq!(p.to_string(), "4*x^2 + 4*x + 4 mod Z_7/(X^3+1)");
// try with other ring
let p = Rq::from_vec_u64(7, 4, vec![0u64, 1, 2, 3, 4, 5]);
let p = Rq::from_vec_u64(&RingParam { q: 7, n: 4 }, vec![0u64, 1, 2, 3, 4, 5]);
assert_eq!(p.to_string(), "3*x^3 + 2*x^2 + 3*x + 3 mod Z_7/(X^4+1)");
let p = Rq::from_vec_u64(q, n, vec![0u64, 0, 0, 0, 4, 5]);
let p = Rq::from_vec_u64(&param, vec![0u64, 0, 0, 0, 4, 5]);
assert_eq!(p.to_string(), "2*x^2 + 3*x mod Z_7/(X^3+1)");
let p = Rq::from_vec_u64(q, n, vec![5u64, 4, 5, 2, 1, 0]);
let p = Rq::from_vec_u64(&param, vec![5u64, 4, 5, 2, 1, 0]);
assert_eq!(p.to_string(), "5*x^2 + 3*x + 3 mod Z_7/(X^3+1)");
let a = Rq::from_vec_u64(q, n, vec![0u64, 1, 2, 3, 4, 5]);
let a = Rq::from_vec_u64(&param, vec![0u64, 1, 2, 3, 4, 5]);
assert_eq!(a.to_string(), "4*x^2 + 4*x + 4 mod Z_7/(X^3+1)");
let b = Rq::from_vec_u64(q, n, vec![5u64, 4, 3, 2, 1, 0]);
let b = Rq::from_vec_u64(&param, vec![5u64, 4, 3, 2, 1, 0]);
assert_eq!(b.to_string(), "3*x^2 + 3*x + 3 mod Z_7/(X^3+1)");
// add
@ -742,39 +732,40 @@ mod tests {
#[test]
fn test_mul() -> Result<()> {
let q: u64 = 2u64.pow(16) + 1;
let n: usize = 4;
let param = RingParam {
q: 2u64.pow(16) + 1,
n: 4,
};
let a: Vec<u64> = vec![1u64, 2, 3, 4];
let b: Vec<u64> = vec![1u64, 2, 3, 4];
let c: Vec<u64> = vec![65513, 65517, 65531, 20];
test_mul_opt(q, n, a, b, c)?;
test_mul_opt(&param, a, b, c)?;
let a: Vec<u64> = vec![0u64, 0, 0, 2];
let b: Vec<u64> = vec![0u64, 0, 0, 2];
let c: Vec<u64> = vec![0u64, 0, 65533, 0];
test_mul_opt(q, n, a, b, c)?;
test_mul_opt(&param, a, b, c)?;
// TODO more testvectors
Ok(())
}
fn test_mul_opt(
q: u64,
n: usize,
param: &RingParam,
a: Vec<u64>,
b: Vec<u64>,
expected_c: Vec<u64>,
) -> Result<()> {
assert_eq!(a.len(), n);
assert_eq!(b.len(), n);
assert_eq!(a.len(), param.n);
assert_eq!(b.len(), param.n);
// let a: [Zq<Q>; N] = array::from_fn(|i| Zq::from_u64(a[i]));
let mut a = Rq::from_vec_u64(q, n, a);
let mut a = Rq::from_vec_u64(&param, a);
// let b: [Zq<Q>; N] = array::from_fn(|i| Zq::from_u64(b[i]));
let mut b = Rq::from_vec_u64(q, n, b);
let mut b = Rq::from_vec_u64(&param, b);
// let expected_c: [Zq<Q>; N] = array::from_fn(|i| Zq::from_u64(expected_c[i]));
let expected_c = Rq::from_vec_u64(q, n, expected_c);
let expected_c = Rq::from_vec_u64(&param, expected_c);
let c = mul_mut(&mut a, &mut b);
assert_eq!(c, expected_c);
@ -783,26 +774,25 @@ mod tests {
#[test]
fn test_rq_decompose() -> Result<()> {
let q: u64 = 16;
let n: usize = 4;
let param = RingParam { q: 16, n: 4 };
let beta = 4;
let l = 2;
let a = Rq::from_vec_u64(q, n, vec![7u64, 14, 3, 6]);
let a = Rq::from_vec_u64(&param, vec![7u64, 14, 3, 6]);
let d = a.decompose(beta, l);
assert_eq!(
d[0].coeffs(),
vec![1u64, 3, 0, 1]
.iter()
.map(|e| Zq::from_u64(q, *e))
.map(|e| Zq::from_u64(param.q, *e))
.collect::<Vec<_>>()
);
assert_eq!(
d[1].coeffs(),
vec![3u64, 2, 3, 2]
.iter()
.map(|e| Zq::from_u64(q, *e))
.map(|e| Zq::from_u64(param.q, *e))
.collect::<Vec<_>>()
);
Ok(())

+ 4
- 1
arith/src/ring_torus.rs

@ -25,11 +25,14 @@ pub struct Tn {
impl Ring for Tn {
type C = T64;
type Params = usize; // n
type Param = usize; // n
// const Q: u64 = u64::MAX; // WIP
// const N: usize = N;
fn param(&self) -> Self::Param {
self.n
}
fn coeffs(&self) -> Vec<T64> {
self.coeffs.to_vec()
}

+ 4
- 1
arith/src/torus.rs

@ -16,11 +16,14 @@ pub struct T64(pub u64);
// `Tn<1>`.
impl Ring for T64 {
type C = T64;
type Params = ();
// type Param = ();
// const Q: u64 = u64::MAX; // WIP
// const N: usize = 1;
fn param(&self) -> Self::Param {
()
}
fn coeffs(&self) -> Vec<T64> {
vec![self.clone()]
}

+ 3
- 3
arith/src/tuple_ring.rs

@ -11,7 +11,7 @@ use std::{
ops::{Add, Mul, Neg, Sub},
};
use crate::Ring;
use crate::{Ring, RingParam};
/// Tuple of K Ring (Rq) elements. We use Vec<R> to allocate it in the heap,
/// since if using a fixed-size array it would overflow the stack.
@ -28,7 +28,7 @@ impl TR {
assert_eq!(r.len(), k);
Self { k, r }
}
pub fn zero(k: usize, r_params: R::Params) -> Self {
pub fn zero(k: usize, r_params: &RingParam) -> Self {
Self {
k,
r: (0..k).into_iter().map(|_| R::zero(r_params)).collect(),
@ -38,7 +38,7 @@ impl TR {
mut rng: impl Rng,
dist: impl Distribution<f64>,
k: usize,
r_params: R::Params,
r_params: &RingParam,
) -> Self {
Self {
k,

+ 87
- 75
bfv/src/lib.rs

@ -10,19 +10,27 @@ use rand::Rng;
use rand_distr::{Normal, Uniform};
use std::ops;
use arith::{Ring, Rq, R};
use arith::{Ring, RingParam, 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;
#[derive(Clone, Copy, Debug)]
pub struct Params {
q: u64,
n: usize,
pub struct Param {
ring: RingParam,
t: u64,
p: u64,
}
impl Param {
// returns the plaintext params
pub fn pt(&self) -> RingParam {
RingParam {
q: self.t,
n: self.ring.n,
}
}
}
#[derive(Clone, Debug)]
pub struct SecretKey(Rq);
@ -49,7 +57,7 @@ impl RLWE {
}
fn tensor(t: u64, a: &Self, b: &Self) -> (Rq, Rq, Rq) {
let (q, n) = (a.0.q, a.0.n);
let (q, n) = (a.0.param.q, a.0.param.n);
// expand Q->PQ // TODO rm
// get the coefficients in Z, ie. interpret a,b \in R (instead of R_q)
@ -83,7 +91,10 @@ impl RLWE {
}
// naive mul in the ring Rq, reusing the ring_n::naive_mul and then applying mod(X^N +1)
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()))
Rq::from_vec_i64(
&a.param.clone(),
arith::ring_n::naive_mul(&a.to_r(), &b.to_r()),
)
}
impl ops::Add<RLWE> for RLWE {
@ -106,7 +117,7 @@ impl BFV {
// const DELTA: u64 = Q / T; // floor
/// generate a new key pair (privK, pubK)
pub fn new_key(mut rng: impl Rng, params: &Params) -> Result<(SecretKey, PublicKey)> {
pub fn new_key(mut rng: impl Rng, params: &Param) -> Result<(SecretKey, PublicKey)> {
// WIP: review probabilities
// let Xi_key = Uniform::new(-1_f64, 1_f64);
@ -115,47 +126,45 @@ impl BFV {
// secret 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)?;
let mut s = Rq::rand_u64(&mut rng, Xi_key, &params.ring)?;
// 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::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 a = Rq::rand_u64(&mut rng, Uniform::new(0_u64, params.ring.q), &params.ring)?;
let e = Rq::rand_f64(&mut rng, Xi_err, &params.ring)?;
let pk: PublicKey = PublicKey(&(&(-a.clone()) * &s) + &e, a.clone()); // TODO rm clones
Ok((SecretKey(s), pk))
}
// note: m is modulus t
pub fn encrypt(mut rng: impl Rng, params: &Params, pk: &PublicKey, m: &Rq) -> Result<RLWE> {
pub fn encrypt(mut rng: impl Rng, params: &Param, 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);
debug_assert_eq!(params.ring, pk.0.param);
debug_assert_eq!(params.t, m.param.q);
debug_assert_eq!(params.ring.n, m.param.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::rand_f64(&mut rng, Xi_key, params.q, params.n)?;
let u = Rq::rand_f64(&mut rng, Xi_key, &params.ring)?;
// 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)?;
let e_1 = Rq::rand_f64(&mut rng, Xi_err, &params.ring)?;
let e_2 = Rq::rand_f64(&mut rng, Xi_err, &params.ring)?;
// migrate m's coeffs to the bigger modulus Q (from T)
let m = m.remodule(params.q);
let c0 = &pk.0 * &u + e_1 + m * (params.q / params.t); // floor(q/t)=DELTA
let m = m.remodule(params.ring.q);
let c0 = &pk.0 * &u + e_1 + m * (params.ring.q / params.t); // floor(q/t)=DELTA
let c1 = &pk.1 * &u + e_2;
Ok(RLWE(c0, c1))
}
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);
pub fn decrypt(param: &Param, sk: &SecretKey, c: &RLWE) -> Rq {
debug_assert_eq!(param.ring, sk.0.param);
debug_assert_eq!(param.ring.q, c.0.param.q);
debug_assert_eq!(param.ring.n, c.0.param.n);
let cs: Rq = &c.0 + &(&c.1 * &sk.0); // done in mod q
@ -164,49 +173,51 @@ impl BFV {
// let c1s = Rq::from_vec_i64(c1s);
// let cs = c.0 + c1s;
let r: Rq = cs.mul_div_round(params.t, params.q);
r.remodule(params.t)
let r: Rq = cs.mul_div_round(param.t, param.ring.q);
r.remodule(param.t)
}
fn add_const(c: &RLWE, m: &Rq) -> RLWE {
let q = c.0.q;
let t = m.q;
let q = c.0.param.q;
let t = m.param.q;
// assuming T<Q, move m from Zq<T> to Zq<Q>
let m = m.remodule(c.0.q);
let m = m.remodule(c.0.param.q);
// TODO rm clones
RLWE(c.0.clone() + m * (q / t), c.1.clone()) // floor(q/t)=DELTA
}
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;
let q = c.0.param.q;
let t = m.param.q;
// assuming T<Q, move m from Zq<T> to Zq<Q>
let m = m.remodule(q);
// encrypt m*Delta without noise, and then perform normal ciphertext multiplication
let md = RLWE(m * (q / t), Rq::zero((q, n))); // floor(q/t)=DELTA
let md = RLWE(m * (q / t), Rq::zero(&c.0.param)); // floor(q/t)=DELTA
RLWE::mul(t, &rlk, &c, &md)
}
fn rlk_key(mut rng: impl Rng, params: &Params, s: &SecretKey) -> Result<RLK> {
let pq = params.p * params.q;
let n = params.n;
fn rlk_key(mut rng: impl Rng, param: &Param, s: &SecretKey) -> Result<RLK> {
let pq = param.p * param.ring.q;
let rlk_param = RingParam {
q: pq,
n: param.ring.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::rand_u64(&mut rng, Uniform::new(0_u64, pq), pq, n)?;
let e = Rq::rand_f64(&mut rng, Xi_err, pq, n)?;
let a = Rq::rand_u64(&mut rng, Uniform::new(0_u64, pq), &rlk_param)?;
let e = Rq::rand_f64(&mut rng, Xi_err, &rlk_param)?;
// let rlk: RLK<PQ, N> = RLK::<PQ, N>(-(&a * &s + e) + (s * s) * P, a.clone());
// TODO rm clones
let rlk: RLK = RLK(
-(tmp_naive_mul(a.clone(), s.clone()) + e)
+ tmp_naive_mul(s.clone(), s.clone()) * params.p,
+ tmp_naive_mul(s.clone(), s.clone()) * param.p,
a.clone(),
);
@ -214,10 +225,10 @@ impl BFV {
}
fn relinearize(rlk: &RLK, c0: &Rq, c1: &Rq, c2: &Rq) -> RLWE {
let pq = rlk.0.q;
let q = c0.q;
let pq = rlk.0.param.q;
let param = c0.param;
let q = param.q;
let p = pq / q;
let n = c0.n;
let c2rlk0: Vec<f64> = (c2.clone().to_r() * rlk.0.clone().to_r())
.coeffs()
@ -231,17 +242,17 @@ impl BFV {
.map(|e| (*e as f64 / p as f64).round())
.collect();
let r0 = Rq::from_vec_f64(q, n, c2rlk0);
let r1 = Rq::from_vec_f64(q, n, c2rlk1);
let r0 = Rq::from_vec_f64(&param, c2rlk0);
let r1 = Rq::from_vec_f64(&param, c2rlk1);
let res = RLWE(c0 + &r0, c1 + &r1);
res
}
fn relinearize_204(rlk: &RLK, c0: &Rq, c1: &Rq, c2: &Rq) -> RLWE {
let pq = rlk.0.q;
let q = c0.q;
let pq = rlk.0.param.q;
let q = c0.param.q;
let p = pq / q;
let n = c0.n;
let n = c0.param.n;
// TODO (in debug) check that all Ns match
// let c2rlk0: Rq<PQ, N> = c2.remodule::<PQ>() * rlk.0.remodule::<PQ>();
@ -269,9 +280,11 @@ mod tests {
#[test]
fn test_encrypt_decrypt() -> Result<()> {
let params = Params {
q: 2u64.pow(16) + 1, // q prime, and 2^q + 1 shape
n: 512,
let params = Param {
ring: RingParam {
q: 2u64.pow(16) + 1, // q prime, and 2^q + 1 shape
n: 512,
},
t: 32, // plaintext modulus
p: 0, // unused in this test
};
@ -282,7 +295,7 @@ mod tests {
let (sk, pk) = BFV::new_key(&mut rng, &params)?;
let msg_dist = Uniform::new(0_u64, params.t);
let m = Rq::rand_u64(&mut rng, msg_dist, params.t, params.n)?;
let m = Rq::rand_u64(&mut rng, msg_dist, &params.pt())?;
let c = BFV::encrypt(&mut rng, &params, &pk, &m)?;
let m_recovered = BFV::decrypt(&params, &sk, &c);
@ -295,9 +308,11 @@ mod tests {
#[test]
fn test_addition() -> Result<()> {
let params = Params {
q: 2u64.pow(16) + 1, // q prime, and 2^q + 1 shape
n: 128,
let params = Param {
ring: RingParam {
q: 2u64.pow(16) + 1, // q prime, and 2^q + 1 shape
n: 128,
},
t: 32, // plaintext modulus
p: 0, // unused in this test
};
@ -308,8 +323,8 @@ mod tests {
let (sk, pk) = BFV::new_key(&mut rng, &params)?;
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 m1 = Rq::rand_u64(&mut rng, msg_dist, &params.pt())?;
let m2 = Rq::rand_u64(&mut rng, msg_dist, &params.pt())?;
let c1 = BFV::encrypt(&mut rng, &params, &pk, &m1)?;
let c2 = BFV::encrypt(&mut rng, &params, &pk, &m2)?;
@ -327,9 +342,8 @@ mod tests {
#[test]
fn test_constant_add_mul() -> Result<()> {
let q: u64 = 2u64.pow(16) + 1; // q prime, and 2^q + 1 shape
let params = Params {
q,
n: 16,
let params = Param {
ring: RingParam { q, n: 16 },
t: 8, // plaintext modulus
p: q * q,
};
@ -339,8 +353,8 @@ mod tests {
let (sk, pk) = BFV::new_key(&mut rng, &params)?;
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 m1 = Rq::rand_u64(&mut rng, msg_dist, &params.pt())?;
let m2_const = Rq::rand_u64(&mut rng, msg_dist, &params.pt())?;
let c1 = BFV::encrypt(&mut rng, &params, &pk, &m1)?;
let c3_add = &c1 + &m2_const;
@ -490,9 +504,8 @@ mod tests {
#[test]
fn test_tensor() -> Result<()> {
let q: u64 = 2u64.pow(16) + 1; // q prime, and 2^q + 1 shape
let params = Params {
q,
n: 16,
let params = Param {
ring: RingParam { q, n: 16 },
t: 2, // plaintext modulus
p: q * q,
};
@ -500,15 +513,15 @@ mod tests {
let msg_dist = Uniform::new(0_u64, params.t);
for _ in 0..1_000 {
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 m1 = Rq::rand_u64(&mut rng, msg_dist, &params.pt())?;
let m2 = Rq::rand_u64(&mut rng, msg_dist, &params.pt())?;
test_tensor_opt(&mut rng, &params, m1, m2)?;
}
Ok(())
}
fn test_tensor_opt(mut rng: impl Rng, params: &Params, m1: Rq, m2: Rq) -> Result<()> {
fn test_tensor_opt(mut rng: impl Rng, params: &Param, m1: Rq, m2: Rq) -> Result<()> {
let (sk, pk) = BFV::new_key(&mut rng, &params)?;
let c1 = BFV::encrypt(&mut rng, &params, &pk, &m1)?;
@ -526,7 +539,7 @@ mod tests {
// &c_c.to_r(),
// &R::<N>::from_vec(arith::ring_n::naive_mul(&sk.0.to_r(), &sk.0.to_r())),
// ));
let m3: Rq = m3.mul_div_round(params.t, params.q); // descale
let m3: Rq = m3.mul_div_round(params.t, params.ring.q); // descale
let m3 = m3.remodule(params.t);
let naive = (m1.clone().to_r() * m2.clone().to_r()).to_rq(params.t); // TODO rm clones
@ -544,9 +557,8 @@ mod tests {
#[test]
fn test_mul_relin() -> Result<()> {
let q: u64 = 2u64.pow(16) + 1; // q prime, and 2^q + 1 shape
let params = Params {
q,
n: 16,
let params = Param {
ring: RingParam { q, n: 16 },
t: 2, // plaintext modulus
p: q * q,
};
@ -555,8 +567,8 @@ mod tests {
let msg_dist = Uniform::new(0_u64, params.t);
for _ in 0..1_000 {
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 m1 = Rq::rand_u64(&mut rng, msg_dist, &params.pt())?;
let m2 = Rq::rand_u64(&mut rng, msg_dist, &params.pt())?;
test_mul_relin_opt(&mut rng, &params, m1, m2)?;
}
@ -564,7 +576,7 @@ mod tests {
Ok(())
}
fn test_mul_relin_opt(mut rng: impl Rng, params: &Params, m1: Rq, m2: Rq) -> Result<()> {
fn test_mul_relin_opt(mut rng: impl Rng, params: &Param, m1: Rq, m2: Rq) -> Result<()> {
let (sk, pk) = BFV::new_key(&mut rng, &params)?;
let rlk = BFV::rlk_key(&mut rng, &params, &sk)?;

+ 30
- 18
ckks/src/lib.rs

@ -5,7 +5,7 @@
#![allow(clippy::upper_case_acronyms)]
#![allow(dead_code)] // TMP
use arith::{Rq, C, R};
use arith::{RingParam, Rq, C, R};
use anyhow::Result;
use rand::Rng;
@ -20,8 +20,7 @@ const ERR_SIGMA: f64 = 3.2;
#[derive(Clone, Copy, Debug)]
pub struct Params {
q: u64,
n: usize,
ring: RingParam,
t: u64,
}
@ -37,7 +36,7 @@ pub struct CKKS {
impl CKKS {
pub fn new(params: &Params, delta: C<f64>) -> Self {
let encoder = Encoder::new(params.n, delta);
let encoder = Encoder::new(params.ring.n, delta);
Self {
params: params.clone(),
encoder,
@ -50,14 +49,14 @@ impl CKKS {
let Xi_key = Uniform::new(-1_f64, 1_f64);
let Xi_err = Normal::new(0_f64, ERR_SIGMA)?;
let e = Rq::rand_f64(&mut rng, Xi_err, params.q, params.n)?;
let e = Rq::rand_f64(&mut rng, Xi_err, &params.ring)?;
let mut s = Rq::rand_f64(&mut rng, Xi_key, params.q, params.n)?;
let mut s = Rq::rand_f64(&mut rng, Xi_key, &params.ring)?;
// since s is going to be multiplied by other Rq elements, already
// compute its NTT
s.compute_evals();
let a = Rq::rand_f64(&mut rng, Xi_key, params.q, params.n)?;
let a = Rq::rand_f64(&mut rng, Xi_key, &params.ring)?;
let pk: PublicKey = PublicKey((&(-a.clone()) * &s) + e, a.clone()); // TODO rm clones
Ok((SecretKey(s), pk))
@ -74,13 +73,13 @@ impl CKKS {
let Xi_key = Uniform::new(-1_f64, 1_f64);
let Xi_err = Normal::new(0_f64, ERR_SIGMA)?;
let e_0 = Rq::rand_f64(&mut rng, Xi_err, params.q, params.n)?;
let e_1 = Rq::rand_f64(&mut rng, Xi_err, params.q, params.n)?;
let e_0 = Rq::rand_f64(&mut rng, Xi_err, &params.ring)?;
let e_1 = Rq::rand_f64(&mut rng, Xi_err, &params.ring)?;
let v = Rq::rand_f64(&mut rng, Xi_key, params.q, params.n)?;
let v = Rq::rand_f64(&mut rng, Xi_key, &params.ring)?;
// let m: Rq = Rq::from(*m);
let m: Rq = m.clone().to_rq(params.q); // TODO rm clone
let m: Rq = m.clone().to_rq(params.ring.q); // TODO rm clone
Ok((m + e_0 + &v * &pk.0.clone(), &v * &pk.1 + e_1))
}
@ -128,7 +127,10 @@ mod tests {
let q: u64 = 2u64.pow(16) + 1;
let n: usize = 32;
let t: u64 = 50;
let params = Params { q, n, t };
let params = Params {
ring: RingParam { q, n },
t,
};
let scale_factor_u64 = 512_u64; // delta
let scale_factor = C::<f64>::new(scale_factor_u64 as f64, 0.0); // delta
@ -139,7 +141,8 @@ mod tests {
let (sk, pk) = ckks.new_key(&mut rng)?;
let m_raw: R = Rq::rand_f64(&mut rng, Uniform::new(0_f64, t as f64), q, n)?.to_r();
let m_raw: R =
Rq::rand_f64(&mut rng, Uniform::new(0_f64, t as f64), &params.ring)?.to_r();
let m = &m_raw * &scale_factor_u64;
let ct = ckks.encrypt(&mut rng, &pk, &m)?;
@ -150,7 +153,7 @@ mod tests {
.iter()
.map(|e| (*e as f64 / (scale_factor_u64 as f64)).round() as u64)
.collect();
let m_decrypted = Rq::from_vec_u64(q, n, m_decrypted);
let m_decrypted = Rq::from_vec_u64(&params.ring, m_decrypted);
// assert_eq!(m_decrypted, Rq::from(m_raw));
assert_eq!(m_decrypted, m_raw.to_rq(q));
}
@ -163,7 +166,10 @@ mod tests {
let q: u64 = 2u64.pow(16) + 1;
let n: usize = 16;
let t: u64 = 8;
let params = Params { q, n, t };
let params = Params {
ring: RingParam { q, n },
t,
};
let scale_factor = C::<f64>::new(512.0, 0.0); // delta
let mut rng = rand::thread_rng();
@ -209,7 +215,10 @@ mod tests {
let q: u64 = 2u64.pow(16) + 1;
let n: usize = 16;
let t: u64 = 8;
let params = Params { q, n, t };
let params = Params {
ring: RingParam { q, n },
t,
};
let scale_factor = C::<f64>::new(1024.0, 0.0); // delta
let mut rng = rand::thread_rng();
@ -252,8 +261,11 @@ mod tests {
fn test_sub() -> Result<()> {
let q: u64 = 2u64.pow(16) + 1;
let n: usize = 16;
let t: u64 = 8;
let params = Params { q, n, t };
let t: u64 = 4;
let params = Params {
ring: RingParam { q, n },
t,
};
let scale_factor = C::<f64>::new(1024.0, 0.0); // delta
let mut rng = rand::thread_rng();

Loading…
Cancel
Save