//! Polynomial ring Z_q[X]/(X^N+1) //! use anyhow::{anyhow, Result}; use rand::{distributions::Distribution, Rng}; use std::array; use std::fmt; use std::iter::Sum; use std::ops::{Add, AddAssign, Mul, Neg, Sub, SubAssign}; use crate::ntt::NTT; use crate::zq::{modulus_u64, Zq}; use crate::Ring; // 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 // use Vec. /// PolynomialRing element, where the PolynomialRing is R = Z_q[X]/(X^n +1) /// The implementation assumes that q is prime. #[derive(Clone, Copy)] pub struct Rq { pub(crate) coeffs: [Zq; N], // evals are set when doig a PRxPR multiplication, so it can be reused in future // multiplications avoiding recomputing it pub(crate) evals: Option<[Zq; N]>, } impl Ring for Rq { type C = Zq; fn coeffs(&self) -> Vec { self.coeffs.to_vec() } fn zero() -> Self { let coeffs = array::from_fn(|_| Zq::zero()); Self { coeffs, evals: None, } } fn rand(mut rng: impl Rng, dist: impl Distribution) -> Self { // let coeffs: [Zq; N] = array::from_fn(|_| Zq::from_u64(dist.sample(&mut rng))); let coeffs: [Zq; N] = array::from_fn(|_| Self::C::rand(&mut rng, &dist)); Self { coeffs, evals: None, } } fn from_vec(coeffs: Vec>) -> Self { let mut p = coeffs; modulus::(&mut p); let coeffs = array::from_fn(|i| p[i]); Self { coeffs, evals: None, } } // returns the decomposition of each polynomial coefficient, such // decomposition will be a vecotor of length N, containint N vectors of Zq fn decompose(&self, beta: u32, l: u32) -> Vec { let elems: Vec>> = self.coeffs.iter().map(|r| r.decompose(beta, l)).collect(); // transpose it let r: Vec>> = (0..elems[0].len()) .map(|i| (0..elems.len()).map(|j| elems[j][i]).collect()) .collect(); // convert it to Rq r.iter().map(|a_i| Self::from_vec(a_i.clone())).collect() } // returns [ [(num/den) * self].round() ] mod q // ie. performs the multiplication and division over f64, and then it rounds the // result, only applying the mod Q at the end fn mul_div_round(&self, num: u64, den: u64) -> Self { let r: Vec = self .coeffs() .iter() .map(|e| ((num as f64 * e.0 as f64) / den as f64).round()) .collect(); Rq::::from_vec_f64(r) } } impl From> for Rq { fn from(r: crate::ring_n::R) -> Self { Self::from_vec( r.coeffs() .iter() .map(|e| Zq::::from_f64(*e as f64)) .collect(), ) } } // apply mod (X^N+1) pub fn modulus(p: &mut Vec>) { if p.len() < N { return; } for i in N..p.len() { p[i - N] = p[i - N].clone() - p[i].clone(); p[i] = Zq(0); } p.truncate(N); } // PR stands for PolynomialRing impl Rq { pub fn coeffs(&self) -> [Zq; N] { self.coeffs } pub fn compute_evals(&mut self) { self.evals = Some(NTT::::ntt(self.coeffs)); } pub fn to_r(self) -> crate::R { crate::R::::from(self) } // TODO rm since it is implemented in Ring trait impl // pub fn zero() -> Self { // let coeffs = array::from_fn(|_| Zq::zero()); // Self { // coeffs, // evals: None, // } // } // this method is mostly for tests pub fn from_vec_u64(coeffs: Vec) -> Self { let coeffs_mod_q = coeffs.iter().map(|c| Zq::from_u64(*c)).collect(); Self::from_vec(coeffs_mod_q) } pub fn from_vec_f64(coeffs: Vec) -> Self { let coeffs_mod_q = coeffs.iter().map(|c| Zq::from_f64(*c)).collect(); Self::from_vec(coeffs_mod_q) } pub fn from_vec_i64(coeffs: Vec) -> Self { let coeffs_mod_q = coeffs.iter().map(|c| Zq::from_f64(*c as f64)).collect(); Self::from_vec(coeffs_mod_q) } pub fn new(coeffs: [Zq; N], evals: Option<[Zq; N]>) -> Self { Self { coeffs, evals } } pub fn rand_abs(mut rng: impl Rng, dist: impl Distribution) -> Result { let coeffs: [Zq; N] = array::from_fn(|_| Zq::from_f64(dist.sample(&mut rng).abs())); Ok(Self { coeffs, evals: None, }) } pub fn rand_f64_abs(mut rng: impl Rng, dist: impl Distribution) -> Result { let coeffs: [Zq; N] = array::from_fn(|_| Zq::from_f64(dist.sample(&mut rng).abs())); Ok(Self { coeffs, evals: None, }) } pub fn rand_f64(mut rng: impl Rng, dist: impl Distribution) -> Result { let coeffs: [Zq; N] = array::from_fn(|_| Zq::from_f64(dist.sample(&mut rng))); Ok(Self { coeffs, evals: None, }) } pub fn rand_u64(mut rng: impl Rng, dist: impl Distribution) -> Result { let coeffs: [Zq; N] = array::from_fn(|_| Zq::from_u64(dist.sample(&mut rng))); Ok(Self { coeffs, evals: None, }) } // WIP. returns random v \in {0,1}. // TODO {-1, 0, 1} pub fn rand_bin(mut rng: impl Rng, dist: impl Distribution) -> Result { let coeffs: [Zq; N] = array::from_fn(|_| Zq::from_bool(dist.sample(&mut rng))); Ok(Rq { coeffs, evals: None, }) } // Warning: this method will behave differently depending on the values P and Q: // if Q=P, it crops to mod P pub fn remodule(&self) -> Rq { Rq::::from_vec_u64(self.coeffs().iter().map(|m_i| m_i.0).collect()) } /// perform the mod switch operation from Q to Q', where Q2=Q' pub fn mod_switch(&self) -> Rq { Rq:: { coeffs: array::from_fn(|i| self.coeffs[i].mod_switch::()), evals: None, } } // applies mod(T) to all coefficients of self pub fn coeffs_mod(&self) -> Self { Rq::::from_vec_u64( self.coeffs() .iter() .map(|m_i| modulus_u64::(m_i.0)) .collect(), ) } // TODO review if needed, or if with this interface pub fn mul_by_matrix(&self, m: &Vec>>) -> Result>> { matrix_vec_product(m, &self.coeffs.to_vec()) } pub fn mul_by_zq(&self, s: &Zq) -> Self { Self { coeffs: array::from_fn(|i| self.coeffs[i] * *s), evals: None, } } pub fn mul_by_u64(&self, s: u64) -> Self { let s = Zq::from_u64(s); Self { coeffs: array::from_fn(|i| self.coeffs[i] * s), // coeffs: self.coeffs.iter().map(|&e| e * s).collect(), evals: None, } } pub fn mul_by_f64(&self, s: f64) -> Self { Self { coeffs: array::from_fn(|i| Zq::from_f64(self.coeffs[i].0 as f64 * s)), evals: None, } } pub fn mul(&mut self, rhs: &mut Self) -> Self { mul_mut(self, rhs) } // divides by the given scalar 's' and rounds, returning a Rq // TODO rm pub fn div_round(&self, s: u64) -> Self { let r: Vec = self .coeffs() .iter() .map(|e| (e.0 as f64 / s as f64).round()) .collect(); Rq::::from_vec_f64(r) } fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { // TODO simplify let mut str = ""; let mut zero = true; for (i, coeff) in self.coeffs.iter().enumerate().rev() { if coeff.0 == 0 { continue; } zero = false; f.write_str(str)?; if coeff.0 != 1 { f.write_str(coeff.0.to_string().as_str())?; if i > 0 { f.write_str("*")?; } } if coeff.0 == 1 && i == 0 { f.write_str(coeff.0.to_string().as_str())?; } if i == 1 { f.write_str("x")?; } else if i > 1 { f.write_str("x^")?; f.write_str(i.to_string().as_str())?; } str = " + "; } if zero { f.write_str("0")?; } f.write_str(" mod Z_")?; f.write_str(Q.to_string().as_str())?; f.write_str("/(X^")?; f.write_str(N.to_string().as_str())?; f.write_str("+1)")?; Ok(()) } pub fn infinity_norm(&self) -> u64 { self.coeffs() .iter() .map(|x| if x.0 > (Q / 2) { Q - x.0 } else { x.0 }) .fold(0, |a, b| a.max(b)) } pub fn mod_centered_q(&self) -> crate::ring_n::R { self.to_r().mod_centered_q::() } } pub fn matrix_vec_product(m: &Vec>>, v: &Vec>) -> Result>> { // assert_eq!(m.len(), m[0].len()); // TODO change to returning err // assert_eq!(m.len(), v.len()); if m.len() != m[0].len() { return Err(anyhow!("expected 'm' to be a square matrix")); } if m.len() != v.len() { return Err(anyhow!( "m.len: {} should be equal to v.len(): {}", m.len(), v.len(), )); } Ok(m.iter() .map(|row| { row.iter() .zip(v.iter()) .map(|(&row_i, &v_i)| row_i * v_i) .sum() }) .collect::>>()) } pub fn transpose(m: &[Vec>]) -> Vec>> { // TODO case when m[0].len()=0 // TODO non square matrix let mut r: Vec>> = vec![vec![Zq(0); m[0].len()]; m.len()]; for (i, m_row) in m.iter().enumerate() { for (j, m_ij) in m_row.iter().enumerate() { r[j][i] = *m_ij; } } r } impl PartialEq for Rq { fn eq(&self, other: &Self) -> bool { self.coeffs == other.coeffs } } impl Add> for Rq { type Output = Self; fn add(self, rhs: Self) -> Self { Self { coeffs: array::from_fn(|i| self.coeffs[i] + rhs.coeffs[i]), evals: None, } // Self { // coeffs: self // .coeffs // .iter() // .zip(rhs.coeffs) // .map(|(a, b)| *a + b) // .collect(), // evals: None, // } // Self(r.iter_mut().map(|e| e.r#mod()).collect()) // TODO mod should happen auto in + } } impl Add<&Rq> for &Rq { type Output = Rq; fn add(self, rhs: &Rq) -> Self::Output { Rq { coeffs: array::from_fn(|i| self.coeffs[i] + rhs.coeffs[i]), evals: None, } } } impl AddAssign for Rq { fn add_assign(&mut self, rhs: Self) { for i in 0..N { self.coeffs[i] += rhs.coeffs[i]; } } } impl Sum> for Rq { fn sum(iter: I) -> Self where I: Iterator, { let mut acc = Rq::::zero(); for e in iter { acc += e; } acc } } impl Sub> for Rq { type Output = Self; fn sub(self, rhs: Self) -> Self { Self { coeffs: array::from_fn(|i| self.coeffs[i] - rhs.coeffs[i]), evals: None, } } } impl Sub<&Rq> for &Rq { type Output = Rq; fn sub(self, rhs: &Rq) -> Self::Output { Rq { coeffs: array::from_fn(|i| self.coeffs[i] - rhs.coeffs[i]), evals: None, } } } impl SubAssign for Rq { fn sub_assign(&mut self, rhs: Self) { for i in 0..N { self.coeffs[i] -= rhs.coeffs[i]; } } } impl Mul> for Rq { type Output = Self; fn mul(self, rhs: Self) -> Self { mul(&self, &rhs) } } impl Mul<&Rq> for &Rq { type Output = Rq; fn mul(self, rhs: &Rq) -> Self::Output { mul(self, rhs) } } // mul by Zq element impl Mul> for Rq { type Output = Self; fn mul(self, s: Zq) -> Self { self.mul_by_zq(&s) } } impl Mul<&Zq> for &Rq { type Output = Rq; fn mul(self, s: &Zq) -> Self::Output { self.mul_by_zq(s) } } // mul by u64 impl Mul for Rq { type Output = Self; fn mul(self, s: u64) -> Self { self.mul_by_u64(s) } } impl Mul<&u64> for &Rq { type Output = Rq; fn mul(self, s: &u64) -> Self::Output { self.mul_by_u64(*s) } } // mul by f64 impl Mul for Rq { type Output = Self; fn mul(self, s: f64) -> Self { self.mul_by_f64(s) } } impl Mul<&f64> for &Rq { type Output = Rq; fn mul(self, s: &f64) -> Self::Output { self.mul_by_f64(*s) } } impl Neg for Rq { type Output = Self; fn neg(self) -> Self::Output { Self { coeffs: array::from_fn(|i| -self.coeffs[i]), evals: None, } } } // note: this assumes that Q is prime fn mul_mut(lhs: &mut Rq, rhs: &mut Rq) -> Rq { // reuse evaluations if already computed if !lhs.evals.is_some() { lhs.evals = Some(NTT::::ntt(lhs.coeffs)); }; if !rhs.evals.is_some() { rhs.evals = Some(NTT::::ntt(rhs.coeffs)); }; let lhs_evals = lhs.evals.unwrap(); let rhs_evals = rhs.evals.unwrap(); let c_ntt: [Zq; N] = array::from_fn(|i| lhs_evals[i] * rhs_evals[i]); let c = NTT::::intt(c_ntt); Rq::new(c, Some(c_ntt)) } // note: this assumes that Q is prime // TODO impl karatsuba for non-prime Q fn mul(lhs: &Rq, rhs: &Rq) -> Rq { // reuse evaluations if already computed let lhs_evals = if lhs.evals.is_some() { lhs.evals.unwrap() } else { NTT::::ntt(lhs.coeffs) }; let rhs_evals = if rhs.evals.is_some() { rhs.evals.unwrap() } else { NTT::::ntt(rhs.coeffs) }; let c_ntt: [Zq; N] = array::from_fn(|i| lhs_evals[i] * rhs_evals[i]); let c = NTT::::intt(c_ntt); Rq::new(c, Some(c_ntt)) } impl fmt::Display for Rq { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.fmt(f)?; Ok(()) } } impl fmt::Debug for Rq { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.fmt(f)?; Ok(()) } } #[cfg(test)] mod tests { use super::*; #[test] fn test_polynomial_ring() { // the test values used are generated with SageMath const Q: u64 = 7; const N: usize = 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(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(vec![0u64, 1, 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::<7, 4>::from_vec_u64(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(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(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(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(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 assert_eq!((a.clone() + b.clone()).to_string(), "0 mod Z_7/(X^3+1)"); assert_eq!((&a + &b).to_string(), "0 mod Z_7/(X^3+1)"); // assert_eq!((a.0.clone() + b.0.clone()).to_string(), "[0, 0, 0]"); // TODO // sub assert_eq!( (a.clone() - b.clone()).to_string(), "x^2 + x + 1 mod Z_7/(X^3+1)" ); } #[test] fn test_mul() -> Result<()> { const Q: u64 = 2u64.pow(16) + 1; const N: usize = 4; let a: [u64; N] = [1u64, 2, 3, 4]; let b: [u64; N] = [1u64, 2, 3, 4]; let c: [u64; N] = [65513, 65517, 65531, 20]; test_mul_opt::(a, b, c)?; let a: [u64; N] = [0u64, 0, 0, 2]; let b: [u64; N] = [0u64, 0, 0, 2]; let c: [u64; N] = [0u64, 0, 65533, 0]; test_mul_opt::(a, b, c)?; // TODO more testvectors Ok(()) } fn test_mul_opt( a: [u64; N], b: [u64; N], expected_c: [u64; N], ) -> Result<()> { let a: [Zq; N] = array::from_fn(|i| Zq::from_u64(a[i])); let mut a = Rq::new(a, None); let b: [Zq; N] = array::from_fn(|i| Zq::from_u64(b[i])); let mut b = Rq::new(b, None); let expected_c: [Zq; N] = array::from_fn(|i| Zq::from_u64(expected_c[i])); let expected_c = Rq::new(expected_c, None); let c = mul_mut(&mut a, &mut b); assert_eq!(c, expected_c); Ok(()) } #[test] fn test_rq_decompose() -> Result<()> { const Q: u64 = 16; const N: usize = 4; let beta = 4; let l = 2; let a = Rq::::from_vec_u64(vec![7u64, 14, 3, 6]); let d = a.decompose(beta, l); assert_eq!( d[0].coeffs().to_vec(), vec![1u64, 3, 0, 1] .iter() .map(|e| Zq::::from_u64(*e)) .collect::>() ); assert_eq!( d[1].coeffs().to_vec(), vec![3u64, 2, 3, 2] .iter() .map(|e| Zq::::from_u64(*e)) .collect::>() ); Ok(()) } }