Skip to main content

bouncycastle_mldsa/
matrix.rs

1//! These are somewhat unnecessary wrappers around simple arrays, but they are helpful to me in clearly
2//! keeping the types and sizes obvious.
3
4use crate::aux_functions::multiply_ntt;
5use crate::mldsa::H;
6use crate::polynomial::Polynomial;
7use bouncycastle_core::traits::XOF;
8use bouncycastle_utils::secret::ZeroizablePrimitive;
9use core::ops::{Index, IndexMut};
10
11/// A matrix over the ML-DSA ring.
12#[derive(Clone)]
13pub struct Matrix<const k: usize, const l: usize>(/*pub(crate)*/ [[Polynomial; l]; k]);
14
15/// Convenience function to avoid ".0" all over the place.
16impl<const k: usize, const l: usize> Index<usize> for Matrix<k, l> {
17    type Output = [Polynomial; l];
18
19    fn index(&self, index: usize) -> &Self::Output {
20        &self.0[index]
21    }
22}
23/// Convenience function to avoid ".0" all over the place.
24impl<const k: usize, const l: usize> IndexMut<usize> for Matrix<k, l> {
25    fn index_mut(&mut self, index: usize) -> &mut Self::Output {
26        &mut self.0[index]
27    }
28}
29
30impl<const k: usize, const l: usize> Matrix<k, l> {
31    pub(crate) fn new() -> Self {
32        Self { 0: [[(); l]; k].map(|_| [(); l].map(|_| Polynomial::new())) }
33    }
34
35    /// Algorithm 48 MatrixVectorNTT(𝐌, 𝐯)
36    /// Computes the product 𝐌 βˆ˜Μ‚ 𝐯_hat of a matrix 𝐌_hat and a vector 𝐯_hat over π‘‡π‘ž.
37    /// Input: π‘˜, β„“ ∈ β„•, 𝐌 ∈ π‘‡π‘ž
38    /// π‘˜Γ—β„“ Μ‚ π‘ž .
39    /// Performs dot product multiplication of this matrix by a vector
40    /// Input: vector of length l
41    /// Output: vector of length k
42    pub fn matrix_vector_ntt(&self, v: &Vector<l>) -> Vector<k> {
43        let mut w = Vector::<k>::new();
44        for i in 0..k {
45            // split out the 0 case to skip a no-op add_ntt()
46            w[i].coeffs.copy_from_slice(&multiply_ntt(&self[i][0], &v[0]).coeffs);
47
48            let mut w1: Polynomial;
49            for j in 1..l {
50                // dot product a vector into a matrix: multiply the input vector
51                // into each row of the matrix, then sum the results to produce a vector of
52                // length k.
53                w1 = multiply_ntt(&self[i][j], &v[j]);
54                w[i].add_ntt(&w1);
55            }
56        }
57
58        w
59    }
60}
61
62#[derive(Clone, Copy)]
63pub(crate) struct Vector<const LEN: usize> {
64    pub(crate) vec: [Polynomial; LEN],
65}
66
67/// Convenience function to avoid ".0" all over the place.
68impl<const LEN: usize> Index<usize> for Vector<LEN> {
69    type Output = Polynomial;
70
71    fn index(&self, index: usize) -> &Self::Output {
72        &self.vec[index]
73    }
74}
75/// Convenience function to avoid ".0" all over the place.
76impl<const LEN: usize> IndexMut<usize> for Vector<LEN> {
77    fn index_mut(&mut self, index: usize) -> &mut Self::Output {
78        &mut self.vec[index]
79    }
80}
81
82impl<const LEN: usize> ZeroizablePrimitive for Vector<LEN> {
83    const ZEROED: Self = Self::new();
84}
85
86impl<const LEN: usize> Vector<LEN> {
87    pub(crate) const fn new() -> Self {
88        Self { vec: [Polynomial::new(); LEN] }
89    }
90
91    /// Algorithm 46 AddVectorNTT(𝐯, 𝐰)Μ‚
92    /// Computes the sum 𝐯_hat + 𝐰_hat of two vectors 𝐯_hat, 𝐰_hat over π‘‡π‘ž.
93    /// Input: β„“ ∈ β„•, v_hat ∈ T^β„“, w_hat ∈ 𝑇^β„“
94    /// Output: u_hat ∈ T^β„“_π‘ž.
95    /// Add another vector to this vector
96    pub(crate) fn add_vector_ntt(&mut self, s: &Self) {
97        for i in 0..LEN {
98            // perform montgomery addition of each polynomial in the vector
99            self[i].add_ntt(&s[i]);
100        }
101    }
102
103    pub(crate) fn sub_vector(&self, s: &Self) -> Self {
104        let mut out = self.clone();
105        for i in 0..LEN {
106            out[i].sub(&s[i]);
107        }
108        out
109    }
110
111    /// Algorithm 47 ScalarVectorNTT(𝑐,Μ‚ 𝐯)Μ‚
112    /// Computes the product 𝑐_hat * 𝐯_hat of a scalar 𝑐_hat and a vector 𝐯_hat over π‘‡π‘ž.
113    /// Input: 𝑐_hat ∈ π‘‡π‘ž, β„“ ∈ β„•, 𝐯_hat ∈ 𝑇^β„“
114    /// Output: π‘ž .
115    pub(crate) fn scalar_vector_ntt(&self, w: &Polynomial) -> Self {
116        let mut s_hat = Self::new();
117        for i in 0..LEN {
118            s_hat[i] = multiply_ntt(&self[i], &w);
119        }
120
121        s_hat
122    }
123
124    pub(crate) fn conditional_add_q(&mut self) {
125        for i in 0..LEN {
126            self[i].conditional_add_q();
127        }
128    }
129
130    pub(crate) fn reduce(&mut self) {
131        for i in 0..LEN {
132            self[i].reduce();
133        }
134    }
135
136    pub(crate) fn ntt(&mut self) {
137        for i in 0..LEN {
138            self[i].ntt();
139        }
140    }
141
142    pub(crate) fn inv_ntt(&mut self) {
143        for i in 0..LEN {
144            self[i].inv_ntt();
145        }
146    }
147
148    pub(crate) fn high_bits<const GAMMA2: i32>(&self) -> Self {
149        let mut s = Self::new();
150
151        for i in 0..LEN {
152            s[i] = self[i].high_bits::<GAMMA2>();
153        }
154
155        s
156    }
157
158    pub(crate) fn low_bits<const GAMMA2: i32>(&self) -> Self {
159        let mut s = Self::new();
160
161        for i in 0..LEN {
162            s[i] = self[i].low_bits::<GAMMA2>();
163        }
164
165        s
166    }
167
168    pub(crate) fn shift_left<const d: i32>(&self) -> Self {
169        let mut out = self.clone();
170        for i in 0..LEN {
171            out[i].shift_left::<d>();
172        }
173
174        out
175    }
176
177    pub(crate) fn check_norm<const BOUND: i32>(&self) -> bool {
178        // Fine that this is not constant-time because it is used in a rejection loop -- the early quit leads to rejection.
179        for x in self.vec.iter() {
180            if x.check_norm::<BOUND>() {
181                return true;
182            }
183        }
184        false
185    }
186
187    /// Algorithm 28 w1Encode(𝐰1)
188    /// Encodes a polynomial vector 𝐰1 into a byte string.
189    /// Input: 𝐰1 ∈ π‘…π‘˜ whose polynomial coordinates have coefficients in \[0, (π‘ž βˆ’ 1)/(2𝛾2) βˆ’ 1].
190    /// Output: A byte string representation 𝐰1_tilde ∈ 𝔹32π‘˜β‹…bitlen ((π‘žβˆ’1)/(2𝛾2)βˆ’1)
191    /// Optimized from FIPS 204 to feed into the hash one row at a time to reduce overall memory footprint.
192    pub(crate) fn w1_encode_and_hash<const POLY_W1_PACKED_LEN: usize>(&self, h: &mut H) {
193        // 1: 𝐰̃1 ← ()
194        // Nothing needs to be allocated since it is being fed into the hash row-wise
195
196        // 2: for 𝑖 from 0 to π‘˜ βˆ’ 1 do
197        // 3:   𝐰̃1 ← 𝐰̃1 || SimpleBitPack (𝐰1[𝑖], (π‘ž βˆ’ 1)/(2𝛾2) βˆ’ 1)
198        // 4: end for
199        for w in self.vec.iter() {
200            h.absorb(&w.w1_encode::<POLY_W1_PACKED_LEN>())
201                .expect("absorb before squeeze is infallible");
202        }
203    }
204}