Skip to main content

bouncycastle_mlkem_lowmemory/
polynomial.rs

1//! Represents a polynomial over the ML-DSA ring.
2
3use crate::aux_functions::{
4    ZETAS, ZETAS_INV, barrett_reduce, montgomery_reduce, mul_mont, ntt_base_mult,
5};
6use crate::mlkem::{N, q};
7use core::ops::{Index, IndexMut};
8
9/// A polynomial over the ML-KEM ring.
10/// Dev note: this doesn't strictly need to be pub ... ie there's no good reason for a caller to use this class directly,
11/// but in order to test the Debug and Display traits, you need STD, so those can't be tested from inline tests in this file
12/// and the real unit tests are in a different crate, so here we are.
13///
14/// # ๐Ÿšจ Security ๐Ÿšจ
15/// Polynomials themselves are not inherently secret since sometimes they are part of public keys
16/// and sometimes private keys.
17/// It is the responsibility of the caller to wrap sensitive instances in `Secret<Polynomial>`.
18/// Note: at the moment, nothing in this crate uses `Secret<Polynomial>`, so I have left the `impl ZeroizablePrimitive` commented-out.
19#[derive(Clone, Copy)]
20pub struct Polynomial {
21    pub(crate) coeffs: [i16; N],
22}
23
24/// Convenience function to avoid ".0" all over the place.
25impl Index<usize> for Polynomial {
26    type Output = i16;
27
28    fn index(&self, index: usize) -> &Self::Output {
29        &self.coeffs[index]
30    }
31}
32/// Convenience function to avoid ".0" all over the place.
33impl IndexMut<usize> for Polynomial {
34    fn index_mut(&mut self, index: usize) -> &mut Self::Output {
35        &mut self.coeffs[index]
36    }
37}
38
39// Turn this back on if we want to start tagging things as `Secret<Polynomial>`.
40// impl ZeroizablePrimitive for Polynomial {
41//     const ZEROED: Self = Self::new();
42// }
43
44impl Polynomial {
45    /// Create a new polynomial with all coefficients set to zero.
46    pub const fn new() -> Self {
47        Self { coeffs: [0i16; N] }
48    }
49
50    /// Create a Polynomial from the message m
51    pub(crate) fn from_msg(m: [u8; 32]) -> Self {
52        let mut w = Polynomial::new();
53
54        for (i, b) in m.iter().enumerate() {
55            for j in 0..8 {
56                let mask = -(((*b >> j) & 1) as i16);
57                w[8 * i + j] = mask & ((q + 1) / 2);
58            }
59        }
60
61        w
62    }
63
64    /// Decodes a `Polynomial` into its 32-byte message `m`, implementing the message
65    /// recovery step of K-PKE.Decrypt `ByteEncode_1(Compress_1(self))`,
66    /// (FIPS 203, Alg. 15). Each coefficient yields one message bit: `Compress_1`
67    /// (ยง4.2.1) sets the bit when the coefficient lies nearer `q/2` than `0`, i.e. in
68    /// the central interval `[833, 2496]` for `q = 3329`. The decision is computed
69    /// branchlessly and the bits are packed LSB-first.
70    ///
71    /// # Precondition
72    /// Coefficients are expected to already be canonical in `[0, q]`: the unsigned
73    /// interval test is not periodic mod `q`, so the caller must reduce beforehand (`poly_reduce()`
74    /// in `pke_decrypt`) and no reduction is repeated here.
75    pub(crate) fn to_msg(self) -> [u8; 32] {
76        const LOWER: i32 = q as i32 >> 2; // 832
77        const UPPER: i32 = q as i32 - LOWER; // 2497
78
79        let mut msg = [0u8; 32];
80
81        // Using full reduce() might be expected here.
82        // However, this function is only called by pke_decrypt (see mlkem.rs), which performs a
83        // reduction on every coefficient of the polynomial immediately prior to the call.
84        // For completeness, testing against the bc-test-data set of KATs shows that everything passes
85        // without modular reduction.
86        // self.cond_sub_q();
87
88        // for (i, item) in msg.iter_mut().enumerate().take(N/8) {
89        for i in 0..N / 8 {
90            for j in 0..8 {
91                let c_j = self[8 * i + j] as i32;
92                let t = (((LOWER - c_j) & (c_j - UPPER)) >> 31) & 0x0000000000000001;
93                msg[i] |= (t << j) as u8;
94            }
95        }
96
97        msg
98    }
99
100    // Not currently used. It is left here as a reference since it's useful for debugging if it's
101    // necessary to output values that are normalized to [0,q] to compare against intermediate results
102    // from other libraries.
103    // pub(crate) fn conditional_add_q(&mut self) {
104    //     for x in self.0.iter_mut() {
105    //         *x = conditional_add_q(*x);
106    //     }
107    // }
108
109    pub(crate) fn add(&mut self, w: &Self) {
110        for i in 0..N {
111            self[i] += w[i];
112        }
113    }
114
115    pub(crate) fn sub(&mut self, w: &Self) {
116        for i in 0..N {
117            self[i] -= w[i];
118        }
119    }
120
121    /// Multiplication of two polynomials in NTT domain
122    ///
123    /// Borrowed from:
124    /// https://github.com/pq-crystals/kyber/blob/main/ref/poly.c#L290
125    pub(crate) fn base_mult_montgomery(&mut self, b: &Polynomial) {
126        for i in 0..(N / 4) {
127            let a1: i16 = self[4 * i];
128            let a2: i16 = self[4 * i + 1];
129            ntt_base_mult(
130                self.coeffs.as_mut(),
131                4 * i,
132                a1,
133                a2,
134                b[4 * i],
135                b[4 * i + 1],
136                ZETAS[64 + i],
137            );
138
139            let a1: i16 = self[4 * i + 2];
140            let a2: i16 = self[4 * i + 3];
141            ntt_base_mult(
142                self.coeffs.as_mut(),
143                4 * i + 2,
144                a1,
145                a2,
146                b[4 * i + 2],
147                b[4 * i + 3],
148                -ZETAS[64 + i],
149            );
150        }
151    }
152
153    pub(crate) fn poly_reduce(&mut self) {
154        for i in 0..N {
155            self[i] = barrett_reduce(self[i]);
156        }
157    }
158
159    /// In-place conversion of all coefficients of a polynomial
160    /// from normal domain to Montgomery domain
161    ///
162    /// Borrowed from:
163    /// https://github.com/pq-crystals/kyber/blob/main/ref/poly.c#L307
164    pub(crate) fn convert_to_mont(&mut self) {
165        const F: i16 = ((1u64 << 32) % q as u64) as i16;
166        for i in 0..N {
167            self[i] = montgomery_reduce((self[i] as i32) * (F as i32));
168        }
169    }
170
171    /// This is an optimized version of
172    ///   ByteEncode_๐‘‘๐‘ฃ( Compress_๐‘‘๐‘ฃ(๐‘ฃ) )
173    /// which packs a single polynomial according to the packing coefficient dv
174    pub(crate) fn compress_poly<const dv: i16>(&self, out: &mut [u8]) {
175        // make sure to received a dv
176        debug_assert!(dv == 4 || dv == 5);
177
178        // make sure the right size output buffer is given
179        // each of the N i16's will take dv bits
180        debug_assert_eq!(out.len(), N * (dv as usize) / 8);
181
182        let mut t = [0u8; 8];
183        let mut idx = 0;
184
185        // bc-java has a cond_sub_q() here, however, it is not needed
186        // The reason for this is because a modular reduction is performed immediately
187        // prior to calling pack_ciphertext in mlkem.rs
188        // This can be corroborated by running the corresponding unit tests
189        // let mut s = self.clone();
190        // s.cond_sub_q();
191
192        match dv {
193            4 => {
194                // MLKEM512 and MLKEM768
195                for i in 0..N / 8 {
196                    // fill the temp array t
197                    for (j, item) in t.iter_mut().enumerate() {
198                        *item = ((((self[8 * i + j] as i32) << 4) + (q as i32 / 2)) / (q as i32)
199                            & 15) as u8;
200                    }
201
202                    out[idx] = t[0] | (t[1] << 4);
203                    out[idx + 1] = t[2] | (t[3] << 4);
204                    out[idx + 2] = t[4] | (t[5] << 4);
205                    out[idx + 3] = t[6] | (t[7] << 4);
206                    idx += 4;
207                }
208            }
209            5 => {
210                // MLKEM1024
211                for i in 0..N / 8 {
212                    // fill the temp array t
213                    for (j, item) in t.iter_mut().enumerate() {
214                        *item = (((((self[8 * i + j] as i32) << 5) + (q as i32 / 2)) / (q as i32))
215                            & 31) as u8;
216                    }
217
218                    out[idx] = t[0] | (t[1] << 5);
219                    out[idx + 1] = (t[1] >> 3) | (t[2] << 2) | (t[3] << 7);
220                    out[idx + 2] = (t[3] >> 1) | (t[4] << 4);
221                    out[idx + 3] = (t[4] >> 4) | (t[5] << 1) | (t[6] << 6);
222                    out[idx + 4] = (t[6] >> 2) | (t[7] << 3);
223                    idx += 5;
224                }
225            }
226            _ => unreachable!(),
227        };
228    }
229
230    /// This is an optimized version of
231    ///   Decompress_๐‘‘๐‘ฃ( ByteDecode_๐‘‘๐‘ฃ(๐‘2) )
232    /// which unpacks a single polynomial according to the packing coefficient dv
233    pub(crate) fn decompress_poly<const dv: i16>(compressed_v: &[u8]) -> Polynomial {
234        // make sure we have received a dv
235        debug_assert!(dv == 4 || dv == 5);
236
237        // make sure we were given the right size output buffer
238        // each of the N i16's will take dv bits
239        debug_assert_eq!(compressed_v.len(), N * (dv as usize) / 8);
240
241        let mut v = Polynomial::new();
242
243        let mut idx = 0usize;
244
245        // if self.m_engine.poly_compressed_bytes() == 128 {
246        match dv {
247            4 => {
248                // MLKEM512 and MLKEM768
249                for i in 0..N / 2 {
250                    v[2 * i] =
251                        (((((compressed_v[idx] & 15) as i16) as i32 * (q as i32)) + 8) >> 4) as i16;
252                    v[2 * i + 1] =
253                        (((((compressed_v[idx] >> 4) as i16) as i32 * (q as i32)) + 8) >> 4) as i16;
254                    idx += 1;
255                }
256            }
257            5 => {
258                // MLKEM1024
259                let mut t = [0u8; 8];
260                for i in 0..N / 8 {
261                    t[0] = compressed_v[idx];
262                    t[1] = (compressed_v[idx] >> 5) | (compressed_v[idx + 1] << 3);
263                    t[2] = compressed_v[idx + 1] >> 2;
264                    t[3] = (compressed_v[idx + 1] >> 7) | (compressed_v[idx + 2] << 1);
265                    t[4] = (compressed_v[idx + 2] >> 4) | (compressed_v[idx + 3] << 4);
266                    t[5] = compressed_v[idx + 3] >> 1;
267                    t[6] = (compressed_v[idx + 3] >> 6) | (compressed_v[idx + 4] << 2);
268                    t[7] = compressed_v[idx + 4] >> 3;
269                    idx += 5;
270                    for (j, item) in t.iter_mut().enumerate() {
271                        v[8 * i + j] = (((*item & 31) as i32 * (q as i32) + 16) >> 5) as i16;
272                    }
273                }
274            }
275            _ => unreachable!(),
276        }
277
278        v
279    }
280
281    // Not currently used. It is left here as a reference since it's useful for debugging if it's
282    // necessary to output values that are normalized to [0,q] to compare against intermediate results
283    // from other libraries.
284    // pub(crate) fn cond_sub_q(&mut self) {
285    //     for i in 0..N {
286    //         self[i] = cond_sub_q(self[i]);
287    //     }
288    // }
289
290    /// Algorithm 9 NTT(๐‘“)
291    /// Computes the NTT representation ๐‘“_hat of the given polynomial ๐‘“ โˆˆ ๐‘…๐‘ž.
292    /// Input: array ๐‘“ โˆˆ โ„ค256  โ–ท the coefficients of the input polynomial
293    /// Output: array ๐‘“_hat โˆˆ โ„ค256  โ–ท the coefficients of the NTT of the input polynomial
294    pub(crate) fn ntt(&mut self) {
295        let mut len = 128;
296        let mut k = 1;
297
298        while len >= 2 {
299            let mut start = 0;
300            while start < 256 {
301                let zeta = ZETAS[k];
302                k += 1;
303                let mut j = start;
304                while j < start + len {
305                    let t = mul_mont(zeta, self[j + len]);
306                    self[j + len] = self[j] - t;
307                    self[j] += t;
308                    j += 1;
309                }
310                start = j + len;
311            }
312            len >>= 1;
313        }
314    }
315
316    /// Algorithm 10 NTT (๐‘“_hat)
317    /// Computes the polynomial ๐‘“ โˆˆ ๐‘…๐‘ž that corresponds to the given NTT representation ๐‘“ โˆˆ ๐‘‡๐‘ž.
318    /// Input: array ๐‘“ โˆˆ โ„ค256  โ–ท the coefficients of input NTT representation
319    /// Output: array ๐‘“ โˆˆ โ„ค256  โ–ท the coefficients of the inverse NTT of the input
320    pub(crate) fn inv_ntt(&mut self) {
321        // FIPS 203 ALg 10 wants you to copy f_hat into f, and then act of f
322        // but we're going to do this in-place for memory-saving reasons.
323
324        let mut len = 2;
325        let mut k = 0;
326
327        while len <= 128 {
328            let mut start = 0;
329            while start < 256 {
330                let zeta = ZETAS_INV[k];
331                k += 1;
332                let mut j = start;
333                while j < start + len {
334                    let t = self[j];
335                    let u = self[j + len];
336
337                    self[j] = barrett_reduce(t + u);
338                    self[j + len] = mul_mont(zeta, t - u);
339                    j += 1;
340                }
341                start = j + len;
342            }
343            len <<= 1;
344        }
345
346        // 14: ๐‘“ โ† ๐‘“ โ‹… 3303 mod ๐‘ž
347        //   โ–ท multiply every entry by 3303 โ‰ก 128โˆ’1 mod ๐‘ž
348        for i in 0..N {
349            self[i] = mul_mont(self[i], ZETAS_INV[127]);
350        }
351    }
352}
353
354// Not currently used. It is left here as a reference since it's useful for debugging if it's
355// necessary to output values that are normalized to [0,q] to compare against intermediate results
356// from other libraries.
357// /// if a is in \[-q..0], then it shifts it up by q to be in \[0..q]
358// pub(crate) fn conditional_add_q(a: i16) -> i16 {
359//     a + ((a >> 15) & q)
360// }
361//
362// #[test]
363// /// These are the results it's giving; I'm not sure if these are "correct" or not.
364// fn test_conditional_add_q() {
365//     assert_eq!(conditional_add_q(-q -1), -1);
366//     assert_eq!(conditional_add_q(-q), 0);
367//     assert_eq!(conditional_add_q(-q -2), -2);
368//     assert_eq!(conditional_add_q(-q +1), 1);
369//     assert_eq!(conditional_add_q(-1), q -1);
370//     assert_eq!(conditional_add_q(0), 0);
371//     assert_eq!(conditional_add_q(1), 1);
372//     assert_eq!(conditional_add_q(q -1), q -1);
373//     assert_eq!(conditional_add_q(q), q);
374//     assert_eq!(conditional_add_q(q +1), q +1);
375// }