Skip to main content

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