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, Sub, SubAssign};
14
15use crate::{ring::Ring, torus::T64};
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 fn coeffs(&self) -> Vec<T64> {
26 self.0.to_vec()
27 }
28
29 fn zero() -> Self {
30 Self(array::from_fn(|_| T64::zero()))
31 }
32
33 fn rand(mut rng: impl Rng, dist: impl Distribution<f64>) -> Self {
34 Self(array::from_fn(|_| T64::rand_f64(&mut rng, &dist)))
35 }
36
37 fn from_vec(coeffs: Vec<Self::C>) -> Self {
38 let mut p = coeffs;
39 modulus::<N>(&mut p);
40 Self(array::from_fn(|i| p[i]))
41 }
42
43 fn decompose(&self, beta: u32, l: u32) -> Vec<Self> {
44 let elems: Vec<Vec<T64>> = self.0.iter().map(|r| r.decompose(beta, l)).collect();
45 // transpose it
46 let r: Vec<Vec<T64>> = (0..elems[0].len())
47 .map(|i| (0..elems.len()).map(|j| elems[j][i]).collect())
48 .collect();
49 // convert it to Tn<N>
50 r.iter().map(|a_i| Self::from_vec(a_i.clone())).collect()
51 }
52}
53
54// apply mod (X^N+1)
55pub fn modulus<const N: usize>(p: &mut Vec<T64>) {
56 if p.len() < N {
57 return;
58 }
59 for i in N..p.len() {
60 p[i - N] = p[i - N].clone() - p[i].clone();
61 p[i] = T64::zero();
62 }
63 p.truncate(N);
64}
65
66impl<const N: usize> Add<Tn<N>> for Tn<N> {
67 type Output = Self;
68
69 fn add(self, rhs: Self) -> Self {
70 Self(array::from_fn(|i| self.0[i] + rhs.0[i]))
71 }
72}
73impl<const N: usize> Add<&Tn<N>> for &Tn<N> {
74 type Output = Tn<N>;
75
76 fn add(self, rhs: &Tn<N>) -> Self::Output {
77 Tn(array::from_fn(|i| self.0[i] + rhs.0[i]))
78 }
79}
80impl<const N: usize> AddAssign for Tn<N> {
81 fn add_assign(&mut self, rhs: Self) {
82 for i in 0..N {
83 self.0[i] += rhs.0[i];
84 }
85 }
86}
87
88impl<const N: usize> Sum<Tn<N>> for Tn<N> {
89 fn sum<I>(iter: I) -> Self
90 where
91 I: Iterator<Item = Self>,
92 {
93 let mut acc = Tn::<N>::zero();
94 for e in iter {
95 acc += e;
96 }
97 acc
98 }
99}
100
101impl<const N: usize> Sub<Tn<N>> for Tn<N> {
102 type Output = Self;
103
104 fn sub(self, rhs: Self) -> Self {
105 Self(array::from_fn(|i| self.0[i] - rhs.0[i]))
106 }
107}
108impl<const N: usize> Sub<&Tn<N>> for &Tn<N> {
109 type Output = Tn<N>;
110
111 fn sub(self, rhs: &Tn<N>) -> Self::Output {
112 Tn(array::from_fn(|i| self.0[i] - rhs.0[i]))
113 }
114}
115
116impl<const N: usize> SubAssign for Tn<N> {
117 fn sub_assign(&mut self, rhs: Self) {
118 for i in 0..N {
119 self.0[i] -= rhs.0[i];
120 }
121 }
122}
123
124impl<const N: usize> PartialEq for Tn<N> {
125 fn eq(&self, other: &Self) -> bool {
126 self.0 == other.0
127 }
128}
129
130impl<const N: usize> Mul<Tn<N>> for Tn<N> {
131 type Output = Self;
132
133 fn mul(self, rhs: Self) -> Self {
134 naive_poly_mul(&self, &rhs)
135 }
136}
137impl<const N: usize> Mul<&Tn<N>> for &Tn<N> {
138 type Output = Tn<N>;
139
140 fn mul(self, rhs: &Tn<N>) -> Self::Output {
141 naive_poly_mul(self, rhs)
142 }
143}
144
145fn naive_poly_mul<const N: usize>(poly1: &Tn<N>, poly2: &Tn<N>) -> Tn<N> {
146 let poly1: Vec<u128> = poly1.0.iter().map(|c| c.0 as u128).collect();
147 let poly2: Vec<u128> = poly2.0.iter().map(|c| c.0 as u128).collect();
148 let mut result: Vec<u128> = vec![0; (N * 2) - 1];
149 for i in 0..N {
150 for j in 0..N {
151 result[i + j] = result[i + j] + poly1[i] * poly2[j];
152 }
153 }
154
155 // apply mod (X^N + 1))
156 modulus_u128::<N>(&mut result);
157
158 // sanity check: check that there are no coeffs > i64_max
159 assert_eq!(
160 result,
161 Tn::<N>(array::from_fn(|i| T64(result[i] as u64)))
162 .coeffs()
163 .iter()
164 .map(|c| c.0 as u128)
165 .collect::<Vec<_>>()
166 );
167 Tn(array::from_fn(|i| T64(result[i] as u64)))
168}
169fn modulus_u128<const N: usize>(p: &mut Vec<u128>) {
170 if p.len() < N {
171 return;
172 }
173 for i in N..p.len() {
174 p[i - N] = p[i - N].clone() - p[i].clone();
175 p[i] = 0;
176 }
177 p.truncate(N);
178}
179
180// mul by u64
181impl<const N: usize> Mul<u64> for Tn<N> {
182 type Output = Self;
183 fn mul(self, s: u64) -> Self {
184 Self(array::from_fn(|i| self.0[i] * s))
185 }
186}
187impl<const N: usize> Mul<&u64> for &Tn<N> {
188 type Output = Tn<N>;
189 fn mul(self, s: &u64) -> Self::Output {
190 Tn::<N>(array::from_fn(|i| self.0[i] * *s))
191 }
192}