fhe-study/arith/src/ring_torus.rs
arith/src/ring_torus.rsRaw
1//! π_<N,q>[X] = β_<N,q>[X] / β€_<N,q>[X], polynomials modulo X^N+1 with
2//! coefficients in π_Q.
3//!
4//! Note: this is not an algebraic ring, since internal-product is not well
5//! defined. But since we work over the discrete torus π_q, which we identify as
6//! πq = β€/qβ€ β β€q, whith q=64. Since we allow product between πq elements and
7//! u64, we fit it into the `Ring` trait (from ring.rs) so that we can compose
8//! the π_<N,q> implementation with the other objects from the code.
9
10use rand::{distributions::Distribution, Rng};
11use std::array;
12use std::iter::Sum;
13use std::ops::{Add, AddAssign, Mul, Neg, Sub, SubAssign};
14
15use crate::{ring::Ring, torus::T64, Rq, Zq};
16
17/// π_<N,Q>[X] = π<Q>[X]/(X^N +1), polynomials modulo X^N+1 with coefficients in
18/// π, where Q=2^64.
19#[derive(Clone, Copy, Debug)]
20pub struct Tn<const N: usize>(pub [T64; N]);
21
22impl<const N: usize> Ring for Tn<N> {
23 type C = T64;
24
25 const Q: u64 = u64::MAX; // WIP
26 const N: usize = N;
27
28 fn coeffs(&self) -> Vec<T64> {
29 self.0.to_vec()
30 }
31
32 fn zero() -> Self {
33 Self(array::from_fn(|_| T64::zero()))
34 }
35
36 fn rand(mut rng: impl Rng, dist: impl Distribution<f64>) -> Self {
37 Self(array::from_fn(|_| T64::rand(&mut rng, &dist)))
38 }
39
40 fn from_vec(coeffs: Vec<Self::C>) -> Self {
41 let mut p = coeffs;
42 modulus::<N>(&mut p);
43 Self(array::from_fn(|i| p[i]))
44 }
45
46 fn decompose(&self, beta: u32, l: u32) -> Vec<Self> {
47 let elems: Vec<Vec<T64>> = self.0.iter().map(|r| r.decompose(beta, l)).collect();
48 // transpose it
49 let r: Vec<Vec<T64>> = (0..elems[0].len())
50 .map(|i| (0..elems.len()).map(|j| elems[j][i]).collect())
51 .collect();
52 // convert it to Tn<N>
53 r.iter().map(|a_i| Self::from_vec(a_i.clone())).collect()
54 }
55
56 fn remodule<const P: u64>(&self) -> Tn<N> {
57 todo!()
58 // Rq::<P, N>::from_vec_u64(self.coeffs().iter().map(|m_i| m_i.0).collect())
59 }
60
61 // fn mod_switch<const P: u64>(&self) -> impl Ring {
62 fn mod_switch<const P: u64>(&self) -> Rq<P, N> {
63 // unimplemented!()
64 // TODO WIP
65 let coeffs = array::from_fn(|i| Zq::<P>::from_u64(self.0[i].mod_switch::<P>().0));
66 Rq::<P, N> {
67 coeffs,
68 evals: None,
69 }
70 }
71
72 /// returns [ [(num/den) * self].round() ] mod q
73 /// ie. performs the multiplication and division over f64, and then it rounds the
74 /// result, only applying the mod Q at the end
75 fn mul_div_round(&self, num: u64, den: u64) -> Self {
76 let r: Vec<T64> = self
77 .coeffs()
78 .iter()
79 .map(|e| T64(((num as f64 * e.0 as f64) / den as f64).round() as u64))
80 .collect();
81 Self::from_vec(r)
82 }
83}
84
85impl<const N: usize> Tn<N> {
86 // multiply self by X^-h
87 pub fn left_rotate(&self, h: usize) -> Self {
88 dbg!(&h);
89 dbg!(&N);
90 let h = h % N;
91 assert!(h < N);
92 let c = self.0;
93 // c[h], c[h+1], c[h+2], ..., c[n-1], -c[0], -c[1], ..., -c[h-1]
94 // let r: Vec<T64> = vec![c[h..N], c[0..h].iter().map(|&c_i| -c_i).collect()].concat();
95 dbg!(&h);
96 let r: Vec<T64> = c[h..N]
97 .iter()
98 .copied()
99 .chain(c[0..h].iter().map(|&x| -x))
100 .collect();
101 Self::from_vec(r)
102 }
103
104 pub fn from_vec_u64(v: Vec<u64>) -> Self {
105 let coeffs = v.iter().map(|c| T64(*c)).collect();
106 Self::from_vec(coeffs)
107 }
108}
109
110// apply mod (X^N+1)
111pub fn modulus<const N: usize>(p: &mut Vec<T64>) {
112 if p.len() < N {
113 return;
114 }
115 for i in N..p.len() {
116 p[i - N] = p[i - N].clone() - p[i].clone();
117 p[i] = T64::zero();
118 }
119 p.truncate(N);
120}
121
122impl<const N: usize> Add<Tn<N>> for Tn<N> {
123 type Output = Self;
124
125 fn add(self, rhs: Self) -> Self {
126 Self(array::from_fn(|i| self.0[i] + rhs.0[i]))
127 }
128}
129impl<const N: usize> Add<&Tn<N>> for &Tn<N> {
130 type Output = Tn<N>;
131
132 fn add(self, rhs: &Tn<N>) -> Self::Output {
133 Tn(array::from_fn(|i| self.0[i] + rhs.0[i]))
134 }
135}
136impl<const N: usize> AddAssign for Tn<N> {
137 fn add_assign(&mut self, rhs: Self) {
138 for i in 0..N {
139 self.0[i] += rhs.0[i];
140 }
141 }
142}
143
144impl<const N: usize> Sum<Tn<N>> for Tn<N> {
145 fn sum<I>(iter: I) -> Self
146 where
147 I: Iterator<Item = Self>,
148 {
149 let mut acc = Tn::<N>::zero();
150 for e in iter {
151 acc += e;
152 }
153 acc
154 }
155}
156
157impl<const N: usize> Sub<Tn<N>> for Tn<N> {
158 type Output = Self;
159
160 fn sub(self, rhs: Self) -> Self {
161 Self(array::from_fn(|i| self.0[i] - rhs.0[i]))
162 }
163}
164impl<const N: usize> Sub<&Tn<N>> for &Tn<N> {
165 type Output = Tn<N>;
166
167 fn sub(self, rhs: &Tn<N>) -> Self::Output {
168 Tn(array::from_fn(|i| self.0[i] - rhs.0[i]))
169 }
170}
171
172impl<const N: usize> SubAssign for Tn<N> {
173 fn sub_assign(&mut self, rhs: Self) {
174 for i in 0..N {
175 self.0[i] -= rhs.0[i];
176 }
177 }
178}
179
180impl<const N: usize> Neg for Tn<N> {
181 type Output = Self;
182
183 fn neg(self) -> Self::Output {
184 Tn(array::from_fn(|i| -self.0[i]))
185 }
186}
187
188impl<const N: usize> PartialEq for Tn<N> {
189 fn eq(&self, other: &Self) -> bool {
190 self.0 == other.0
191 }
192}
193
194impl<const N: usize> Mul<Tn<N>> for Tn<N> {
195 type Output = Self;
196
197 fn mul(self, rhs: Self) -> Self {
198 naive_poly_mul(&self, &rhs)
199 }
200}
201impl<const N: usize> Mul<&Tn<N>> for &Tn<N> {
202 type Output = Tn<N>;
203
204 fn mul(self, rhs: &Tn<N>) -> Self::Output {
205 naive_poly_mul(self, rhs)
206 }
207}
208
209fn naive_poly_mul<const N: usize>(poly1: &Tn<N>, poly2: &Tn<N>) -> Tn<N> {
210 let poly1: Vec<u128> = poly1.0.iter().map(|c| c.0 as u128).collect();
211 let poly2: Vec<u128> = poly2.0.iter().map(|c| c.0 as u128).collect();
212 let mut result: Vec<u128> = vec![0; (N * 2) - 1];
213 for i in 0..N {
214 for j in 0..N {
215 result[i + j] = result[i + j] + poly1[i] * poly2[j];
216 }
217 }
218
219 // apply mod (X^N + 1))
220 modulus_u128::<N>(&mut result);
221
222 Tn(array::from_fn(|i| T64(result[i] as u64)))
223}
224fn modulus_u128<const N: usize>(p: &mut Vec<u128>) {
225 if p.len() < N {
226 return;
227 }
228 for i in N..p.len() {
229 // p[i - N] = p[i - N].clone() - p[i].clone();
230 p[i - N] = p[i - N].wrapping_sub(p[i]);
231 p[i] = 0;
232 }
233 p.truncate(N);
234}
235
236impl<const N: usize> Mul<T64> for Tn<N> {
237 type Output = Self;
238 fn mul(self, s: T64) -> Self {
239 Self(array::from_fn(|i| self.0[i] * s))
240 }
241}
242// mul by u64
243impl<const N: usize> Mul<u64> for Tn<N> {
244 type Output = Self;
245 fn mul(self, s: u64) -> Self {
246 Self(array::from_fn(|i| self.0[i] * s))
247 }
248}
249impl<const N: usize> Mul<&u64> for &Tn<N> {
250 type Output = Tn<N>;
251 fn mul(self, s: &u64) -> Self::Output {
252 Tn::<N>(array::from_fn(|i| self.0[i] * *s))
253 }
254}
255
256#[cfg(test)]
257mod tests {
258 use super::*;
259
260 #[test]
261 fn test_left_rotate() {
262 const N: usize = 4;
263 let f = Tn::<N>::from_vec(
264 vec![2i64, 3, -4, -1]
265 .iter()
266 .map(|c| T64(*c as u64))
267 .collect(),
268 );
269
270 // expect f*x^-3 == -1 -2x -3x^2 +4x^3
271 assert_eq!(
272 f.left_rotate(3),
273 Tn::<N>::from_vec(
274 vec![-1i64, -2, -3, 4]
275 .iter()
276 .map(|c| T64(*c as u64))
277 .collect(),
278 )
279 );
280 // expect f*x^-1 == 3 -4x -1x^2 -2x^3
281 assert_eq!(
282 f.left_rotate(1),
283 Tn::<N>::from_vec(
284 vec![3i64, -4, -1, -2]
285 .iter()
286 .map(|c| T64(*c as u64))
287 .collect(),
288 )
289 );
290 }
291}