use alloc::vec::Vec; use core::{ default::Default, fmt::Debug, ops::{Add, AddAssign, Div, Mul, MulAssign, Neg, Sub, SubAssign}, }; use num::{One, Zero}; use super::{field::FalconFelt, Inverse}; use crate::{ dsa::rpo_falcon512::{MODULUS, N}, Felt, }; #[derive(Debug, Clone, Default)] pub struct Polynomial { pub coefficients: Vec, } impl Polynomial where F: Clone, { pub fn new(coefficients: Vec) -> Self { Self { coefficients } } } impl< F: Mul + Sub + AddAssign + Zero + Div + Clone + Inverse, > Polynomial { pub fn hadamard_mul(&self, other: &Self) -> Self { Polynomial::new( self.coefficients .iter() .zip(other.coefficients.iter()) .map(|(a, b)| *a * *b) .collect(), ) } pub fn hadamard_div(&self, other: &Self) -> Self { let other_coefficients_inverse = F::batch_inverse_or_zero(&other.coefficients); Polynomial::new( self.coefficients .iter() .zip(other_coefficients_inverse.iter()) .map(|(a, b)| *a * *b) .collect(), ) } pub fn hadamard_inv(&self) -> Self { let coefficients_inverse = F::batch_inverse_or_zero(&self.coefficients); Polynomial::new(coefficients_inverse) } } impl Polynomial { pub fn degree(&self) -> Option { if self.coefficients.is_empty() { return None; } let mut max_index = self.coefficients.len() - 1; while self.coefficients[max_index] == F::zero() { if let Some(new_index) = max_index.checked_sub(1) { max_index = new_index; } else { return None; } } Some(max_index) } pub fn lc(&self) -> F { match self.degree() { Some(non_negative_degree) => self.coefficients[non_negative_degree].clone(), None => F::zero(), } } } /// The following implementations are specific to cyclotomic polynomial rings, /// i.e., F\[ X \] / , and are used extensively in Falcon. impl< F: One + Zero + Clone + Neg + MulAssign + AddAssign + Div + Sub + PartialEq, > Polynomial { /// Reduce the polynomial by X^n + 1. pub fn reduce_by_cyclotomic(&self, n: usize) -> Self { let mut coefficients = vec![F::zero(); n]; let mut sign = -F::one(); for (i, c) in self.coefficients.iter().cloned().enumerate() { if i % n == 0 { sign *= -F::one(); } coefficients[i % n] += sign.clone() * c; } Polynomial::new(coefficients) } /// Computes the field norm of the polynomial as an element of the cyclotomic ring /// F\[ X \] / relative to one of half the size, i.e., F\[ X \] / . /// /// Corresponds to formula 3.25 in the spec [1, p.30]. /// /// [1]: https://falcon-sign.info/falcon.pdf pub fn field_norm(&self) -> Self { let n = self.coefficients.len(); let mut f0_coefficients = vec![F::zero(); n / 2]; let mut f1_coefficients = vec![F::zero(); n / 2]; for i in 0..n / 2 { f0_coefficients[i] = self.coefficients[2 * i].clone(); f1_coefficients[i] = self.coefficients[2 * i + 1].clone(); } let f0 = Polynomial::new(f0_coefficients); let f1 = Polynomial::new(f1_coefficients); let f0_squared = (f0.clone() * f0).reduce_by_cyclotomic(n / 2); let f1_squared = (f1.clone() * f1).reduce_by_cyclotomic(n / 2); let x = Polynomial::new(vec![F::zero(), F::one()]); f0_squared - (x * f1_squared).reduce_by_cyclotomic(n / 2) } /// Lifts an element from a cyclotomic polynomial ring to one of double the size. pub fn lift_next_cyclotomic(&self) -> Self { let n = self.coefficients.len(); let mut coefficients = vec![F::zero(); n * 2]; for i in 0..n { coefficients[2 * i] = self.coefficients[i].clone(); } Self::new(coefficients) } /// Computes the galois adjoint of the polynomial in the cyclotomic ring F\[ X \] / < X^n + 1 > /// , which corresponds to f(x^2). pub fn galois_adjoint(&self) -> Self { Self::new( self.coefficients .iter() .enumerate() .map(|(i, c)| if i % 2 == 0 { c.clone() } else { c.clone().neg() }) .collect(), ) } } impl> Polynomial { pub(crate) fn l2_norm_squared(&self) -> f64 { self.coefficients .iter() .map(|i| Into::::into(i.clone())) .map(|i| i * i) .sum::() } } impl PartialEq for Polynomial where F: Zero + PartialEq + Clone + AddAssign, { fn eq(&self, other: &Self) -> bool { if self.is_zero() && other.is_zero() { true } else if self.is_zero() || other.is_zero() { false } else { let self_degree = self.degree().unwrap(); let other_degree = other.degree().unwrap(); self.coefficients[0..=self_degree] == other.coefficients[0..=other_degree] } } } impl Eq for Polynomial where F: Zero + PartialEq + Clone + AddAssign {} impl Add for &Polynomial where F: Add + AddAssign + Clone, { type Output = Polynomial; fn add(self, rhs: Self) -> Self::Output { let coefficients = if self.coefficients.len() >= rhs.coefficients.len() { let mut coefficients = self.coefficients.clone(); for (i, c) in rhs.coefficients.iter().enumerate() { coefficients[i] += c.clone(); } coefficients } else { let mut coefficients = rhs.coefficients.clone(); for (i, c) in self.coefficients.iter().enumerate() { coefficients[i] += c.clone(); } coefficients }; Self::Output { coefficients } } } impl Add for Polynomial where F: Add + AddAssign + Clone, { type Output = Polynomial; fn add(self, rhs: Self) -> Self::Output { let coefficients = if self.coefficients.len() >= rhs.coefficients.len() { let mut coefficients = self.coefficients.clone(); for (i, c) in rhs.coefficients.into_iter().enumerate() { coefficients[i] += c; } coefficients } else { let mut coefficients = rhs.coefficients.clone(); for (i, c) in self.coefficients.into_iter().enumerate() { coefficients[i] += c; } coefficients }; Self::Output { coefficients } } } impl AddAssign for Polynomial where F: Add + AddAssign + Clone, { fn add_assign(&mut self, rhs: Self) { if self.coefficients.len() >= rhs.coefficients.len() { for (i, c) in rhs.coefficients.into_iter().enumerate() { self.coefficients[i] += c; } } else { let mut coefficients = rhs.coefficients.clone(); for (i, c) in self.coefficients.iter().enumerate() { coefficients[i] += c.clone(); } self.coefficients = coefficients; } } } impl Sub for &Polynomial where F: Sub + Clone + Neg + Add + AddAssign, { type Output = Polynomial; fn sub(self, rhs: Self) -> Self::Output { self + &(-rhs) } } impl Sub for Polynomial where F: Sub + Clone + Neg + Add + AddAssign, { type Output = Polynomial; fn sub(self, rhs: Self) -> Self::Output { self + (-rhs) } } impl SubAssign for Polynomial where F: Add + Neg + AddAssign + Clone + Sub, { fn sub_assign(&mut self, rhs: Self) { self.coefficients = self.clone().sub(rhs).coefficients; } } impl + Clone> Neg for &Polynomial { type Output = Polynomial; fn neg(self) -> Self::Output { Self::Output { coefficients: self.coefficients.iter().cloned().map(|a| -a).collect(), } } } impl + Clone> Neg for Polynomial { type Output = Self; fn neg(self) -> Self::Output { Self::Output { coefficients: self.coefficients.iter().cloned().map(|a| -a).collect(), } } } impl Mul for &Polynomial where F: Add + AddAssign + Mul + Sub + Zero + PartialEq + Clone, { type Output = Polynomial; fn mul(self, other: Self) -> Self::Output { if self.is_zero() || other.is_zero() { return Polynomial::::zero(); } let mut coefficients = vec![F::zero(); self.coefficients.len() + other.coefficients.len() - 1]; for i in 0..self.coefficients.len() { for j in 0..other.coefficients.len() { coefficients[i + j] += self.coefficients[i].clone() * other.coefficients[j].clone(); } } Polynomial { coefficients } } } impl Mul for Polynomial where F: Add + AddAssign + Mul + Zero + PartialEq + Clone, { type Output = Self; fn mul(self, other: Self) -> Self::Output { if self.is_zero() || other.is_zero() { return Self::zero(); } let mut coefficients = vec![F::zero(); self.coefficients.len() + other.coefficients.len() - 1]; for i in 0..self.coefficients.len() { for j in 0..other.coefficients.len() { coefficients[i + j] += self.coefficients[i].clone() * other.coefficients[j].clone(); } } Self { coefficients } } } impl + Zero + Clone> Mul for &Polynomial { type Output = Polynomial; fn mul(self, other: F) -> Self::Output { Polynomial { coefficients: self.coefficients.iter().cloned().map(|i| i * other.clone()).collect(), } } } impl + Zero + Clone> Mul for Polynomial { type Output = Polynomial; fn mul(self, other: F) -> Self::Output { Polynomial { coefficients: self.coefficients.iter().cloned().map(|i| i * other.clone()).collect(), } } } impl + Sub + AddAssign + Zero + Div + Clone> Polynomial { /// Multiply two polynomials using Karatsuba's divide-and-conquer algorithm. pub fn karatsuba(&self, other: &Self) -> Self { Polynomial::new(vector_karatsuba(&self.coefficients, &other.coefficients)) } } impl One for Polynomial where F: Clone + One + PartialEq + Zero + AddAssign, { fn one() -> Self { Self { coefficients: vec![F::one()] } } } impl Zero for Polynomial where F: Zero + PartialEq + Clone + AddAssign, { fn zero() -> Self { Self { coefficients: vec![] } } fn is_zero(&self) -> bool { self.degree().is_none() } } impl Polynomial { pub fn shift(&self, shamt: usize) -> Self { Self { coefficients: [vec![F::zero(); shamt], self.coefficients.clone()].concat(), } } pub fn constant(f: F) -> Self { Self { coefficients: vec![f] } } pub fn map G>(&self, closure: C) -> Polynomial { Polynomial::::new(self.coefficients.iter().map(closure).collect()) } pub fn fold G + Clone>(&self, mut initial_value: G, closure: C) -> G { for c in self.coefficients.iter() { initial_value = (closure.clone())(initial_value, c); } initial_value } } impl Div> for Polynomial where F: Zero + One + PartialEq + AddAssign + Clone + Mul + MulAssign + Div + Neg + Sub, { type Output = Polynomial; fn div(self, denominator: Self) -> Self::Output { if denominator.is_zero() { panic!(); } if self.is_zero() { Self::zero(); } let mut remainder = self.clone(); let mut quotient = Polynomial::::zero(); while remainder.degree().unwrap() >= denominator.degree().unwrap() { let shift = remainder.degree().unwrap() - denominator.degree().unwrap(); let quotient_coefficient = remainder.lc() / denominator.lc(); let monomial = Self::constant(quotient_coefficient).shift(shift); quotient += monomial.clone(); remainder -= monomial * denominator.clone(); if remainder.is_zero() { break; } } quotient } } fn vector_karatsuba< F: Zero + AddAssign + Mul + Sub + Div + Clone, >( left: &[F], right: &[F], ) -> Vec { let n = left.len(); if n <= 8 { let mut product = vec![F::zero(); left.len() + right.len() - 1]; for (i, l) in left.iter().enumerate() { for (j, r) in right.iter().enumerate() { product[i + j] += l.clone() * r.clone(); } } return product; } let n_over_2 = n / 2; let mut product = vec![F::zero(); 2 * n - 1]; let left_lo = &left[0..n_over_2]; let right_lo = &right[0..n_over_2]; let left_hi = &left[n_over_2..]; let right_hi = &right[n_over_2..]; let left_sum: Vec = left_lo.iter().zip(left_hi).map(|(a, b)| a.clone() + b.clone()).collect(); let right_sum: Vec = right_lo.iter().zip(right_hi).map(|(a, b)| a.clone() + b.clone()).collect(); let prod_lo = vector_karatsuba(left_lo, right_lo); let prod_hi = vector_karatsuba(left_hi, right_hi); let prod_mid: Vec = vector_karatsuba(&left_sum, &right_sum) .iter() .zip(prod_lo.iter().zip(prod_hi.iter())) .map(|(s, (l, h))| s.clone() - (l.clone() + h.clone())) .collect(); for (i, l) in prod_lo.into_iter().enumerate() { product[i] = l; } for (i, m) in prod_mid.into_iter().enumerate() { product[i + n_over_2] += m; } for (i, h) in prod_hi.into_iter().enumerate() { product[i + n] += h } product } impl From> for Polynomial { fn from(item: Polynomial) -> Self { let res: Vec = item.coefficients.iter().map(|a| Felt::from(a.value() as u16)).collect(); Polynomial::new(res) } } impl From<&Polynomial> for Polynomial { fn from(item: &Polynomial) -> Self { let res: Vec = item.coefficients.iter().map(|a| Felt::from(a.value() as u16)).collect(); Polynomial::new(res) } } impl From> for Polynomial { fn from(item: Polynomial) -> Self { let res: Vec = item.coefficients.iter().map(|&a| FalconFelt::new(a)).collect(); Polynomial::new(res) } } impl From<&Polynomial> for Polynomial { fn from(item: &Polynomial) -> Self { let res: Vec = item.coefficients.iter().map(|&a| FalconFelt::new(a)).collect(); Polynomial::new(res) } } impl From> for Polynomial { fn from(item: Vec) -> Self { let res: Vec = item.iter().map(|&a| FalconFelt::new(a)).collect(); Polynomial::new(res) } } impl From<&Vec> for Polynomial { fn from(item: &Vec) -> Self { let res: Vec = item.iter().map(|&a| FalconFelt::new(a)).collect(); Polynomial::new(res) } } impl Polynomial { pub fn norm_squared(&self) -> u64 { self.coefficients .iter() .map(|&i| i.balanced_value() as i64) .map(|i| (i * i) as u64) .sum::() } // PUBLIC ACCESSORS // -------------------------------------------------------------------------------------------- /// Returns the coefficients of this polynomial as field elements. pub fn to_elements(&self) -> Vec { self.coefficients.iter().map(|&a| Felt::from(a.value() as u16)).collect() } // POLYNOMIAL OPERATIONS // -------------------------------------------------------------------------------------------- /// Multiplies two polynomials over Z_p\[x\] without reducing modulo p. Given that the degrees /// of the input polynomials are less than 512 and their coefficients are less than the modulus /// q equal to 12289, the resulting product polynomial is guaranteed to have coefficients less /// than the Miden prime. /// /// Note that this multiplication is not over Z_p\[x\]/(phi). pub fn mul_modulo_p(a: &Self, b: &Self) -> [u64; 1024] { let mut c = [0; 2 * N]; for i in 0..N { for j in 0..N { c[i + j] += a.coefficients[i].value() as u64 * b.coefficients[j].value() as u64; } } c } /// Reduces a polynomial, that is the product of two polynomials over Z_p\[x\], modulo /// the irreducible polynomial phi. This results in an element in Z_p\[x\]/(phi). pub fn reduce_negacyclic(a: &[u64; 1024]) -> Self { let mut c = [FalconFelt::zero(); N]; let modulus = MODULUS as u16; for i in 0..N { let ai = a[N + i] % modulus as u64; let neg_ai = (modulus - ai as u16) % modulus; let bi = (a[i] % modulus as u64) as u16; c[i] = FalconFelt::new(((neg_ai + bi) % modulus) as i16); } Self::new(c.to_vec()) } } // TESTS // ================================================================================================ #[cfg(test)] mod tests { use super::{FalconFelt, Polynomial, N}; #[test] fn test_negacyclic_reduction() { let coef1: [u8; N] = rand_utils::rand_array(); let coef2: [u8; N] = rand_utils::rand_array(); let poly1 = Polynomial::new(coef1.iter().map(|&a| FalconFelt::new(a as i16)).collect()); let poly2 = Polynomial::new(coef2.iter().map(|&a| FalconFelt::new(a as i16)).collect()); let prod = poly1.clone() * poly2.clone(); assert_eq!( prod.reduce_by_cyclotomic(N), Polynomial::reduce_negacyclic(&Polynomial::mul_modulo_p(&poly1, &poly2)) ); } }