1#[cfg(feature = "alloc")]
4extern crate alloc;
5#[cfg(feature = "alloc")]
6use alloc::{boxed::Box, vec};
7
8use super::ntt::montgomery_reduce;
9use super::params::{Modulus, NttModulus}; use crate::error::{Error, Result};
11use core::marker::PhantomData;
12use core::ops::{Add, Neg, Sub};
13use dcrypt_internal::zeroing::{Zeroize, ZeroizeOnDrop, Zeroizing};
14
15#[inline(always)]
17fn to_montgomery<M: NttModulus>(val: u32) -> u32 {
18 ((val as u64 * M::MONT_R as u64) % M::Q as u64) as u32
19}
20
21#[derive(Debug, Clone, PartialEq, Eq)]
23pub struct Polynomial<M: Modulus> {
24 #[cfg(feature = "alloc")]
26 pub coeffs: Box<[u32]>,
27 #[cfg(not(feature = "alloc"))]
29 pub coeffs: [u32; 256], _marker: PhantomData<M>,
31}
32
33impl<M: Modulus> Zeroize for Polynomial<M> {
35 fn zeroize(&mut self) {
36 #[cfg(feature = "alloc")]
38 {
39 self.coeffs.as_mut().zeroize();
40 }
41 #[cfg(not(feature = "alloc"))]
42 {
43 self.coeffs.zeroize();
44 }
45 }
46}
47
48impl<M: Modulus> Drop for Polynomial<M> {
49 fn drop(&mut self) {
50 self.zeroize();
51 }
52}
53
54impl<M: Modulus> ZeroizeOnDrop for Polynomial<M> {}
55
56impl<M: Modulus> Polynomial<M> {
57 pub fn zero() -> Self {
59 Self {
60 #[cfg(feature = "alloc")]
61 coeffs: vec![0; M::N].into_boxed_slice(),
62 #[cfg(not(feature = "alloc"))]
63 coeffs: [0; 256],
64 _marker: PhantomData,
65 }
66 }
67
68 pub fn from_coeffs(coeffs_slice: &[u32]) -> Result<Self> {
70 if coeffs_slice.len() != M::N {
71 return Err(Error::Parameter {
72 name: "coeffs_slice".into(),
73 reason: "Incorrect number of coefficients for polynomial degree N".into(),
74 });
75 }
76
77 #[cfg(feature = "alloc")]
78 let coeffs = Box::from(coeffs_slice);
79
80 #[cfg(not(feature = "alloc"))]
81 let mut coeffs = [0u32; 256];
82 #[cfg(not(feature = "alloc"))]
83 coeffs[..M::N].copy_from_slice(coeffs_slice);
84
85 Ok(Self {
86 coeffs,
87 _marker: PhantomData,
88 })
89 }
90
91 pub fn degree() -> usize {
93 M::N
94 }
95
96 pub fn modulus_q() -> u32 {
98 M::Q
99 }
100
101 pub fn as_coeffs_slice(&self) -> &[u32] {
103 &self.coeffs[..M::N]
104 }
105
106 pub fn as_mut_coeffs_slice(&mut self) -> &mut [u32] {
108 &mut self.coeffs[..M::N]
109 }
110
111 #[inline(always)]
113 fn reduce_coefficient(a: u32) -> u32 {
114 let q = M::Q;
116 let mask = ((a >= q) as u32).wrapping_neg();
117 a.wrapping_sub(q & mask)
118 }
119
120 #[inline(always)]
123 fn conditional_sub_q(a: i64) -> u32 {
124 let q = M::Q as i64;
125 a.rem_euclid(q) as u32
127 }
128
129 pub fn add(&self, other: &Self) -> Self {
131 let mut result = Self::zero();
132 for i in 0..M::N {
133 let sum = self.coeffs[i].wrapping_add(other.coeffs[i]);
134 result.coeffs[i] = Self::reduce_coefficient(sum);
135 }
136 result
137 }
138
139 pub fn sub(&self, other: &Self) -> Self {
141 let mut result = Self::zero();
142 for i in 0..M::N {
143 let diff = (self.coeffs[i] as i64) - (other.coeffs[i] as i64);
144 result.coeffs[i] = Self::conditional_sub_q(diff);
145 }
146 result
147 }
148
149 pub fn neg(&self) -> Self {
151 let mut result = Self::zero();
152 for i in 0..M::N {
153 let mask = ((self.coeffs[i] != 0) as u32).wrapping_neg();
155 result.coeffs[i] = (M::Q - self.coeffs[i]) & mask;
156 }
157 result
158 }
159
160 pub fn scalar_mul(&self, scalar: u32) -> Self {
162 let mut result = Self::zero();
163 for i in 0..M::N {
164 let prod = (self.coeffs[i] as u64) * (scalar as u64);
165 result.coeffs[i] = (prod % M::Q as u64) as u32;
166 }
167 result
168 }
169
170 pub fn schoolbook_mul(&self, other: &Self) -> Self {
173 let mut result = Self::zero();
174 let n = M::N;
175 let q = M::Q as u64;
176
177 let mut tmp = Zeroizing::new(vec![0u64; 2 * n].into_boxed_slice());
180
181 for (i, &ai_u32) in self.coeffs.iter().enumerate().take(n) {
184 let ai = ai_u32 as u64;
185 for (j, &bj_u32) in other.coeffs.iter().enumerate().take(n) {
186 let bj = bj_u32 as u64;
187 tmp[i + j] = tmp[i + j].wrapping_add(ai * bj);
188 }
189 }
190
191 for k in n..(2 * n) {
194 let upper_val = tmp[k] % q;
197 if upper_val > 0 {
198 tmp[k - n] = (tmp[k - n] + q - upper_val) % q;
200 }
201 }
202
203 #[allow(clippy::needless_range_loop)]
205 for i in 0..n {
207 result.coeffs[i] = (tmp[i] % q) as u32;
208 }
209
210 result
211 }
212
213 pub fn reduce_coeffs(&mut self) {
215 for i in 0..M::N {
216 self.coeffs[i] = Self::reduce_coefficient(self.coeffs[i]);
217 }
218 }
219}
220
221pub trait PolynomialNttExt<M: NttModulus> {
225 fn scalar_mul_montgomery(&self, scalar: u32) -> Polynomial<M>;
228}
229
230impl<M: NttModulus> PolynomialNttExt<M> for Polynomial<M> {
231 fn scalar_mul_montgomery(&self, scalar: u32) -> Polynomial<M> {
233 let mut result = Polynomial::<M>::zero();
234 let scalar_mont = to_montgomery::<M>(scalar);
236 for i in 0..M::N {
237 let prod = (self.coeffs[i] as u64) * (scalar_mont as u64);
239 result.coeffs[i] = montgomery_reduce::<M>(prod);
240 }
241 result
242 }
243}
244
245#[inline(always)]
247pub fn barrett_reduce<M: Modulus>(a: u32) -> u32 {
248 a % M::Q
251}
252
253impl<M: Modulus> Add for &Polynomial<M> {
256 type Output = Polynomial<M>;
257
258 fn add(self, other: Self) -> Self::Output {
259 self.add(other)
260 }
261}
262
263impl<M: Modulus> Sub for &Polynomial<M> {
264 type Output = Polynomial<M>;
265
266 fn sub(self, other: Self) -> Self::Output {
267 self.sub(other)
268 }
269}
270
271impl<M: Modulus> Neg for &Polynomial<M> {
272 type Output = Polynomial<M>;
273
274 fn neg(self) -> Self::Output {
275 self.neg()
276 }
277}
278
279impl<M: Modulus> Add for Polynomial<M> {
281 type Output = Self;
282
283 fn add(self, other: Self) -> Self::Output {
284 &self + &other
285 }
286}
287
288impl<M: Modulus> Sub for Polynomial<M> {
289 type Output = Self;
290
291 fn sub(self, other: Self) -> Self::Output {
292 &self - &other
293 }
294}
295
296impl<M: Modulus> Neg for Polynomial<M> {
297 type Output = Self;
298
299 fn neg(self) -> Self::Output {
300 -&self
301 }
302}
303
304#[cfg(test)]
305mod tests {
306 use super::*;
307
308 #[derive(Clone)]
310 struct TestModulus;
311 impl Modulus for TestModulus {
312 const Q: u32 = 3329;
313 const N: usize = 4; }
315
316 #[test]
317 fn test_polynomial_creation() {
318 let poly = Polynomial::<TestModulus>::zero();
319 assert_eq!(poly.as_coeffs_slice(), &[0, 0, 0, 0]);
320
321 let coeffs = vec![1, 2, 3, 4];
322 let poly = Polynomial::<TestModulus>::from_coeffs(&coeffs).unwrap();
323 assert_eq!(poly.as_coeffs_slice(), &[1, 2, 3, 4]);
324 }
325
326 #[test]
327 fn test_polynomial_addition() {
328 let a = Polynomial::<TestModulus>::from_coeffs(&[1, 2, 3, 4]).unwrap();
329 let b = Polynomial::<TestModulus>::from_coeffs(&[5, 6, 7, 8]).unwrap();
330 let c = a + b;
332 assert_eq!(c.as_coeffs_slice(), &[6, 8, 10, 12]);
333 }
334
335 #[test]
336 fn test_polynomial_subtraction() {
337 let a = Polynomial::<TestModulus>::from_coeffs(&[10, 20, 30, 40]).unwrap();
338 let b = Polynomial::<TestModulus>::from_coeffs(&[5, 6, 7, 8]).unwrap();
339 let c = a - b;
341 assert_eq!(c.as_coeffs_slice(), &[5, 14, 23, 32]);
342 }
343
344 #[test]
345 fn test_polynomial_negation() {
346 let a = Polynomial::<TestModulus>::from_coeffs(&[1, 2, 0, 4]).unwrap();
347 let neg_a = -a;
349 assert_eq!(neg_a.as_coeffs_slice(), &[3328, 3327, 0, 3325]);
350 }
351
352 #[test]
353 fn test_modular_reduction() {
354 let a = Polynomial::<TestModulus>::from_coeffs(&[3330, 3331, 3328, 0]).unwrap();
355 let mut b = a.clone();
356 b.reduce_coeffs();
357 assert_eq!(b.as_coeffs_slice(), &[1, 2, 3328, 0]);
358 }
359
360 #[test]
361 fn test_zeroization() {
362 let mut poly = Polynomial::<TestModulus>::from_coeffs(&[1, 2, 3, 4]).unwrap();
363 poly.zeroize();
364 assert_eq!(poly.as_coeffs_slice(), &[0, 0, 0, 0]);
365 assert_eq!(poly.coeffs.len(), 4); }
367
368 #[test]
369 fn test_schoolbook_mul_negacyclic() {
370 let mut x_cubed = Polynomial::<TestModulus>::zero();
373 x_cubed.coeffs[3] = 1; let mut x = Polynomial::<TestModulus>::zero();
376 x.coeffs[1] = 1; let result = x_cubed.schoolbook_mul(&x);
379 assert_eq!(result.coeffs[0], TestModulus::Q - 1);
381 assert_eq!(result.coeffs[1], 0);
382 assert_eq!(result.coeffs[2], 0);
383 assert_eq!(result.coeffs[3], 0);
384
385 let a = Polynomial::<TestModulus>::from_coeffs(&[1, 2, 3, 4]).unwrap();
387 let b = Polynomial::<TestModulus>::from_coeffs(&[5, 6, 7, 8]).unwrap();
388 let c = a.schoolbook_mul(&b);
389
390 let expected_0 = ((5i32 - 61i32).rem_euclid(TestModulus::Q as i32)) as u32;
414 let expected_1 = ((16i32 - 52i32).rem_euclid(TestModulus::Q as i32)) as u32;
415 let expected_2 = ((34i32 - 32i32).rem_euclid(TestModulus::Q as i32)) as u32;
416 let expected_3 = 60u32;
417
418 assert_eq!(c.coeffs[0], expected_0);
419 assert_eq!(c.coeffs[1], expected_1);
420 assert_eq!(c.coeffs[2], expected_2);
421 assert_eq!(c.coeffs[3], expected_3);
422 }
423
424 #[test]
425 fn test_ml_dsa_negacyclic() {
426 #[derive(Clone)]
428 struct MlDsaTestModulus;
429 impl Modulus for MlDsaTestModulus {
430 const Q: u32 = 8380417; const N: usize = 4; }
433
434 let mut x_to_n_minus_1 = Polynomial::<MlDsaTestModulus>::zero();
436 x_to_n_minus_1.coeffs[3] = 1; let mut x = Polynomial::<MlDsaTestModulus>::zero();
439 x.coeffs[1] = 1; let result = x_to_n_minus_1.schoolbook_mul(&x);
442 assert_eq!(result.coeffs[0], MlDsaTestModulus::Q - 1);
444 assert_eq!(result.coeffs[1], 0);
445 assert_eq!(result.coeffs[2], 0);
446 assert_eq!(result.coeffs[3], 0);
447
448 let mut sparse = Polynomial::<MlDsaTestModulus>::zero();
450 sparse.coeffs[0] = 1; sparse.coeffs[2] = MlDsaTestModulus::Q - 1; let dense = Polynomial::<MlDsaTestModulus>::from_coeffs(&[100, 200, 300, 400]).unwrap();
454 let result = sparse.schoolbook_mul(&dense);
455
456 assert_eq!(result.coeffs[0], 400);
464 assert_eq!(result.coeffs[1], 600);
465 assert_eq!(result.coeffs[2], 200);
466 assert_eq!(result.coeffs[3], 200);
467 }
468}