Skip to main content

miden_crypto/dsa/falcon512_common/math/
polynomial.rs

1//! Generic polynomial type and operations used in Falcon.
2
3use alloc::vec::Vec;
4use core::{
5    default::Default,
6    fmt::Debug,
7    ops::{Add, AddAssign, Div, Mul, MulAssign, Neg, Sub, SubAssign},
8};
9
10use num::{One, Zero};
11
12use super::{Inverse, MODULUS, N, field::FalconFelt};
13use crate::{Felt, utils::zeroize::Zeroize};
14
15/// Represents a polynomial with coefficients of type F.
16#[derive(Debug, Clone, Default)]
17pub struct Polynomial<F> {
18    /// Coefficients of the polynomial, ordered from lowest to highest degree.
19    pub coefficients: Vec<F>,
20}
21
22impl<F> Polynomial<F>
23where
24    F: Clone,
25{
26    /// Creates a new polynomial from the provided coefficients.
27    pub fn new(coefficients: Vec<F>) -> Self {
28        Self { coefficients }
29    }
30}
31
32impl<F: Mul<Output = F> + Sub<Output = F> + AddAssign + Zero + Div<Output = F> + Clone + Inverse>
33    Polynomial<F>
34{
35    /// Multiplies two polynomials coefficient-wise (Hadamard multiplication).
36    pub fn hadamard_mul(&self, other: &Self) -> Self {
37        Polynomial::new(
38            self.coefficients
39                .iter()
40                .zip(other.coefficients.iter())
41                .map(|(a, b)| *a * *b)
42                .collect(),
43        )
44    }
45    /// Divides two polynomials coefficient-wise (Hadamard division).
46    pub fn hadamard_div(&self, other: &Self) -> Self {
47        let other_coefficients_inverse = F::batch_inverse_or_zero(&other.coefficients);
48        Polynomial::new(
49            self.coefficients
50                .iter()
51                .zip(other_coefficients_inverse.iter())
52                .map(|(a, b)| *a * *b)
53                .collect(),
54        )
55    }
56
57    /// Computes the coefficient-wise inverse (Hadamard inverse).
58    pub fn hadamard_inv(&self) -> Self {
59        let coefficients_inverse = F::batch_inverse_or_zero(&self.coefficients);
60        Polynomial::new(coefficients_inverse)
61    }
62}
63
64impl<F: Zero + PartialEq + Clone> Polynomial<F> {
65    /// Returns the degree of the polynomial.
66    pub fn degree(&self) -> Option<usize> {
67        if self.coefficients.is_empty() {
68            return None;
69        }
70        let mut max_index = self.coefficients.len() - 1;
71        while self.coefficients[max_index] == F::zero() {
72            max_index = max_index.checked_sub(1)?;
73        }
74        Some(max_index)
75    }
76
77    /// Returns the leading coefficient of the polynomial.
78    pub fn lc(&self) -> F {
79        match self.degree() {
80            Some(non_negative_degree) => self.coefficients[non_negative_degree].clone(),
81            None => F::zero(),
82        }
83    }
84}
85
86/// The following implementations are specific to cyclotomic polynomial rings,
87/// i.e., F\[ X \] / <X^n + 1>, and are used extensively in Falcon.
88impl<
89    F: One
90        + Zero
91        + Clone
92        + Neg<Output = F>
93        + MulAssign
94        + AddAssign
95        + Div<Output = F>
96        + Sub<Output = F>
97        + PartialEq,
98> Polynomial<F>
99{
100    /// Reduce the polynomial by X^n + 1.
101    pub fn reduce_by_cyclotomic(&self, n: usize) -> Self {
102        let mut coefficients = vec![F::zero(); n];
103        let mut sign = -F::one();
104        for (i, c) in self.coefficients.iter().cloned().enumerate() {
105            if i.is_multiple_of(n) {
106                sign *= -F::one();
107            }
108            coefficients[i % n] += sign.clone() * c;
109        }
110        Polynomial::new(coefficients)
111    }
112
113    /// Computes the field norm of the polynomial as an element of the cyclotomic ring
114    ///  F\[ X \] / <X^n + 1 > relative to one of half the size, i.e., F\[ X \] / <X^(n/2) + 1> .
115    ///
116    /// Corresponds to formula 3.25 in the spec [1, p.30].
117    ///
118    /// [1]: <https://falcon-sign.info/falcon.pdf>
119    pub fn field_norm(&self) -> Self {
120        let n = self.coefficients.len();
121        let mut f0_coefficients = vec![F::zero(); n / 2];
122        let mut f1_coefficients = vec![F::zero(); n / 2];
123        for i in 0..n / 2 {
124            f0_coefficients[i] = self.coefficients[2 * i].clone();
125            f1_coefficients[i] = self.coefficients[2 * i + 1].clone();
126        }
127        let f0 = Polynomial::new(f0_coefficients);
128        let f1 = Polynomial::new(f1_coefficients);
129        let f0_squared = (f0.clone() * f0).reduce_by_cyclotomic(n / 2);
130        let f1_squared = (f1.clone() * f1).reduce_by_cyclotomic(n / 2);
131        let x = Polynomial::new(vec![F::zero(), F::one()]);
132        f0_squared - (x * f1_squared).reduce_by_cyclotomic(n / 2)
133    }
134
135    /// Lifts an element from a cyclotomic polynomial ring to one of double the size.
136    pub fn lift_next_cyclotomic(&self) -> Self {
137        let n = self.coefficients.len();
138        let mut coefficients = vec![F::zero(); n * 2];
139        for i in 0..n {
140            coefficients[2 * i] = self.coefficients[i].clone();
141        }
142        Self::new(coefficients)
143    }
144
145    /// Computes the Galois adjoint of the polynomial in the cyclotomic ring
146    /// F\[ X \] / < X^n + 1 >: the map f(X) -> f(-X), negating the odd-degree coefficients.
147    ///
148    /// Together with [`Self::lift_next_cyclotomic`], which computes f(X^2), this implements the
149    /// field-norm identity NTRU solving relies on: N(f)(X^2) = f(X) * f(-X).
150    pub fn galois_adjoint(&self) -> Self {
151        Self::new(
152            self.coefficients
153                .iter()
154                .enumerate()
155                .map(|(i, c)| {
156                    if i.is_multiple_of(2) {
157                        c.clone()
158                    } else {
159                        c.clone().neg()
160                    }
161                })
162                .collect(),
163        )
164    }
165}
166
167impl<F: Clone + Into<f64>> Polynomial<F> {
168    pub(crate) fn l2_norm_squared(&self) -> f64 {
169        self.coefficients
170            .iter()
171            .map(|i| Into::<f64>::into(i.clone()))
172            .map(|i| i * i)
173            .sum::<f64>()
174    }
175}
176
177impl<F> PartialEq for Polynomial<F>
178where
179    F: Zero + PartialEq + Clone + AddAssign,
180{
181    fn eq(&self, other: &Self) -> bool {
182        if self.is_zero() && other.is_zero() {
183            true
184        } else if self.is_zero() || other.is_zero() {
185            false
186        } else {
187            let self_degree = self.degree().unwrap();
188            let other_degree = other.degree().unwrap();
189            self.coefficients[0..=self_degree] == other.coefficients[0..=other_degree]
190        }
191    }
192}
193
194impl<F> Eq for Polynomial<F> where F: Zero + PartialEq + Clone + AddAssign {}
195
196impl<F> Add for &Polynomial<F>
197where
198    F: Add<Output = F> + AddAssign + Clone,
199{
200    type Output = Polynomial<F>;
201
202    fn add(self, rhs: Self) -> Self::Output {
203        let coefficients = if self.coefficients.len() >= rhs.coefficients.len() {
204            let mut coefficients = self.coefficients.clone();
205            for (i, c) in rhs.coefficients.iter().enumerate() {
206                coefficients[i] += c.clone();
207            }
208            coefficients
209        } else {
210            let mut coefficients = rhs.coefficients.clone();
211            for (i, c) in self.coefficients.iter().enumerate() {
212                coefficients[i] += c.clone();
213            }
214            coefficients
215        };
216        Self::Output { coefficients }
217    }
218}
219
220impl<F> Add for Polynomial<F>
221where
222    F: Add<Output = F> + AddAssign + Clone,
223{
224    type Output = Polynomial<F>;
225    fn add(self, rhs: Self) -> Self::Output {
226        let coefficients = if self.coefficients.len() >= rhs.coefficients.len() {
227            let mut coefficients = self.coefficients;
228            for (i, c) in rhs.coefficients.into_iter().enumerate() {
229                coefficients[i] += c;
230            }
231            coefficients
232        } else {
233            let mut coefficients = rhs.coefficients;
234            for (i, c) in self.coefficients.into_iter().enumerate() {
235                coefficients[i] += c;
236            }
237            coefficients
238        };
239        Self::Output { coefficients }
240    }
241}
242
243impl<F> AddAssign for Polynomial<F>
244where
245    F: Add<Output = F> + AddAssign + Clone,
246{
247    fn add_assign(&mut self, rhs: Self) {
248        if self.coefficients.len() >= rhs.coefficients.len() {
249            for (i, c) in rhs.coefficients.into_iter().enumerate() {
250                self.coefficients[i] += c;
251            }
252        } else {
253            let mut coefficients = rhs.coefficients;
254            for (i, c) in self.coefficients.iter().enumerate() {
255                coefficients[i] += c.clone();
256            }
257            self.coefficients = coefficients;
258        }
259    }
260}
261
262impl<F> Sub for &Polynomial<F>
263where
264    F: Sub<Output = F> + Clone + Neg<Output = F> + Add<Output = F> + AddAssign,
265{
266    type Output = Polynomial<F>;
267
268    fn sub(self, rhs: Self) -> Self::Output {
269        self + &(-rhs)
270    }
271}
272
273impl<F> Sub for Polynomial<F>
274where
275    F: Sub<Output = F> + Clone + Neg<Output = F> + Add<Output = F> + AddAssign,
276{
277    type Output = Polynomial<F>;
278
279    fn sub(self, rhs: Self) -> Self::Output {
280        self + (-rhs)
281    }
282}
283
284impl<F> SubAssign for Polynomial<F>
285where
286    F: Add<Output = F> + Neg<Output = F> + AddAssign + Clone + Sub<Output = F>,
287{
288    fn sub_assign(&mut self, rhs: Self) {
289        self.coefficients = self.clone().sub(rhs).coefficients;
290    }
291}
292
293impl<F: Neg<Output = F> + Clone> Neg for &Polynomial<F> {
294    type Output = Polynomial<F>;
295
296    fn neg(self) -> Self::Output {
297        Self::Output {
298            coefficients: self.coefficients.iter().cloned().map(|a| -a).collect(),
299        }
300    }
301}
302
303impl<F: Neg<Output = F> + Clone> Neg for Polynomial<F> {
304    type Output = Self;
305
306    fn neg(self) -> Self::Output {
307        Self::Output {
308            coefficients: self.coefficients.iter().cloned().map(|a| -a).collect(),
309        }
310    }
311}
312
313impl<F> Mul for &Polynomial<F>
314where
315    F: Add + AddAssign + Mul<Output = F> + Sub<Output = F> + Zero + PartialEq + Clone,
316{
317    type Output = Polynomial<F>;
318
319    fn mul(self, other: Self) -> Self::Output {
320        if self.is_zero() || other.is_zero() {
321            return Polynomial::<F>::zero();
322        }
323        let mut coefficients =
324            vec![F::zero(); self.coefficients.len() + other.coefficients.len() - 1];
325        for i in 0..self.coefficients.len() {
326            for j in 0..other.coefficients.len() {
327                coefficients[i + j] += self.coefficients[i].clone() * other.coefficients[j].clone();
328            }
329        }
330        Polynomial { coefficients }
331    }
332}
333
334impl<F> Mul for Polynomial<F>
335where
336    F: Add + AddAssign + Mul<Output = F> + Zero + PartialEq + Clone,
337{
338    type Output = Self;
339
340    fn mul(self, other: Self) -> Self::Output {
341        if self.is_zero() || other.is_zero() {
342            return Self::zero();
343        }
344        let mut coefficients =
345            vec![F::zero(); self.coefficients.len() + other.coefficients.len() - 1];
346        for i in 0..self.coefficients.len() {
347            for j in 0..other.coefficients.len() {
348                coefficients[i + j] += self.coefficients[i].clone() * other.coefficients[j].clone();
349            }
350        }
351        Self { coefficients }
352    }
353}
354
355impl<F: Add + Mul<Output = F> + Zero + Clone> Mul<F> for &Polynomial<F> {
356    type Output = Polynomial<F>;
357
358    fn mul(self, other: F) -> Self::Output {
359        Polynomial {
360            coefficients: self.coefficients.iter().cloned().map(|i| i * other.clone()).collect(),
361        }
362    }
363}
364
365impl<F: Add + Mul<Output = F> + Zero + Clone> Mul<F> for Polynomial<F> {
366    type Output = Polynomial<F>;
367
368    fn mul(self, other: F) -> Self::Output {
369        Polynomial {
370            coefficients: self.coefficients.iter().cloned().map(|i| i * other.clone()).collect(),
371        }
372    }
373}
374
375impl<F: Mul<Output = F> + Sub<Output = F> + AddAssign + Zero + Div<Output = F> + Clone>
376    Polynomial<F>
377{
378    /// Multiply two polynomials using Karatsuba's divide-and-conquer algorithm.
379    ///
380    /// Both coefficient vectors must have the same nonzero length `n`, and `n` must stay even
381    /// under repeated halving until it reaches the recursion's base case (`n <= 8`); any power of
382    /// two qualifies, and Falcon only multiplies power-of-two lengths.
383    ///
384    /// # Panics
385    ///
386    /// Panics if the coefficient vectors have different lengths, are empty, or have a length that
387    /// reaches an odd value above eight while being repeatedly halved.
388    pub fn karatsuba(&self, other: &Self) -> Self {
389        assert_eq!(
390            self.coefficients.len(),
391            other.coefficients.len(),
392            "karatsuba operands must have equal coefficient counts",
393        );
394        assert!(
395            karatsuba_length_is_supported(self.coefficients.len()),
396            "karatsuba operand length must be nonzero and stay even down to the base case (e.g. a power of two)",
397        );
398        Polynomial::new(vector_karatsuba(&self.coefficients, &other.coefficients))
399    }
400}
401
402impl<F> One for Polynomial<F>
403where
404    F: Clone + One + PartialEq + Zero + AddAssign,
405{
406    fn one() -> Self {
407        Self { coefficients: vec![F::one()] }
408    }
409}
410
411impl<F> Zero for Polynomial<F>
412where
413    F: Zero + PartialEq + Clone + AddAssign,
414{
415    fn zero() -> Self {
416        Self { coefficients: vec![] }
417    }
418
419    fn is_zero(&self) -> bool {
420        self.degree().is_none()
421    }
422}
423
424impl<F: Zero + Clone> Polynomial<F> {
425    /// Shifts the polynomial by the specified amount (adds leading zeros).
426    pub fn shift(&self, shamt: usize) -> Self {
427        Self {
428            coefficients: [vec![F::zero(); shamt], self.coefficients.clone()].concat(),
429        }
430    }
431
432    /// Creates a constant polynomial with a single coefficient.
433    pub fn constant(f: F) -> Self {
434        Self { coefficients: vec![f] }
435    }
436
437    /// Applies a function to each coefficient and returns a new polynomial.
438    pub fn map<G: Clone, C: FnMut(&F) -> G>(&self, closure: C) -> Polynomial<G> {
439        Polynomial::<G>::new(self.coefficients.iter().map(closure).collect())
440    }
441
442    /// Folds the coefficients using the provided function and initial value.
443    pub fn fold<G, C: FnMut(G, &F) -> G + Clone>(&self, mut initial_value: G, closure: C) -> G {
444        for c in self.coefficients.iter() {
445            initial_value = (closure.clone())(initial_value, c);
446        }
447        initial_value
448    }
449}
450
451impl<F> Div<Polynomial<F>> for Polynomial<F>
452where
453    F: Zero
454        + One
455        + PartialEq
456        + AddAssign
457        + Clone
458        + Mul<Output = F>
459        + MulAssign
460        + Div<Output = F>
461        + Neg<Output = F>
462        + Sub<Output = F>,
463{
464    type Output = Polynomial<F>;
465
466    /// Polynomial long division.
467    ///
468    /// Each step divides the remainder's leading coefficient by the denominator's; that quotient
469    /// must cancel the remainder's leading term. This is automatic for exact field
470    /// implementations such as `FalconFelt`; it can fail for non-field coefficient types (e.g.
471    /// integers, where a step may not divide evenly) and for IEEE floats (where
472    /// `(a / b) * b` can leave a nonzero residue).
473    ///
474    /// # Panics
475    /// Panics if `denominator` is zero or if a step's leading term does not cancel. With a
476    /// non-field `F`, the loop would otherwise repeat forever on an unchanged remainder.
477    fn div(self, denominator: Self) -> Self::Output {
478        assert!(!denominator.is_zero(), "cannot divide a polynomial by the zero polynomial");
479        if self.is_zero() {
480            return Self::zero();
481        }
482        let mut remainder = self;
483        let mut quotient = Polynomial::<F>::zero();
484        while remainder.degree().unwrap() >= denominator.degree().unwrap() {
485            let degree_before = remainder.degree().unwrap();
486            let shift = degree_before - denominator.degree().unwrap();
487            let quotient_coefficient = remainder.lc() / denominator.lc();
488            let monomial = Self::constant(quotient_coefficient).shift(shift);
489            quotient += monomial.clone();
490            remainder -= monomial * denominator.clone();
491            if remainder.is_zero() {
492                break;
493            }
494            assert!(
495                remainder.degree().unwrap() < degree_before,
496                "inexact polynomial division: the leading-coefficient quotient did not cancel the leading term"
497            );
498        }
499        quotient
500    }
501}
502
503/// True when `n` is nonzero and halves down to [`vector_karatsuba`]'s base case without passing
504/// through an odd intermediate length. An odd split overruns the output buffer (the cross term
505/// spills past `2n - 1` entries), and a zero length underflows the base case's `n + n - 1`
506/// product size.
507const fn karatsuba_length_is_supported(mut n: usize) -> bool {
508    if n == 0 {
509        return false;
510    }
511    while n > 8 {
512        if n % 2 == 1 {
513            return false;
514        }
515        n /= 2;
516    }
517    true
518}
519
520fn vector_karatsuba<
521    F: Zero + AddAssign + Mul<Output = F> + Sub<Output = F> + Div<Output = F> + Clone,
522>(
523    left: &[F],
524    right: &[F],
525) -> Vec<F> {
526    let n = left.len();
527    if n <= 8 {
528        let mut product = vec![F::zero(); left.len() + right.len() - 1];
529        for (i, l) in left.iter().enumerate() {
530            for (j, r) in right.iter().enumerate() {
531                product[i + j] += l.clone() * r.clone();
532            }
533        }
534        return product;
535    }
536    let n_over_2 = n / 2;
537    let mut product = vec![F::zero(); 2 * n - 1];
538    let left_lo = &left[0..n_over_2];
539    let right_lo = &right[0..n_over_2];
540    let left_hi = &left[n_over_2..];
541    let right_hi = &right[n_over_2..];
542    let left_sum: Vec<F> =
543        left_lo.iter().zip(left_hi).map(|(a, b)| a.clone() + b.clone()).collect();
544    let right_sum: Vec<F> =
545        right_lo.iter().zip(right_hi).map(|(a, b)| a.clone() + b.clone()).collect();
546
547    let prod_lo = vector_karatsuba(left_lo, right_lo);
548    let prod_hi = vector_karatsuba(left_hi, right_hi);
549    let prod_mid: Vec<F> = vector_karatsuba(&left_sum, &right_sum)
550        .iter()
551        .zip(prod_lo.iter().zip(prod_hi.iter()))
552        .map(|(s, (l, h))| s.clone() - (l.clone() + h.clone()))
553        .collect();
554
555    for (i, l) in prod_lo.into_iter().enumerate() {
556        product[i] = l;
557    }
558    for (i, m) in prod_mid.into_iter().enumerate() {
559        product[i + n_over_2] += m;
560    }
561    for (i, h) in prod_hi.into_iter().enumerate() {
562        product[i + n] += h
563    }
564    product
565}
566
567impl From<Polynomial<FalconFelt>> for Polynomial<Felt> {
568    fn from(item: Polynomial<FalconFelt>) -> Self {
569        let res: Vec<Felt> =
570            item.coefficients.iter().map(|a| Felt::from_u16(a.value() as u16)).collect();
571        Polynomial::new(res)
572    }
573}
574
575impl From<&Polynomial<FalconFelt>> for Polynomial<Felt> {
576    fn from(item: &Polynomial<FalconFelt>) -> Self {
577        let res: Vec<Felt> =
578            item.coefficients.iter().map(|a| Felt::from_u16(a.value() as u16)).collect();
579        Polynomial::new(res)
580    }
581}
582
583impl From<Polynomial<i16>> for Polynomial<FalconFelt> {
584    fn from(item: Polynomial<i16>) -> Self {
585        let res: Vec<FalconFelt> = item.coefficients.iter().map(|&a| FalconFelt::new(a)).collect();
586        Polynomial::new(res)
587    }
588}
589
590impl From<&Polynomial<i16>> for Polynomial<FalconFelt> {
591    fn from(item: &Polynomial<i16>) -> Self {
592        let res: Vec<FalconFelt> = item.coefficients.iter().map(|&a| FalconFelt::new(a)).collect();
593        Polynomial::new(res)
594    }
595}
596
597impl From<Vec<i16>> for Polynomial<FalconFelt> {
598    fn from(item: Vec<i16>) -> Self {
599        let res: Vec<FalconFelt> = item.iter().map(|&a| FalconFelt::new(a)).collect();
600        Polynomial::new(res)
601    }
602}
603
604impl From<&Vec<i16>> for Polynomial<FalconFelt> {
605    fn from(item: &Vec<i16>) -> Self {
606        let res: Vec<FalconFelt> = item.iter().map(|&a| FalconFelt::new(a)).collect();
607        Polynomial::new(res)
608    }
609}
610
611impl Polynomial<FalconFelt> {
612    /// Computes the squared L2 norm of the polynomial.
613    pub fn norm_squared(&self) -> u64 {
614        self.coefficients
615            .iter()
616            .map(|&i| i.balanced_value() as i64)
617            .map(|i| (i * i) as u64)
618            .sum::<u64>()
619    }
620
621    // PUBLIC ACCESSORS
622    // --------------------------------------------------------------------------------------------
623
624    /// Returns the coefficients of this polynomial as field elements.
625    pub fn to_elements(&self) -> Vec<Felt> {
626        self.coefficients.iter().map(|&a| Felt::from_u16(a.value() as u16)).collect()
627    }
628
629    /// Returns the coefficients of this polynomial as balanced signed values.
630    pub fn to_balanced_values(&self) -> Vec<i16> {
631        self.coefficients.iter().copied().map(FalconFelt::balanced_value).collect()
632    }
633
634    // POLYNOMIAL OPERATIONS
635    // --------------------------------------------------------------------------------------------
636
637    /// Multiplies two polynomials over Z_p\[x\] without reducing modulo p. Given that the degrees
638    /// of the input polynomials are less than 512 and their coefficients are less than the modulus
639    /// q equal to 12289, the resulting product polynomial is guaranteed to have coefficients less
640    /// than the Miden prime.
641    ///
642    /// Note that this multiplication is not over Z_p\[x\]/(phi).
643    pub fn mul_modulo_p(a: &Self, b: &Self) -> [u64; 1024] {
644        let mut c = [0; 2 * N];
645        for i in 0..N {
646            for j in 0..N {
647                c[i + j] += a.coefficients[i].value() as u64 * b.coefficients[j].value() as u64;
648            }
649        }
650
651        c
652    }
653
654    /// Reduces a polynomial, that is the product of two polynomials over Z_p\[x\], modulo
655    /// the irreducible polynomial phi. This results in an element in Z_p\[x\]/(phi).
656    pub fn reduce_negacyclic(a: &[u64; 1024]) -> Self {
657        let mut c = [FalconFelt::zero(); N];
658        let modulus = MODULUS as u16;
659        for i in 0..N {
660            let ai = a[N + i] % modulus as u64;
661            let neg_ai = (modulus - ai as u16) % modulus;
662
663            let bi = (a[i] % modulus as u64) as u16;
664            c[i] = FalconFelt::new(((neg_ai + bi) % modulus) as i16);
665        }
666
667        Self::new(c.to_vec())
668    }
669}
670
671impl Polynomial<Felt> {
672    /// Returns the coefficients of this polynomial as Miden field elements.
673    pub fn to_elements(&self) -> Vec<Felt> {
674        self.coefficients.clone()
675    }
676}
677
678impl Polynomial<i16> {
679    /// Returns the balanced values of the coefficients of this polynomial.
680    pub fn to_balanced_values(&self) -> Vec<i16> {
681        self.coefficients.iter().map(|c| FalconFelt::new(*c).balanced_value()).collect()
682    }
683}
684
685// ZEROIZE IMPLEMENTATIONS
686// ================================================================================================
687
688impl<F: Zeroize> Zeroize for Polynomial<F> {
689    fn zeroize(&mut self) {
690        self.coefficients.zeroize();
691    }
692}
693
694// TESTS
695// ================================================================================================
696
697#[cfg(test)]
698mod tests {
699    use proptest::{collection::vec, prelude::*};
700
701    use super::{FalconFelt, N, Polynomial};
702    use crate::rand::test_utils::prng_array;
703
704    #[test]
705    fn div_zero_by_nonzero_returns_zero() {
706        use num::Zero;
707        let zero = Polynomial::<i64>::zero();
708        let nonzero = Polynomial::new(vec![1, 2, 3]);
709        let result = zero / nonzero;
710        assert!(result.is_zero());
711    }
712
713    #[test]
714    fn div_exact_integer_division_returns_quotient() {
715        // (x + 1)(x + 2) = x^2 + 3x + 2 divides evenly at every step.
716        let numerator = Polynomial::new(vec![2i64, 3, 1]);
717        let denominator = Polynomial::new(vec![1i64, 1]);
718        assert_eq!((numerator / denominator).coefficients, vec![2, 1]);
719    }
720
721    #[test]
722    #[should_panic(expected = "inexact polynomial division")]
723    fn div_inexact_integer_division_panics_instead_of_looping() {
724        // 3 / 2 truncates to 1, leaving remainder 1 that no further step can reduce; without the
725        // progress check this repeated forever.
726        let numerator = Polynomial::new(vec![3i64]);
727        let denominator = Polynomial::new(vec![2i64]);
728        let _ = numerator / denominator;
729    }
730
731    #[test]
732    #[should_panic(expected = "zero polynomial")]
733    fn div_by_zero_panics_with_message() {
734        use num::Zero;
735        let numerator = Polynomial::new(vec![1i64]);
736        let _ = numerator / Polynomial::<i64>::zero();
737    }
738
739    #[test]
740    fn karatsuba_agrees_with_mul_for_representative_admissible_shapes() {
741        // Powers of two (the live Falcon shapes) plus the accepted non-power-of-two lengths,
742        // which halve evenly to the base case and would otherwise have no output coverage.
743        for n in [2i64, 4, 8, 16, 32, 10, 20, 24] {
744            let f = Polynomial::new((0..n).map(|i| i * i - 7 * i + 3).collect());
745            let g = Polynomial::new((0..n).map(|i| 5 * i - 11).collect());
746            let schoolbook = f.clone() * g.clone();
747            assert_eq!(f.karatsuba(&g), schoolbook, "karatsuba disagrees at n = {n}");
748        }
749    }
750
751    proptest! {
752        #[test]
753        fn karatsuba_agrees_with_mul_for_admissible_operands(
754            (left, right) in (1usize..=8, 0u32..=6).prop_flat_map(|(base, doublings)| {
755                let len = base << doublings;
756                (vec(-1_000i64..=1_000, len), vec(-1_000i64..=1_000, len))
757            }),
758        ) {
759            let left = Polynomial::new(left);
760            let right = Polynomial::new(right);
761            prop_assert_eq!(left.karatsuba(&right), left * right);
762        }
763    }
764
765    #[test]
766    #[should_panic(expected = "karatsuba operand length")]
767    fn karatsuba_rejects_empty_operands() {
768        let empty = Polynomial::<i64>::new(vec![]);
769        let _ = empty.karatsuba(&empty.clone());
770    }
771
772    #[test]
773    fn karatsuba_length_support_matches_the_recursion_shape() {
774        use super::karatsuba_length_is_supported;
775        for supported in [1usize, 2, 5, 8, 10, 20, 24, 64, 512] {
776            assert!(karatsuba_length_is_supported(supported), "{supported} must be supported");
777        }
778        for unsupported in [0usize, 9, 11, 17, 18, 34, 513] {
779            assert!(
780                !karatsuba_length_is_supported(unsupported),
781                "{unsupported} must be unsupported"
782            );
783        }
784    }
785
786    #[test]
787    #[should_panic(expected = "karatsuba operand length")]
788    fn karatsuba_rejects_odd_lengths_above_the_base_case() {
789        let f = Polynomial::new(vec![1i64; 9]);
790        let _ = f.karatsuba(&f.clone());
791    }
792
793    #[test]
794    #[should_panic(expected = "equal coefficient counts")]
795    fn karatsuba_rejects_unequal_lengths() {
796        let f = Polynomial::new(vec![1i64; 8]);
797        let g = Polynomial::new(vec![1i64; 4]);
798        let _ = f.karatsuba(&g);
799    }
800
801    #[test]
802    fn galois_adjoint_negates_odd_degree_coefficients() {
803        let f = Polynomial::new(vec![1i64, 2, 3, 4]);
804        assert_eq!(f.galois_adjoint().coefficients, vec![1, -2, 3, -4]);
805    }
806
807    #[test]
808    fn galois_adjoint_product_with_self_is_even() {
809        // f(X) * f(-X) is an even function, so before any cyclotomic reduction every odd-degree
810        // coefficient of the product vanishes -- the property the NTRU field norm relies on.
811        let f = Polynomial::new(vec![3i64, -1, 4, 1, -5, 9, -2, 6]);
812        let product = f.clone() * f.galois_adjoint();
813        for (degree, c) in product.coefficients.iter().enumerate() {
814            if degree % 2 == 1 {
815                assert_eq!(*c, 0, "odd-degree coefficient {degree} must vanish");
816            }
817        }
818    }
819
820    #[test]
821    fn test_negacyclic_reduction() {
822        let coef1: [u8; N] = prng_array([0u8; 32]);
823        let coef2: [u8; N] = prng_array([1u8; 32]);
824
825        let poly1 = Polynomial::new(coef1.iter().map(|&a| FalconFelt::new(a as i16)).collect());
826        let poly2 = Polynomial::new(coef2.iter().map(|&a| FalconFelt::new(a as i16)).collect());
827        let prod = poly1.clone() * poly2.clone();
828
829        assert_eq!(
830            prod.reduce_by_cyclotomic(N),
831            Polynomial::reduce_negacyclic(&Polynomial::mul_modulo_p(&poly1, &poly2))
832        );
833    }
834}