arnaucube
arnaucube/fhe-studyPublic Self-hosted repository
group ring params under a single struct
arnaucube git@arnaucube.com
Tue, 12 Aug 2025 12:09:51 +0000
commit

9e90f094a9cb82401cb58378edca4f4aff1c425a

parent

0c7e078aebfc99b854e4a14ebbde98fc84bca0c5

M arith/src/lib.rs → 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;
M arith/src/ntt.rs → arith/src/ntt.rs

@@ -6,7 +6,11 @@ //! NOTE: initially I implemented it with fixed Q & N, given as constant

//! 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 @@ /// implements the Cooley-Tukey (CT) algorithm. Details at

/// 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 @@ m *= 2;

} // Rq::from_vec((a.q, n), r) Rq { - q, - n, + param: RingParam { q, n }, coeffs: r, evals: None, }

@@ -84,7 +87,7 @@ /// implements the Cooley-Tukey (CT) algorithm. Details at

/// 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 @@ r[i] = r[i] * n_inv;

} // Rq::from_vec((a.q, n), r) Rq { - q, - n, + param: RingParam { q, n }, coeffs: r, evals: None, }

@@ -202,9 +204,10 @@ #[test]

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 @@ #[test]

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);
M arith/src/ring.rs → 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 @@ + Sum<<Self as Mul>::Output>

{ /// 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>;
M arith/src/ring_n.rs → arith/src/ring_n.rs

@@ -23,7 +23,7 @@

// 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 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 @@ .iter()

.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
M arith/src/ring_nq.rs → arith/src/ring_nq.rs

@@ -13,7 +13,7 @@

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 @@ /// 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 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 @@ }

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 @@ .map(|i| (0..elems.len()).map(|j| elems[j][i]).collect())

.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 @@ // Warning: this method will behave differently depending on the values P and Q:

// 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 @@ .coeffs()

.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 @@ let (q, r) = qr;

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 @@ // evals: None,

// } // } // 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 @@

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 @@ }

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 @@ }

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 @@ }

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 @@ // WIP. returns random v \in {0,1}. // TODO {-1, 0, 1}

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 @@ // Rq::<P, N>::from_vec_u64(self.coeffs().iter().map(|m_i| m_i.0).collect())

// } // 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 @@ matrix_vec_product(m, &self.coeffs.to_vec())

} 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 @@ }

} 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 @@ .coeffs()

.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 @@ f.write_str("0")?;

} 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 @@ pub fn infinity_norm(&self) -> u64 {

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 @@ })

.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 @@ }

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 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<Rq> 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 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 @@ type Output = Self;

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 @@ }

// 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 @@ let rhs_evals = rhs.evals.clone().unwrap();

// 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 @@ };

// 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 @@

#[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 @@ }

#[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 @@ }

#[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(())
M arith/src/ring_torus.rs → arith/src/ring_torus.rs

@@ -25,11 +25,14 @@ }

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() }
M arith/src/torus.rs → arith/src/torus.rs

@@ -16,11 +16,14 @@ // implement the `Ring` trait for T64, so that it can be used where we would use

// `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()] }
M arith/src/tuple_ring.rs → arith/src/tuple_ring.rs

@@ -11,7 +11,7 @@ array,

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 @@ pub fn new(k: usize, r: Vec<R>) -> Self {

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 @@ pub fn rand(

mut rng: impl Rng, dist: impl Distribution<f64>, k: usize, - r_params: R::Params, + r_params: &RingParam, ) -> Self { Self { k,
M bfv/src/lib.rs → 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 @@ RLWE(x, y)

} 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 @@ }

} // 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 @@ let Xi_err = Normal::new(0_f64, ERR_SIGMA)?;

// 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 @@ // let c1s = arith::ring_n::naive_mul(&c.1.to_r(), &sk.0.to_r());

// 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 @@ Ok(rlk)

} 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 @@ .iter()

.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 @@ use super::*;

#[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 @@ for _ in 0..100 {

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 @@ }

#[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 @@ for _ in 0..100 {

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 @@

#[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 @@

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 @@

#[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 @@ let mut rng = rand::thread_rng();

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 @@ // + 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 = 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 @@

#[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 @@ let mut rng = rand::thread_rng();

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 @@

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)?;
M ckks/src/lib.rs → ckks/src/lib.rs

@@ -5,7 +5,7 @@ #![allow(non_camel_case_types)]

#![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 @@ }

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 @@

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 @@ let params = self.params;

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 @@ fn test_encrypt_decrypt() -> Result<()> {

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 @@ let ckks = CKKS::new(&params, scale_factor);

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 @@ .coeffs()

.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 @@ fn test_encode_encrypt_decrypt_decode() -> Result<()> {

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 @@ fn test_add() -> Result<()> {

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 @@ #[test]

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();