Skip to main content

module_lattice/
algebra.rs

1use super::truncate::Truncate;
2
3use array::{Array, ArraySize, typenum::U256};
4use core::fmt::Debug;
5use core::ops::{Add, Mul, Neg, Sub};
6use num_traits::PrimInt;
7
8#[cfg(feature = "ctutils")]
9use ctutils::{Choice, CtEq, CtEqSlice};
10#[cfg(feature = "zeroize")]
11use zeroize::Zeroize;
12
13/// Finite field with efficient modular reduction for lattice-based cryptography.
14pub trait Field: Copy + Default + Debug + PartialEq {
15    /// Base integer type used to represent field elements
16    type Int: PrimInt + Default + Debug + From<u8> + Into<u128> + Into<Self::Long> + Truncate<u128>;
17    /// Double-width integer type used for intermediate computations.
18    type Long: PrimInt + From<Self::Int>;
19    /// Quadruple-width integer type used for Barrett reduction.
20    type LongLong: PrimInt;
21
22    /// Field modulus.
23    const Q: Self::Int;
24    /// Field modulus as [`Self::Long`].
25    const QL: Self::Long;
26    /// Field modulus as [`Self::LongLong`].
27    const QLL: Self::LongLong;
28
29    /// Bit shift used in Barrett reduction.
30    const BARRETT_SHIFT: usize;
31    /// Precomputed multiplier for Barrett reduction.
32    const BARRETT_MULTIPLIER: Self::LongLong;
33
34    /// Reduce a value that's already close to the modulus range.
35    fn small_reduce(x: Self::Int) -> Self::Int;
36    /// Reduce a wider value to a field element using Barrett reduction.
37    fn barrett_reduce(x: Self::Long) -> Self::Int;
38}
39
40/// The `define_field` macro creates a zero-sized struct and an implementation of the [`Field`]
41/// trait for that struct.  The caller must specify:
42///
43/// * `$field`: The name of the zero-sized struct to be created
44/// * `$q`: The prime number that defines the field.
45/// * `$int`: The primitive integer type to be used to represent members of the field
46/// * `$long`: The primitive integer type to be used to represent products of two field members.
47///   This type should have roughly twice the bits of `$int`.
48/// * `$longlong`: The primitive integer type to be used to represent products of three field
49///   members. This type should have roughly four times the bits of `$int`.
50#[macro_export]
51macro_rules! define_field {
52    ($field:ident, $int:ty, $long:ty, $longlong:ty, $q:literal) => {
53        $crate::define_field!($field, $int, $long, $longlong, $q, "Finite field");
54    };
55    ($field:ident, $int:ty, $long:ty, $longlong:ty, $q:literal, $doc:expr) => {
56        #[doc = $doc]
57        #[derive(Copy, Clone, Default, Debug, Eq, PartialEq)]
58        pub struct $field;
59
60        impl $crate::Field for $field {
61            type Int = $int;
62            type Long = $long;
63            type LongLong = $longlong;
64
65            const Q: Self::Int = $q;
66            const QL: Self::Long = $q;
67            const QLL: Self::LongLong = $q;
68
69            #[allow(clippy::as_conversions)]
70            const BARRETT_SHIFT: usize = 2 * (Self::Q.ilog2() + 1) as usize;
71            #[allow(clippy::integer_division_remainder_used)]
72            const BARRETT_MULTIPLIER: Self::LongLong = (1 << Self::BARRETT_SHIFT) / Self::QLL;
73
74            fn small_reduce(x: Self::Int) -> Self::Int {
75                use $crate::ctutils::{CtLt, CtSelect};
76                x - Self::Q.ct_select(&0, x.ct_lt(&Self::Q))
77            }
78
79            fn barrett_reduce(x: Self::Long) -> Self::Int {
80                let x: Self::LongLong = x.into();
81                let product = x * Self::BARRETT_MULTIPLIER;
82                let quotient = product >> Self::BARRETT_SHIFT;
83                let remainder = x - quotient * Self::QLL;
84                Self::small_reduce($crate::Truncate::truncate(remainder))
85            }
86        }
87    };
88}
89
90/// An [`Elem`] is a member of the specified prime-order field.
91///
92/// Elements can be added, subtracted, multiplied, and negated, and the overloaded operators will
93/// ensure both that the integer values remain in the field, and that the reductions are done
94/// efficiently.
95///
96/// For addition and subtraction, a simple conditional subtraction is used; for multiplication,
97/// Barrett reduction.
98#[derive(Copy, Clone, Default, Debug, Eq, PartialEq)]
99pub struct Elem<F: Field>(pub F::Int);
100
101impl<F: Field> Elem<F> {
102    /// Create a new field element.
103    pub const fn new(x: F::Int) -> Self {
104        Self(x)
105    }
106}
107
108#[cfg(feature = "ctutils")]
109impl<F: Field> CtEq for Elem<F>
110where
111    F::Int: CtEq,
112{
113    fn ct_eq(&self, other: &Self) -> Choice {
114        self.0.ct_eq(&other.0)
115    }
116}
117
118#[cfg(feature = "ctutils")]
119impl<F: Field<Int: CtEq>> CtEqSlice for Elem<F> {}
120
121#[cfg(feature = "zeroize")]
122impl<F: Field> Zeroize for Elem<F>
123where
124    F::Int: Zeroize,
125{
126    fn zeroize(&mut self) {
127        self.0.zeroize();
128    }
129}
130
131impl<F: Field> Neg for Elem<F> {
132    type Output = Elem<F>;
133
134    fn neg(self) -> Elem<F> {
135        Elem(F::small_reduce(F::Q - self.0))
136    }
137}
138
139impl<F: Field> Add<Elem<F>> for Elem<F> {
140    type Output = Elem<F>;
141
142    fn add(self, rhs: Elem<F>) -> Elem<F> {
143        Elem(F::small_reduce(self.0 + rhs.0))
144    }
145}
146
147impl<F: Field> Sub<Elem<F>> for Elem<F> {
148    type Output = Elem<F>;
149
150    fn sub(self, rhs: Elem<F>) -> Elem<F> {
151        Elem(F::small_reduce(self.0 + F::Q - rhs.0))
152    }
153}
154
155impl<F: Field> Mul<Elem<F>> for Elem<F> {
156    type Output = Elem<F>;
157
158    fn mul(self, rhs: Elem<F>) -> Elem<F> {
159        let lhs: F::Long = self.0.into();
160        let rhs: F::Long = rhs.0.into();
161        let prod = lhs * rhs;
162        Elem(F::barrett_reduce(prod))
163    }
164}
165
166/// A `Polynomial` is a member of the ring `R_q = Z_q[X] / (X^256)` of degree-256 polynomials
167/// over the finite field with prime order `q`.
168///
169/// Polynomials can be added, subtracted, negated, and multiplied by field elements.
170#[derive(Clone, Copy, Default, Debug, PartialEq)]
171pub struct Polynomial<F: Field>(pub Array<Elem<F>, U256>);
172
173impl<F: Field> Polynomial<F> {
174    /// Create a new polynomial.
175    pub const fn new(x: Array<Elem<F>, U256>) -> Self {
176        Self(x)
177    }
178}
179
180#[cfg(feature = "zeroize")]
181impl<F: Field> Zeroize for Polynomial<F>
182where
183    F::Int: Zeroize,
184{
185    fn zeroize(&mut self) {
186        self.0.zeroize();
187    }
188}
189
190impl<F: Field> Add<&Polynomial<F>> for &Polynomial<F> {
191    type Output = Polynomial<F>;
192
193    fn add(self, rhs: &Polynomial<F>) -> Polynomial<F> {
194        Polynomial(
195            self.0
196                .iter()
197                .zip(rhs.0.iter())
198                .map(|(&x, &y)| x + y)
199                .collect(),
200        )
201    }
202}
203
204impl<F: Field> Sub<&Polynomial<F>> for &Polynomial<F> {
205    type Output = Polynomial<F>;
206
207    fn sub(self, rhs: &Polynomial<F>) -> Polynomial<F> {
208        Polynomial(
209            self.0
210                .iter()
211                .zip(rhs.0.iter())
212                .map(|(&x, &y)| x - y)
213                .collect(),
214        )
215    }
216}
217
218impl<F: Field> Mul<&Polynomial<F>> for Elem<F> {
219    type Output = Polynomial<F>;
220
221    fn mul(self, rhs: &Polynomial<F>) -> Polynomial<F> {
222        Polynomial(rhs.0.iter().map(|&x| self * x).collect())
223    }
224}
225
226impl<F: Field> Neg for &Polynomial<F> {
227    type Output = Polynomial<F>;
228
229    fn neg(self) -> Polynomial<F> {
230        Polynomial(self.0.iter().map(|&x| -x).collect())
231    }
232}
233
234#[cfg(feature = "ctutils")]
235impl<F: Field> CtEq for Polynomial<F>
236where
237    F::Int: CtEq,
238{
239    fn ct_eq(&self, other: &Self) -> Choice {
240        self.0.ct_eq(&other.0)
241    }
242}
243
244#[cfg(feature = "ctutils")]
245impl<F: Field<Int: CtEq>> CtEqSlice for Polynomial<F> {}
246
247/// A `Vector` is a vector of polynomials from `R_q` of length `K`.
248///
249/// Vectors can be added, subtracted, negated, and multiplied by field elements.
250#[derive(Clone, Default, Debug, PartialEq)]
251pub struct Vector<F: Field, K: ArraySize>(pub Array<Polynomial<F>, K>);
252
253impl<F: Field, K: ArraySize> Vector<F, K> {
254    /// Create a new vector.
255    pub const fn new(x: Array<Polynomial<F>, K>) -> Self {
256        Self(x)
257    }
258}
259
260#[cfg(feature = "zeroize")]
261impl<F: Field, K: ArraySize> Zeroize for Vector<F, K>
262where
263    F::Int: Zeroize,
264{
265    fn zeroize(&mut self) {
266        self.0.zeroize();
267    }
268}
269
270impl<F: Field, K: ArraySize> Add<Vector<F, K>> for Vector<F, K> {
271    type Output = Vector<F, K>;
272    fn add(self, rhs: Vector<F, K>) -> Vector<F, K> {
273        Add::add(&self, &rhs)
274    }
275}
276impl<F: Field, K: ArraySize> Add<&Vector<F, K>> for &Vector<F, K> {
277    type Output = Vector<F, K>;
278
279    fn add(self, rhs: &Vector<F, K>) -> Vector<F, K> {
280        Vector(
281            self.0
282                .iter()
283                .zip(rhs.0.iter())
284                .map(|(x, y)| x + y)
285                .collect(),
286        )
287    }
288}
289
290impl<F: Field, K: ArraySize> Sub<&Vector<F, K>> for &Vector<F, K> {
291    type Output = Vector<F, K>;
292
293    fn sub(self, rhs: &Vector<F, K>) -> Vector<F, K> {
294        Vector(
295            self.0
296                .iter()
297                .zip(rhs.0.iter())
298                .map(|(x, y)| x - y)
299                .collect(),
300        )
301    }
302}
303
304impl<F: Field, K: ArraySize> Mul<&Vector<F, K>> for Elem<F> {
305    type Output = Vector<F, K>;
306
307    fn mul(self, rhs: &Vector<F, K>) -> Vector<F, K> {
308        Vector(rhs.0.iter().map(|x| self * x).collect())
309    }
310}
311
312impl<F: Field, K: ArraySize> Neg for &Vector<F, K> {
313    type Output = Vector<F, K>;
314
315    fn neg(self) -> Vector<F, K> {
316        Vector(self.0.iter().map(|x| -x).collect())
317    }
318}
319
320#[cfg(feature = "ctutils")]
321impl<F: Field, K: ArraySize> CtEq for Vector<F, K>
322where
323    F::Int: CtEq,
324{
325    fn ct_eq(&self, other: &Self) -> Choice {
326        self.0.ct_eq(&other.0)
327    }
328}
329
330#[cfg(feature = "ctutils")]
331impl<F: Field<Int: CtEq>, K: ArraySize> CtEqSlice for Vector<F, K> {}
332
333/// An `NttPolynomial` is a member of the NTT algebra `T_q = Z_q[X]^256` of 256-tuples of field
334/// elements.
335///
336/// NTT polynomials can be added and subtracted, negated, and multiplied by scalars.
337/// We do not define multiplication of NTT polynomials here: that is defined by the downstream
338/// crate using the [`MultiplyNtt`] trait.
339///
340/// We also do not define the mappings between normal polynomials and NTT polynomials (i.e., between
341/// `R_q` and `T_q`).
342#[derive(Clone, Default, Debug, Eq, PartialEq)]
343pub struct NttPolynomial<F: Field>(pub Array<Elem<F>, U256>);
344
345impl<F: Field> NttPolynomial<F> {
346    /// Create a new NTT polynomial.
347    pub const fn new(x: Array<Elem<F>, U256>) -> Self {
348        Self(x)
349    }
350}
351
352impl<F: Field> Add<&NttPolynomial<F>> for &NttPolynomial<F> {
353    type Output = NttPolynomial<F>;
354
355    fn add(self, rhs: &NttPolynomial<F>) -> NttPolynomial<F> {
356        NttPolynomial(
357            self.0
358                .iter()
359                .zip(rhs.0.iter())
360                .map(|(&x, &y)| x + y)
361                .collect(),
362        )
363    }
364}
365
366impl<F: Field> Sub<&NttPolynomial<F>> for &NttPolynomial<F> {
367    type Output = NttPolynomial<F>;
368
369    fn sub(self, rhs: &NttPolynomial<F>) -> NttPolynomial<F> {
370        NttPolynomial(
371            self.0
372                .iter()
373                .zip(rhs.0.iter())
374                .map(|(&x, &y)| x - y)
375                .collect(),
376        )
377    }
378}
379
380impl<F: Field> Mul<&NttPolynomial<F>> for Elem<F> {
381    type Output = NttPolynomial<F>;
382
383    fn mul(self, rhs: &NttPolynomial<F>) -> NttPolynomial<F> {
384        NttPolynomial(rhs.0.iter().map(|&x| self * x).collect())
385    }
386}
387
388impl<F> Mul<&NttPolynomial<F>> for &NttPolynomial<F>
389where
390    F: Field + MultiplyNtt,
391{
392    type Output = NttPolynomial<F>;
393
394    fn mul(self, rhs: &NttPolynomial<F>) -> NttPolynomial<F> {
395        F::multiply_ntt(self, rhs)
396    }
397}
398
399/// Perform multiplication in the NTT domain.
400pub trait MultiplyNtt: Field {
401    /// Multiply two NTT polynomials.
402    fn multiply_ntt(lhs: &NttPolynomial<Self>, rhs: &NttPolynomial<Self>) -> NttPolynomial<Self>;
403}
404
405impl<F: Field> Neg for &NttPolynomial<F> {
406    type Output = NttPolynomial<F>;
407
408    fn neg(self) -> NttPolynomial<F> {
409        NttPolynomial(self.0.iter().map(|&x| -x).collect())
410    }
411}
412
413impl<F: Field> From<Array<Elem<F>, U256>> for NttPolynomial<F> {
414    fn from(f: Array<Elem<F>, U256>) -> NttPolynomial<F> {
415        NttPolynomial(f)
416    }
417}
418
419impl<F: Field> From<NttPolynomial<F>> for Array<Elem<F>, U256> {
420    fn from(f_hat: NttPolynomial<F>) -> Array<Elem<F>, U256> {
421        f_hat.0
422    }
423}
424
425#[cfg(feature = "ctutils")]
426impl<F: Field> CtEq for NttPolynomial<F>
427where
428    F::Int: CtEq,
429{
430    fn ct_eq(&self, other: &Self) -> Choice {
431        self.0.ct_eq(&other.0)
432    }
433}
434
435#[cfg(feature = "ctutils")]
436impl<F: Field<Int: CtEq>> CtEqSlice for NttPolynomial<F> {}
437
438#[cfg(feature = "zeroize")]
439impl<F: Field> Zeroize for NttPolynomial<F>
440where
441    F::Int: Zeroize,
442{
443    fn zeroize(&mut self) {
444        self.0.zeroize();
445    }
446}
447
448/// An [`NttVector`] is a vector of polynomials from `T_q` of length `K`.
449///
450/// NTT vectors can be added and subtracted.  If multiplication is defined for NTT polynomials, then
451/// NTT vectors can be multiplied by NTT polynomials, and "multiplied" with each other to produce a
452/// dot product.
453#[derive(Clone, Default, Debug, Eq, PartialEq)]
454pub struct NttVector<F: Field, K: ArraySize>(pub Array<NttPolynomial<F>, K>);
455
456impl<F: Field, K: ArraySize> NttVector<F, K> {
457    /// Create a new NTT vector.
458    pub const fn new(x: Array<NttPolynomial<F>, K>) -> Self {
459        Self(x)
460    }
461}
462
463#[cfg(feature = "ctutils")]
464impl<F: Field, K: ArraySize> CtEq for NttVector<F, K>
465where
466    F::Int: CtEq,
467{
468    fn ct_eq(&self, other: &Self) -> Choice {
469        self.0.ct_eq(&other.0)
470    }
471}
472
473#[cfg(feature = "ctutils")]
474impl<F: Field<Int: CtEq>, K: ArraySize> CtEqSlice for NttVector<F, K> {}
475
476#[cfg(feature = "zeroize")]
477impl<F: Field, K: ArraySize> Zeroize for NttVector<F, K>
478where
479    F::Int: Zeroize,
480{
481    fn zeroize(&mut self) {
482        self.0.zeroize();
483    }
484}
485
486impl<F: Field, K: ArraySize> Add<&NttVector<F, K>> for &NttVector<F, K> {
487    type Output = NttVector<F, K>;
488
489    fn add(self, rhs: &NttVector<F, K>) -> NttVector<F, K> {
490        NttVector(
491            self.0
492                .iter()
493                .zip(rhs.0.iter())
494                .map(|(x, y)| x + y)
495                .collect(),
496        )
497    }
498}
499
500impl<F: Field, K: ArraySize> Sub<&NttVector<F, K>> for &NttVector<F, K> {
501    type Output = NttVector<F, K>;
502
503    fn sub(self, rhs: &NttVector<F, K>) -> NttVector<F, K> {
504        NttVector(
505            self.0
506                .iter()
507                .zip(rhs.0.iter())
508                .map(|(x, y)| x - y)
509                .collect(),
510        )
511    }
512}
513
514impl<F: Field, K: ArraySize> Mul<&NttVector<F, K>> for &NttPolynomial<F>
515where
516    for<'a> &'a NttPolynomial<F>: Mul<&'a NttPolynomial<F>, Output = NttPolynomial<F>>,
517{
518    type Output = NttVector<F, K>;
519
520    fn mul(self, rhs: &NttVector<F, K>) -> NttVector<F, K> {
521        NttVector(rhs.0.iter().map(|x| self * x).collect())
522    }
523}
524
525impl<F: Field, K: ArraySize> Mul<&NttVector<F, K>> for &NttVector<F, K>
526where
527    for<'a> &'a NttPolynomial<F>: Mul<&'a NttPolynomial<F>, Output = NttPolynomial<F>>,
528{
529    type Output = NttPolynomial<F>;
530
531    fn mul(self, rhs: &NttVector<F, K>) -> NttPolynomial<F> {
532        self.0
533            .iter()
534            .zip(rhs.0.iter())
535            .map(|(x, y)| x * y)
536            .fold(NttPolynomial::default(), |x, y| &x + &y)
537    }
538}
539
540/// A `K x L` matrix of NTT-domain polynomials.
541///
542/// Each vector represents a row of the matrix, so that multiplying on the right just requires
543/// iteration.
544///
545/// Multiplication on the right by vectors is the only defined operation, and is only defined when
546/// multiplication of NTT polynomials is defined.
547#[derive(Clone, Default, Debug, PartialEq)]
548pub struct NttMatrix<F: Field, K: ArraySize, L: ArraySize>(pub Array<NttVector<F, L>, K>);
549
550impl<F: Field, K: ArraySize, L: ArraySize> NttMatrix<F, K, L> {
551    /// Create a new NTT matrix.
552    pub const fn new(x: Array<NttVector<F, L>, K>) -> Self {
553        Self(x)
554    }
555}
556
557impl<F: Field, K: ArraySize, L: ArraySize> Mul<&NttVector<F, L>> for &NttMatrix<F, K, L>
558where
559    for<'a> &'a NttPolynomial<F>: Mul<&'a NttPolynomial<F>, Output = NttPolynomial<F>>,
560{
561    type Output = NttVector<F, K>;
562
563    fn mul(self, rhs: &NttVector<F, L>) -> NttVector<F, K> {
564        NttVector(self.0.iter().map(|x| x * rhs).collect())
565    }
566}
567
568#[cfg(feature = "ctutils")]
569impl<F: Field, K: ArraySize, L: ArraySize> CtEq for NttMatrix<F, K, L>
570where
571    F::Int: CtEq,
572{
573    fn ct_eq(&self, other: &Self) -> Choice {
574        self.0.ct_eq(&other.0)
575    }
576}
577
578#[cfg(feature = "ctutils")]
579impl<F: Field<Int: CtEq>, K: ArraySize, L: ArraySize> CtEqSlice for NttMatrix<F, K, L> {}