use rand::{distributions::Distribution, Rng}; use std::array; use std::fmt; use std::ops; use crate::zq::Zq; use anyhow::{anyhow, Result}; // PolynomialRing element, where the PolynomialRing is R = Z_q[X]/(X^n +1) #[derive(Clone, Copy)] pub struct PR { 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]>, } // TODO define a trait "PolynomialRingTrait" or similar, so that when other structs use it can just // use the trait and not need to add '' to their params // 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 PR { pub fn coeffs(&self) -> [Zq; N] { self.coeffs } pub 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, } } // this method is mostly for tests pub fn from_vec_u64(coeffs: Vec) -> Self { let coeffs_mod_q = coeffs.iter().map(|c| Zq::new(*c)).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(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, }) } // 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(PR { coeffs, evals: None, }) } 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 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 PR { fn eq(&self, other: &Self) -> bool { self.coeffs == other.coeffs } } impl ops::Add> for PR { 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 ops::Add<&PR> for &PR { type Output = PR; fn add(self, rhs: &PR) -> Self::Output { PR { coeffs: array::from_fn(|i| self.coeffs[i] + rhs.coeffs[i]), evals: None, } } } impl ops::Sub> for PR { type Output = Self; fn sub(self, rhs: Self) -> Self { Self { coeffs: array::from_fn(|i| self.coeffs[i] - rhs.coeffs[i]), evals: None, } } } impl ops::Sub<&PR> for &PR { type Output = PR; fn sub(self, rhs: &PR) -> Self::Output { PR { coeffs: array::from_fn(|i| self.coeffs[i] - rhs.coeffs[i]), evals: None, } } } impl ops::Neg for PR { type Output = Self; fn neg(self) -> Self::Output { Self { coeffs: array::from_fn(|i| -self.coeffs[i]), evals: None, } } } impl fmt::Display for PR { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.fmt(f)?; Ok(()) } } impl fmt::Debug for PR { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.fmt(f)?; Ok(()) } } #[cfg(test)] mod tests { use super::*; #[test] fn poly_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 = PR::::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 = PR::::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 = PR::<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 = PR::::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 = PR::::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 = PR::::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 = PR::::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)" ); } }