ring_math/
polynomial_ring.rs

1use std::fmt::Debug;
2use std::fmt::Display;
3use std::hash::Hash;
4use std::ops::Add;
5use std::ops::AddAssign;
6use std::ops::Div;
7use std::ops::Mul;
8use std::ops::MulAssign;
9use std::ops::Neg;
10use std::ops::Sub;
11use std::ops::SubAssign;
12use std::str::FromStr;
13
14use scalarff::BigUint;
15use scalarff::FieldElement;
16
17use super::polynomial::Polynomial;
18use super::Matrix2D;
19use super::Vector;
20
21/// A trait representing a polynomial ring
22/// defined as `T[X]/<Self::modulus()>`
23/// where T is a FieldElement trait
24/// and modulus is a function implemented by the
25/// struct implementing PolynomialRingElement
26pub trait PolynomialRingElement:
27    FieldElement
28    + Add<Output = Self>
29    + AddAssign
30    + Div<Output = Self>
31    + Mul<Output = Self>
32    + MulAssign
33    + Neg<Output = Self>
34    + Sub<Output = Self>
35    + SubAssign
36    + FromStr
37    + PartialEq
38    + Clone
39    + Hash
40    + Debug
41    + From<u64>
42    + From<Polynomial<Self::F>>
43    + Display
44{
45    type F: FieldElement;
46
47    /// Modulus used in remainder division to form
48    /// the polynomial ring.
49    ///
50    /// Operations are done modulo this polynomial,
51    /// in a way similar to scalar fields.
52    ///
53    /// See the division implementation for more info.
54    fn modulus() -> Polynomial<Self::F>;
55
56    /// Return the Polynomial representation of the current value
57    /// Used to automatically implement norms and other functions.
58    fn polynomial(&self) -> &Polynomial<Self::F>;
59
60    /// Create a polynomial ring element from a Polynomial
61    /// with terms in the ring's scalar field.
62    fn from_polynomial(p: Polynomial<Self::F>) -> Self;
63
64    /// Attempt to get a scalar representation of the polynomial.
65    /// If the polynomial degree is > 0 this method will error.
66    fn to_scalar(&self) -> anyhow::Result<Self::F> {
67        if self.polynomial().degree() == 0 {
68            Ok(self.polynomial().coefficients[0].clone())
69        } else {
70            anyhow::bail!("Cannot convert polynomial of degree > 0 to scalar")
71        }
72    }
73
74    /// Calculate the l1 norm for this polynomial. That is
75    /// the summation of all coefficients
76    fn norm_l1(&self) -> BigUint {
77        self.coef().norm_l1()
78    }
79
80    /// Calculate the l2 norm for this polynomial. That is
81    /// the square root of the summation of each coefficient squared
82    ///
83    /// Specifically, we're calculating the square root in the integer
84    /// field, not the prime field
85    fn norm_l2(&self) -> BigUint {
86        self.coef().norm_l2()
87    }
88
89    /// Calculate the l-infinity norm for this polynomial. That is
90    /// the largest coefficient
91    fn norm_max(&self) -> BigUint {
92        self.coef().norm_max()
93    }
94
95    /// Returns a coefficient vector of length equal
96    /// to the ring modulus degree.
97    fn coef(&self) -> Vector<Self::F> {
98        let modulus = Self::modulus();
99        let target_degree = modulus.degree();
100        let poly_coefs = self.polynomial().coef_vec().to_vec();
101        let poly_coefs_len = poly_coefs.len();
102        Vector::from_vec(
103            [
104                poly_coefs,
105                vec![Self::F::zero(); target_degree - poly_coefs_len],
106            ]
107            .concat(),
108        )
109    }
110
111    /// Create a rotated matrix of polynomial coefficients
112    ///
113    /// from LatticeFold page 9
114    /// https://eprint.iacr.org/2024/257.pdf
115    fn rot(&self) -> Matrix2D<Self::F> {
116        let modulus = Self::modulus();
117        let degree = modulus.degree();
118        let mut values = vec![Self::F::zero(); degree * degree];
119        // TODO: check if this logic is correct
120        // technically in each row we're multiplying by X
121        // and then reducing by the modulus. In practice this
122        // results in coefficients being rotated and inverted.
123        //
124        // Test in with various coefficients and moduluses or
125        // mathematically verify
126        for i in 0..degree {
127            let mut coefs = self.coef().to_vec();
128            coefs.rotate_right(i);
129            for j in 0..i {
130                coefs[j] = -coefs[j].clone();
131            }
132            for j in 0..degree {
133                values[j * degree + i] = coefs[j].clone();
134            }
135        }
136        Matrix2D {
137            dimensions: (degree, degree),
138            values,
139        }
140    }
141}
142
143/// Use this to build a concrete instance of a polynomial ring.
144///
145/// e.g.
146/// ```
147/// use scalarff::OxfoiFieldElement;
148/// use scalarff::FieldElement;
149/// use ring_math::Polynomial;
150/// use ring_math::PolynomialRingElement;
151///
152/// ring_math::polynomial_ring!(
153/// Poly64,
154/// OxfoiFieldElement,
155/// {
156///     let mut p = Polynomial::new(vec![OxfoiFieldElement::one()]);
157///     p.term(&OxfoiFieldElement::one(), 64);
158///     p
159/// },
160/// "Poly64"
161/// );
162/// ```
163#[macro_export]
164macro_rules! polynomial_ring {
165    ( $name: ident, $field_element: ident, $modulus: expr, $name_str: expr ) => {
166        #[derive(Clone, Debug, PartialEq, Eq, std::hash::Hash)]
167        pub struct $name(pub Polynomial<$field_element>);
168
169        impl PolynomialRingElement for $name {
170            type F = $field_element;
171
172            fn modulus() -> Polynomial<Self::F> {
173                $modulus
174            }
175
176            fn polynomial(&self) -> &Polynomial<Self::F> {
177                &self.0
178            }
179
180            fn from_polynomial(p: Polynomial<Self::F>) -> Self {
181                $name(p)
182            }
183        }
184
185        impl From<Polynomial<$field_element>> for $name {
186            fn from(p: Polynomial<$field_element>) -> Self {
187                $name(p.div(&Self::modulus()).1)
188            }
189        }
190
191        impl FieldElement for $name {
192            fn zero() -> Self {
193                $name(Polynomial {
194                    coefficients: vec![$field_element::zero()],
195                })
196            }
197
198            fn one() -> Self {
199                $name(Polynomial::identity())
200            }
201
202            fn byte_len() -> usize {
203                Self::modulus().degree() * $field_element::byte_len()
204            }
205
206            fn prime() -> scalarff::BigUint {
207                panic!("cannot retrieve a scalar prime for a polynomial field");
208            }
209
210            fn name_str() -> &'static str {
211                $name_str
212            }
213
214            /// Return a constant polynomial with the provided
215            /// value
216            fn from_usize(value: usize) -> Self {
217                $name(Polynomial {
218                    coefficients: vec![$field_element::from_usize(value)],
219                })
220            }
221
222            fn to_biguint(&self) -> scalarff::BigUint {
223                panic!("cannot retrieve a scalar representation for a polynomial field element");
224            }
225
226            fn from_biguint(_v: &scalarff::BigUint) -> Self {
227                panic!();
228            }
229
230            fn from_bytes_le(bytes: &[u8]) -> Self {
231                $name(Polynomial {
232                    coefficients: bytes
233                        .chunks($field_element::byte_len())
234                        .map(|chunk| $field_element::from_bytes_le(chunk))
235                        .collect::<Vec<_>>(),
236                })
237            }
238
239            fn to_bytes_le(&self) -> Vec<u8> {
240                self.0
241                    .coefficients
242                    .iter()
243                    .flat_map(|v| v.to_bytes_le())
244                    .collect()
245            }
246        }
247
248        impl std::fmt::Display for $name {
249            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
250                write!(
251                    f,
252                    "{}",
253                    self.0
254                        .coefficients
255                        .iter()
256                        .map(|v| v.to_string())
257                        .collect::<Vec<_>>()
258                        .join(",")
259                )
260            }
261        }
262
263        impl std::str::FromStr for $name {
264            type Err = <$field_element as std::str::FromStr>::Err;
265
266            fn from_str(s: &str) -> Result<Self, Self::Err> {
267                Ok(Self(Polynomial {
268                    coefficients: s
269                        .split(',')
270                        .map(|v| $field_element::from_str(v))
271                        .collect::<Result<Vec<_>, _>>()?,
272                }))
273            }
274        }
275
276        impl From<u64> for $name {
277            fn from(value: u64) -> Self {
278                Self::from(Polynomial {
279                    coefficients: vec![$field_element::from(value)],
280                })
281            }
282        }
283
284        impl std::ops::Add for $name {
285            type Output = Self;
286
287            fn add(self, other: Self) -> Self {
288                Self::from(self.0 + other.0)
289            }
290        }
291
292        impl std::ops::Sub for $name {
293            type Output = Self;
294
295            fn sub(self, other: Self) -> Self {
296                Self::from(self.0 - other.0)
297            }
298        }
299
300        impl std::ops::Mul for $name {
301            type Output = Self;
302
303            fn mul(self, other: Self) -> Self {
304                Self::from(self.0 * other.0)
305            }
306        }
307
308        impl std::ops::Div for $name {
309            type Output = Self;
310
311            fn div(self, other: Self) -> Self {
312                // this implementation implies floored division, so discard the remainder
313                Self::from(self.0.div(&other.0).0)
314            }
315        }
316
317        impl std::ops::AddAssign for $name {
318            fn add_assign(&mut self, other: Self) {
319                *self = self.clone() + other;
320            }
321        }
322
323        impl std::ops::MulAssign for $name {
324            fn mul_assign(&mut self, other: Self) {
325                *self = self.clone() * other;
326            }
327        }
328
329        impl std::ops::SubAssign for $name {
330            fn sub_assign(&mut self, other: Self) {
331                *self = self.clone() - other;
332            }
333        }
334
335        impl std::ops::Neg for $name {
336            type Output = Self;
337
338            fn neg(self) -> Self {
339                $name(-self.0.clone())
340            }
341        }
342    };
343}
344
345#[cfg(test)]
346mod test {
347    use scalarff::FieldElement;
348    use scalarff::OxfoiFieldElement;
349
350    use super::Polynomial;
351    use super::PolynomialRingElement;
352
353    polynomial_ring!(
354        Poly64,
355        OxfoiFieldElement,
356        {
357            let mut p = Polynomial::new(vec![OxfoiFieldElement::one()]);
358            p.term(&OxfoiFieldElement::one(), 64);
359            p
360        },
361        "Poly64"
362    );
363
364    #[test]
365    fn scalar_math_in_ring() {
366        for x in 100..500 {
367            for y in 200..600 {
368                let z_scalar = OxfoiFieldElement::from(x) * OxfoiFieldElement::from(y);
369                let z_poly = Poly64::from(x) * Poly64::from(y);
370                assert_eq!(z_poly.polynomial().degree(), 0);
371                assert_eq!(z_poly.polynomial().coefficients[0], z_scalar);
372            }
373        }
374    }
375
376    #[test]
377    fn poly_coefs() {
378        let poly = Poly64::one();
379        // the coefficient vector should always be equal in length
380        // to the degree of the polynomial ring modulus
381        let c = poly.coef();
382        assert_eq!(c.len(), Poly64::modulus().degree());
383    }
384
385    #[test]
386    #[cfg(feature = "rand")]
387    fn poly_rot() {
388        // Testing the relationship as defined in the LatticeFold paper
389        // coef(a * b) = rot(a) * coef(b)
390        // we end up doing multiplication without a division
391        // reduction step (still O(n^2))
392        //
393        // sample two random polynomials
394        let a = Poly64::sample_uniform(&mut rand::thread_rng());
395        let b = Poly64::sample_uniform(&mut rand::thread_rng());
396        // create a rotated matrix of polynomial coefficients
397        let rot_mat = a.rot();
398        let b_coef = b.coef();
399
400        // check the above
401        let expected_coef = (a * b).coef();
402        let actual_coef = rot_mat * b_coef.clone();
403        for i in 0..b_coef.len() {
404            assert_eq!(expected_coef[i], actual_coef[i]);
405        }
406    }
407}