1use 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#[derive(Debug, Clone, Default)]
21pub struct Polynomial<F> {
22 pub coefficients: Vec<F>,
24}
25
26impl<F> Polynomial<F>
27where
28 F: Clone,
29{
30 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 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 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 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 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 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
90impl<
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 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 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 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 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 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 pub fn shift(&self, shamt: usize) -> Self {
431 Self {
432 coefficients: [vec![F::zero(); shamt], self.coefficients.clone()].concat(),
433 }
434 }
435
436 pub fn constant(f: F) -> Self {
438 Self { coefficients: vec![f] }
439 }
440
441 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 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 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
507const 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 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 pub fn to_elements(&self) -> Vec<Felt> {
630 self.coefficients.iter().map(|&a| Felt::from_u16(a.value() as u16)).collect()
631 }
632
633 pub fn to_balanced_values(&self) -> Vec<i16> {
635 self.coefficients.iter().copied().map(FalconFelt::balanced_value).collect()
636 }
637
638 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 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 pub fn to_elements(&self) -> Vec<Felt> {
678 self.coefficients.clone()
679 }
680}
681
682impl Polynomial<i16> {
683 pub fn to_balanced_values(&self) -> Vec<i16> {
685 self.coefficients.iter().map(|c| FalconFelt::new(*c).balanced_value()).collect()
686 }
687}
688
689impl<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#[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 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 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 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 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}