Skip to main content

bouncycastle_mlkem/
matrix.rs

1//! These are somewhat unnecessary wrappers around simple arrays, but they are helpful for clearly
2//! keeping the types and sizes obvious.
3
4use core::ops::{Index, IndexMut};
5
6use crate::mlkem::{N, q};
7use crate::polynomial;
8use crate::polynomial::Polynomial;
9use bouncycastle_utils::secret::ZeroizablePrimitive;
10
11#[derive(Clone)]
12/// A matrix over the ML-KEM ring.
13pub struct Matrix<const k: usize, const l: usize> {
14    /*pub(crate)*/ mat: [[Polynomial; l]; k],
15}
16
17/// Convenience function to avoid ".0" all over the place.
18impl<const k: usize, const l: usize> Index<usize> for Matrix<k, l> {
19    type Output = [Polynomial; l];
20
21    fn index(&self, index: usize) -> &Self::Output {
22        &self.mat[index]
23    }
24}
25/// Convenience function to avoid ".0" all over the place.
26impl<const k: usize, const l: usize> IndexMut<usize> for Matrix<k, l> {
27    fn index_mut(&mut self, index: usize) -> &mut Self::Output {
28        &mut self.mat[index]
29    }
30}
31
32impl<const k: usize, const l: usize> Matrix<k, l> {
33    pub(crate) fn new() -> Self {
34        Self { mat: [[(); l]; k].map(|_| [(); l].map(|_| Polynomial::new())) }
35    }
36
37    /// FIPS 204 Algorithm 48 MatrixVectorNTT(𝐌, 𝐯)
38    /// Computes the product 𝐌 ∘̂ 𝐯_hat of a matrix 𝐌_hat and a vector 𝐯_hat over 𝑇𝑞.
39    /// Input: 𝑘, ℓ ∈ ℕ, 𝐌 ∈ 𝑇𝑞 𝑘×ℓ
40    /// Performs dot product multiplication of this matrix by a vector
41    /// Input: vector of length l
42    /// Output: vector of length k
43    ///
44    /// transpose: False will multiply A, where as True will multiply A^T
45    pub(crate) fn matrix_vector_ntt<const transpose: bool>(&self, v: &Vector<l>) -> Vector<k> {
46        let mut w = Vector::<k>::new();
47        for i in 0..k {
48            // split out the 0 case to skip a no-op add_ntt()
49            w[i] = if transpose {
50                polynomial::base_mult_montgomery(&self.mat[0][i], &v[0])
51            } else {
52                polynomial::base_mult_montgomery(&self.mat[i][0], &v[0])
53            };
54
55            let mut w1: Polynomial;
56            for j in 1..l {
57                // dot product a vector into a matrix: multiply the input vector
58                // into each row of the matrix, then sum the results to produce a vector of
59                // length k.
60                w1 = if transpose {
61                    polynomial::base_mult_montgomery(&self.mat[j][i], &v[j])
62                } else {
63                    polynomial::base_mult_montgomery(&self.mat[i][j], &v[j])
64                };
65
66                w[i].add(&w1);
67            }
68        }
69
70        // In the non-transposed case (keygen), we act in montgomery domain; otherwise (encaps / decaps) we reduce normally.
71        if transpose {
72            w.reduce();
73        } else {
74            w.convert_to_mont();
75        }
76
77        w
78    }
79}
80
81#[derive(Clone, Copy)]
82pub(crate) struct Vector<const k: usize> {
83    pub(crate) vec: [Polynomial; k],
84}
85
86/// Convenience function to avoid ".0" all over the place.
87impl<const k: usize> Index<usize> for Vector<k> {
88    type Output = Polynomial;
89
90    fn index(&self, index: usize) -> &Self::Output {
91        &self.vec[index]
92    }
93}
94/// Convenience function to avoid ".0" all over the place.
95impl<const k: usize> IndexMut<usize> for Vector<k> {
96    fn index_mut(&mut self, index: usize) -> &mut Self::Output {
97        &mut self.vec[index]
98    }
99}
100
101impl<const k: usize> ZeroizablePrimitive for Vector<k> {
102    const ZEROED: Self = Self::new();
103}
104
105impl<const k: usize> Vector<k> {
106    pub(crate) const fn new() -> Self {
107        Self { vec: [Polynomial::new(); k] }
108    }
109
110    /// Algorithm 46 AddVectorNTT(𝐯, 𝐰)̂
111    /// Computes the sum 𝐯_hat + 𝐰_hat of two vectors 𝐯_hat, 𝐰_hat over 𝑇𝑞.
112    /// Input: ℓ ∈ ℕ, v_hat ∈ T^ℓ, w_hat ∈ 𝑇^ℓ
113    /// Output: u_hat ∈ T^ℓ_𝑞.
114    /// Add another vector to this vector
115    pub(crate) fn add_vector_ntt(&mut self, s: &Self) {
116        for i in 0..k {
117            // perform Montgomery addition of each polynomial in the vector
118            self[i].add(&s[i]);
119        }
120    }
121
122    pub(crate) fn dot_product(&self, v: &Self) -> Polynomial {
123        // split out the 0 case to skip a no-op add_ntt()
124        let mut w = polynomial::base_mult_montgomery(&self[0], &v[0]);
125
126        for i in 1..k {
127            let w1 = polynomial::base_mult_montgomery(&self[i], &v[i]);
128            w.add(&w1);
129        }
130        // Note: This function DOES NOT perform modular reduction, as the current
131        // construction of ML-KEM only reduces modulo q when it's necessary.
132        // w.poly_reduce();
133
134        w
135    }
136
137    pub(crate) fn reduce(&mut self) {
138        for i in 0..k {
139            self[i].poly_reduce();
140        }
141    }
142
143    pub(crate) fn ntt(&mut self) {
144        for i in 0..k {
145            self[i].ntt();
146        }
147    }
148
149    pub(crate) fn inv_ntt(&mut self) {
150        for i in 0..k {
151            self[i].inv_ntt();
152        }
153    }
154
155    pub(crate) fn convert_to_mont(&mut self) {
156        for i in 0..k {
157            self[i].convert_to_mont();
158        }
159    }
160
161    /// This is an optimized version of
162    ///   ByteEncode_𝑑𝑢( Compress_𝑑𝑢(𝐮) )
163    /// which packs a polynomial vector according to the packing coefficient dv
164    pub(crate) fn compress_pol_vec<const du: i16>(&self, out: &mut [u8]) {
165        // make sure we have received a dv
166        assert!(du == 10 || du == 11);
167
168        // make sure we were given the right size output buffer
169        // each of the N i16's will take dv bits
170        debug_assert_eq!(out.len(), k * (N * (du as usize) / 8));
171
172        // No conditional_sub_q needed (as done in bc-java): callers must reduce() first,
173        // so coefficients are in [0, q) (barrett_reduce, floor variant). The Compress mask `& (2^du - 1)` folds
174        // mod q, so values in [q, 2q) would also be correct. WARNING: the `as u32` cast
175        // below REQUIRES non-negative coefficients. That is to say DO NOT switch barrett_reduce to a
176        // signed/centered variant (e.g. pq-crystals' rounded form) without restoring a
177        // reduction here, or this will silently produce garbage.
178        // let mut s = self.clone();
179        // s.conditional_sub_q();
180
181        let mut idx = 0;
182        match du {
183            10 => {
184                // MLKEM512 and MLKEM 768
185                let mut t = [0i16; 4];
186                for i in 0..k {
187                    for j in 0..N / 4 {
188                        // fill the temp array t
189                        for (l, item) in t.iter_mut().enumerate() {
190                            *item = (((((self[i][4 * j + l] as u32) << 10) as i32
191                                + (q as i32 / 2))
192                                / q as i32)
193                                & 0x3FF) as i16;
194                        }
195
196                        out[idx] = t[0] as u8;
197                        out[idx + 1] = ((t[0] >> 8) | (t[1] << 2)) as u8;
198                        out[idx + 2] = ((t[1] >> 6) | (t[2] << 4)) as u8;
199                        out[idx + 3] = ((t[2] >> 4) | (t[3] << 6)) as u8;
200                        out[idx + 4] = (t[3] >> 2) as u8;
201                        idx += 5;
202                    }
203                }
204            }
205            11 => {
206                let mut t = [0i16; 8];
207                for i in 0..k {
208                    for j in 0..N / 8 {
209                        for (l, item) in t.iter_mut().enumerate() {
210                            *item = (((((self[i][8 * j + l] as u32) << 11) as i32
211                                + (q as i32 / 2))
212                                / q as i32)
213                                & 0x7FF) as i16;
214                        }
215
216                        out[idx] = t[0] as u8;
217                        out[idx + 1] = ((t[0] >> 8) | (t[1] << 3)) as u8;
218                        out[idx + 2] = ((t[1] >> 5) | (t[2] << 6)) as u8;
219                        out[idx + 3] = (t[2] >> 2) as u8;
220                        out[idx + 4] = ((t[2] >> 10) | (t[3] << 1)) as u8;
221                        out[idx + 5] = ((t[3] >> 7) | (t[4] << 4)) as u8;
222                        out[idx + 6] = ((t[4] >> 4) | (t[5] << 7)) as u8;
223                        out[idx + 7] = (t[5] >> 1) as u8;
224                        out[idx + 8] = ((t[5] >> 9) | (t[6] << 2)) as u8;
225                        out[idx + 9] = ((t[6] >> 6) | (t[7] << 5)) as u8;
226                        out[idx + 10] = (t[7] >> 3) as u8;
227                        idx += 11;
228                    }
229                }
230            }
231            _ => unreachable!(),
232        }
233    }
234
235    pub(crate) fn decompress_pol_vec<const du: i16>(compressed_u: &[u8]) -> Vector<k> {
236        let mut u = Vector::<k>::new();
237
238        // make sure we have received a dv
239        assert!(du == 10 || du == 11);
240
241        // make sure we were given the right size output buffer
242        // each of the N i16's will take dv bits
243        debug_assert_eq!(compressed_u.len(), k * (N * (du as usize) / 8));
244
245        let mut idx = 0;
246
247        match du {
248            10 => {
249                // MLKEM512 and MLKEM768
250                let mut t = [0i16; 4];
251                for i in 0..k {
252                    for j in 0..(N / 4) {
253                        t[0] = ((compressed_u[idx] as u16) | (compressed_u[idx + 1] as u16) << 8)
254                            as i16;
255                        t[1] = (((compressed_u[idx + 1] as u16) >> 2)
256                            | (compressed_u[idx + 2] as u16) << 6)
257                            as i16;
258                        t[2] = (((compressed_u[idx + 2] as u16) >> 4)
259                            | (compressed_u[idx + 3] as u16) << 4)
260                            as i16;
261                        t[3] = (((compressed_u[idx + 3] as u16) >> 6)
262                            | (compressed_u[idx + 4] as u16) << 2)
263                            as i16;
264                        idx += 5;
265                        for (l, item) in t.iter().enumerate() {
266                            u[i][4 * j + l] =
267                                ((((*item & 0x3FF) as i32) * (q as i32) + 512) >> 10) as i16;
268                        }
269                    }
270                }
271            }
272            11 => {
273                // MLKEM1024
274                let mut t = [0i16; 8];
275                for i in 0..k {
276                    for j in 0..N / 8 {
277                        t[0] = (compressed_u[idx] as i32
278                            | ((compressed_u[idx + 1] as u16) as i32) << 8)
279                            as i16;
280                        t[1] = ((compressed_u[idx + 1] >> 3) as i32
281                            | ((compressed_u[idx + 2] as u16) as i32) << 5)
282                            as i16;
283                        t[2] = ((compressed_u[idx + 2] >> 6) as i32
284                            | ((compressed_u[idx + 3] as u16) as i32) << 2
285                            | (((compressed_u[idx + 4] as i32) << 10) as u16) as i32)
286                            as i16;
287                        t[3] = ((compressed_u[idx + 4] >> 1) as i32
288                            | ((compressed_u[idx + 5] as u16) as i32) << 7)
289                            as i16;
290                        t[4] = ((compressed_u[idx + 5] >> 4) as i32
291                            | ((compressed_u[idx + 6] as u16) as i32) << 4)
292                            as i16;
293                        t[5] = ((compressed_u[idx + 6] >> 7) as i32
294                            | ((compressed_u[idx + 7] as u16) as i32) << 1
295                            | (((compressed_u[idx + 8] as i32) << 9) as u16) as i32)
296                            as i16;
297                        t[6] = ((compressed_u[idx + 8] >> 2) as i32
298                            | ((compressed_u[idx + 9] as u16) as i32) << 6)
299                            as i16;
300                        t[7] = ((compressed_u[idx + 9] >> 5) as i32
301                            | ((compressed_u[idx + 10] as u16) as i32) << 3)
302                            as i16;
303                        idx += 11;
304                        for (l, item) in t.iter().enumerate() {
305                            u[i][8 * j + l] =
306                                ((((*item & 0x7FF) as i32) * (q as i32) + 1024) >> 11) as i16;
307                        }
308                    }
309                }
310            }
311            _ => unreachable!(),
312        }
313
314        u
315    }
316}