arnaucube
arnaucube/fhe-studyPublic Self-hosted repository
fhe-study/arithmetic/src/ring.rs
arithmetic/src/ring.rsRaw
  1//! Polynomial ring Z[X]/(X^N+1)
  2//!
  3
  4use anyhow::{anyhow, Result};
  5use rand::{distributions::Distribution, Rng};
  6use std::array;
  7use std::fmt;
  8use std::ops;
  9
 10use crate::ntt::NTT;
 11use crate::zq::Zq;
 12
 13// PolynomialRing element, where the PolynomialRing is R = Z[X]/(X^n +1)
 14#[derive(Clone, Copy, Debug)]
 15pub struct R<const N: usize>([i64; N]);
 16
 17impl<const Q: u64, const N: usize> From<crate::ringq::Rq<Q, N>> for R<N> {
 18    fn from(rq: crate::ringq::Rq<Q, N>) -> Self {
 19        Self::from_vec_u64(rq.coeffs().to_vec().iter().map(|e| e.0).collect())
 20    }
 21}
 22
 23impl<const N: usize> R<N> {
 24    pub fn coeffs(&self) -> [i64; N] {
 25        self.0
 26    }
 27    pub fn to_rq<const Q: u64>(self) -> crate::Rq<Q, N> {
 28        crate::Rq::<Q, N>::from(self)
 29    }
 30
 31    pub fn from_vec(coeffs: Vec<i64>) -> Self {
 32        let mut p = coeffs;
 33        modulus::<N>(&mut p);
 34        Self(array::from_fn(|i| p[i]))
 35    }
 36    // this method is mostly for tests
 37    pub fn from_vec_u64(coeffs: Vec<u64>) -> Self {
 38        let coeffs_i64 = coeffs.iter().map(|c| *c as i64).collect();
 39        Self::from_vec(coeffs_i64)
 40    }
 41    pub fn from_vec_f64(coeffs: Vec<f64>) -> Self {
 42        let coeffs_i64 = coeffs.iter().map(|c| c.round() as i64).collect();
 43        Self::from_vec(coeffs_i64)
 44    }
 45    pub fn new(coeffs: [i64; N]) -> Self {
 46        Self(coeffs)
 47    }
 48    pub fn mul_by_i64(&self, s: i64) -> Self {
 49        Self(array::from_fn(|i| self.0[i] * s))
 50    }
 51    // performs the multiplication and division over f64, and then it rounds the
 52    // result, only applying the mod Q at the end
 53    pub fn mul_div_round<const Q: u64>(&self, num: u64, den: u64) -> crate::Rq<Q, N> {
 54        let r: Vec<f64> = self
 55            .coeffs()
 56            .iter()
 57            .map(|e| ((num as f64 * *e as f64) / den as f64).round())
 58            .collect();
 59        crate::Rq::<Q, N>::from_vec_f64(r)
 60    }
 61}
 62pub fn mul_div_round<const Q: u64, const N: usize>(
 63    v: Vec<i64>,
 64    num: u64,
 65    den: u64,
 66) -> crate::Rq<Q, N> {
 67    // dbg!(&v);
 68    let r: Vec<f64> = v
 69        .iter()
 70        .map(|e| ((num as f64 * *e as f64) / den as f64).round())
 71        .collect();
 72    // dbg!(&r);
 73    crate::Rq::<Q, N>::from_vec_f64(r)
 74}
 75
 76// apply mod (X^N+1)
 77pub fn modulus<const N: usize>(p: &mut Vec<i64>) {
 78    if p.len() < N {
 79        return;
 80    }
 81    for i in N..p.len() {
 82        p[i - N] = p[i - N].clone() - p[i].clone();
 83        p[i] = 0;
 84    }
 85    p.truncate(N);
 86}
 87
 88impl<const N: usize> PartialEq for R<N> {
 89    fn eq(&self, other: &Self) -> bool {
 90        self.0 == other.0
 91    }
 92}
 93impl<const N: usize> ops::Add<R<N>> for R<N> {
 94    type Output = Self;
 95
 96    fn add(self, rhs: Self) -> Self {
 97        Self(array::from_fn(|i| self.0[i] + rhs.0[i]))
 98    }
 99}
100impl<const N: usize> ops::Add<&R<N>> for &R<N> {
101    type Output = R<N>;
102
103    fn add(self, rhs: &R<N>) -> Self::Output {
104        R(array::from_fn(|i| self.0[i] + rhs.0[i]))
105    }
106}
107impl<const N: usize> ops::Sub<R<N>> for R<N> {
108    type Output = Self;
109
110    fn sub(self, rhs: Self) -> Self {
111        Self(array::from_fn(|i| self.0[i] - rhs.0[i]))
112    }
113}
114impl<const N: usize> ops::Sub<&R<N>> for &R<N> {
115    type Output = R<N>;
116
117    fn sub(self, rhs: &R<N>) -> Self::Output {
118        R(array::from_fn(|i| self.0[i] - rhs.0[i]))
119    }
120}
121impl<const N: usize> ops::Mul<R<N>> for R<N> {
122    type Output = Self;
123
124    fn mul(self, rhs: Self) -> Self {
125        naive_poly_mul(&self, &rhs)
126    }
127}
128impl<const N: usize> ops::Mul<&R<N>> for &R<N> {
129    type Output = R<N>;
130
131    fn mul(self, rhs: &R<N>) -> Self::Output {
132        naive_poly_mul(self, rhs)
133    }
134}
135
136// TODO with NTT(?)
137pub fn naive_poly_mul<const N: usize>(poly1: &R<N>, poly2: &R<N>) -> R<N> {
138    let poly1: Vec<i128> = poly1.0.iter().map(|c| *c as i128).collect();
139    let poly2: Vec<i128> = poly2.0.iter().map(|c| *c as i128).collect();
140    let mut result: Vec<i128> = vec![0; (N * 2) - 1];
141    for i in 0..N {
142        for j in 0..N {
143            result[i + j] = result[i + j] + poly1[i] * poly2[j];
144        }
145    }
146
147    // apply mod (X^N + 1))
148    R::<N>::from_vec(result.iter().map(|c| *c as i64).collect())
149}
150pub fn naive_mul<const N: usize>(poly1: &R<N>, poly2: &R<N>) -> Vec<i64> {
151    let poly1: Vec<i128> = poly1.0.iter().map(|c| *c as i128).collect();
152    let poly2: Vec<i128> = poly2.0.iter().map(|c| *c as i128).collect();
153    let mut result = vec![0; (N * 2) - 1];
154    for i in 0..N {
155        for j in 0..N {
156            result[i + j] = result[i + j] + poly1[i] * poly2[j];
157        }
158    }
159    result.iter().map(|c| *c as i64).collect()
160}
161
162// mul by u64
163impl<const N: usize> ops::Mul<u64> for R<N> {
164    type Output = Self;
165
166    fn mul(self, s: u64) -> Self {
167        self.mul_by_i64(s as i64)
168    }
169}
170impl<const N: usize> ops::Mul<&u64> for &R<N> {
171    type Output = R<N>;
172
173    fn mul(self, s: &u64) -> Self::Output {
174        self.mul_by_i64(*s as i64)
175    }
176}
177
178impl<const N: usize> ops::Neg for R<N> {
179    type Output = Self;
180
181    fn neg(self) -> Self::Output {
182        Self(array::from_fn(|i| -self.0[i]))
183    }
184}