/ algorithms / src / fft / polynomial / mod.rs
mod.rs
  1  // Copyright (c) 2025-2026 ACDC Network
  2  // This file is part of the alphavm library.
  3  //
  4  // Alpha Chain | Delta Chain Protocol
  5  // International Monetary Graphite.
  6  //
  7  // Derived from Aleo (https://aleo.org) and ProvableHQ (https://provable.com).
  8  // They built world-class ZK infrastructure. We installed the EASY button.
  9  // Their cryptography: elegant. Our modifications: bureaucracy-compatible.
 10  // Original brilliance: theirs. Robert's Rules: ours. Bugs: definitely ours.
 11  //
 12  // Original Aleo/ProvableHQ code subject to Apache 2.0 https://www.apache.org/licenses/LICENSE-2.0
 13  // All modifications and new work: CC0 1.0 Universal Public Domain Dedication.
 14  // No rights reserved. No permission required. No warranty. No refunds.
 15  //
 16  // https://creativecommons.org/publicdomain/zero/1.0/
 17  // SPDX-License-Identifier: CC0-1.0
 18  
 19  //! Work with sparse and dense polynomials.
 20  
 21  use crate::fft::{EvaluationDomain, Evaluations};
 22  use alphavm_fields::{Field, PrimeField};
 23  use alphavm_utilities::{cfg_iter_mut, serialize::*, SerializationError};
 24  use Polynomial::*;
 25  
 26  use anyhow::{ensure, Result};
 27  use std::{borrow::Cow, convert::TryInto};
 28  
 29  #[cfg(not(feature = "serial"))]
 30  use rayon::prelude::*;
 31  
 32  mod dense;
 33  pub use dense::DensePolynomial;
 34  
 35  mod sparse;
 36  pub use sparse::SparsePolynomial;
 37  
 38  mod multiplier;
 39  pub use multiplier::*;
 40  
 41  /// Represents either a sparse polynomial or a dense one.
 42  #[derive(Clone, Debug, PartialEq, Eq)]
 43  pub enum Polynomial<'a, F: Field> {
 44      /// Represents the case where `self` is a sparse polynomial
 45      Sparse(Cow<'a, SparsePolynomial<F>>),
 46      /// Represents the case where `self` is a dense polynomial
 47      Dense(Cow<'a, DensePolynomial<F>>),
 48  }
 49  
 50  impl<F: Field> CanonicalSerialize for Polynomial<'_, F> {
 51      fn serialize_with_mode<W: Write>(&self, writer: W, compress: Compress) -> Result<(), SerializationError> {
 52          match self {
 53              Sparse(p) => {
 54                  let p: DensePolynomial<F> = p.clone().into_owned().into();
 55                  CanonicalSerialize::serialize_with_mode(&p.coeffs, writer, compress)
 56              }
 57              Dense(p) => CanonicalSerialize::serialize_with_mode(&p.coeffs, writer, compress),
 58          }
 59      }
 60  
 61      fn serialized_size(&self, mode: Compress) -> usize {
 62          match self {
 63              Sparse(p) => {
 64                  let p: DensePolynomial<F> = p.clone().into_owned().into();
 65                  p.serialized_size(mode)
 66              }
 67              Dense(p) => p.serialized_size(mode),
 68          }
 69      }
 70  }
 71  
 72  impl<F: Field> Valid for Polynomial<'_, F> {
 73      fn check(&self) -> Result<(), SerializationError> {
 74          // Check that the polynomial contains a trailing zero coefficient.
 75          let has_trailing_zero = match self {
 76              Sparse(p) => p.coeffs().last().map(|(_, c)| c.is_zero()),
 77              Dense(p) => p.coeffs.last().map(|c| c.is_zero()),
 78          };
 79          // Fail if the trailing coefficient is zero.
 80          match has_trailing_zero {
 81              Some(true) => Err(SerializationError::InvalidData),
 82              Some(false) | None => Ok(()),
 83          }
 84      }
 85  }
 86  
 87  impl<F: Field> CanonicalDeserialize for Polynomial<'_, F> {
 88      fn deserialize_with_mode<R: Read>(
 89          reader: R,
 90          compress: Compress,
 91          validate: Validate,
 92      ) -> Result<Self, SerializationError> {
 93          DensePolynomial::<F>::deserialize_with_mode(reader, compress, validate).map(|e| Self::Dense(Cow::Owned(e)))
 94      }
 95  }
 96  
 97  impl<F: Field> From<DensePolynomial<F>> for Polynomial<'_, F> {
 98      fn from(other: DensePolynomial<F>) -> Self {
 99          Dense(Cow::Owned(other))
100      }
101  }
102  
103  impl<'a, F: Field> From<&'a DensePolynomial<F>> for Polynomial<'a, F> {
104      fn from(other: &'a DensePolynomial<F>) -> Self {
105          Dense(Cow::Borrowed(other))
106      }
107  }
108  
109  impl<F: Field> From<SparsePolynomial<F>> for Polynomial<'_, F> {
110      fn from(other: SparsePolynomial<F>) -> Self {
111          Sparse(Cow::Owned(other))
112      }
113  }
114  
115  impl<'a, F: Field> From<&'a SparsePolynomial<F>> for Polynomial<'a, F> {
116      fn from(other: &'a SparsePolynomial<F>) -> Self {
117          Sparse(Cow::Borrowed(other))
118      }
119  }
120  
121  #[allow(clippy::from_over_into)]
122  impl<F: Field> Into<DensePolynomial<F>> for Polynomial<'_, F> {
123      fn into(self) -> DensePolynomial<F> {
124          match self {
125              Dense(p) => p.into_owned(),
126              Sparse(p) => p.into_owned().into(),
127          }
128      }
129  }
130  
131  impl<F: Field> TryInto<SparsePolynomial<F>> for Polynomial<'_, F> {
132      type Error = ();
133  
134      fn try_into(self) -> Result<SparsePolynomial<F>, ()> {
135          match self {
136              Sparse(p) => Ok(p.into_owned()),
137              _ => Err(()),
138          }
139      }
140  }
141  
142  impl<'a, F: Field> Polynomial<'a, F> {
143      /// The zero polynomial.
144      pub fn zero() -> Self {
145          Sparse(Cow::Owned(SparsePolynomial::zero()))
146      }
147  
148      /// Checks if the given polynomial is zero.
149      pub fn is_zero(&self) -> bool {
150          match self {
151              Sparse(s) => s.is_zero(),
152              Dense(d) => d.is_zero(),
153          }
154      }
155  
156      /// Return the degree of `self.
157      pub fn degree(&self) -> usize {
158          match self {
159              Sparse(s) => s.degree(),
160              Dense(d) => d.degree(),
161          }
162      }
163  
164      #[inline]
165      pub fn leading_coefficient(&self) -> Option<&F> {
166          match self {
167              Sparse(p) => p.coeffs().last().map(|(_, c)| c),
168              Dense(p) => p.last(),
169          }
170      }
171  
172      #[inline]
173      pub fn as_dense(&self) -> Option<&DensePolynomial<F>> {
174          match self {
175              Dense(p) => Some(p.as_ref()),
176              _ => None,
177          }
178      }
179  
180      #[inline]
181      pub fn to_dense(&self) -> Cow<'_, DensePolynomial<F>> {
182          match self {
183              Dense(p) => Cow::Borrowed(p.as_ref()),
184              Sparse(p) => Cow::Owned(p.clone().into_owned().into()),
185          }
186      }
187  
188      #[inline]
189      pub fn as_dense_mut(&mut self) -> Option<&mut DensePolynomial<F>> {
190          match self {
191              Dense(p) => Some(p.to_mut()),
192              _ => None,
193          }
194      }
195  
196      #[inline]
197      pub fn as_sparse(&self) -> Option<&SparsePolynomial<F>> {
198          match self {
199              Sparse(p) => Some(p.as_ref()),
200              _ => None,
201          }
202      }
203  
204      #[inline]
205      pub fn into_dense(&self) -> DensePolynomial<F> {
206          self.clone().into()
207      }
208  
209      #[inline]
210      pub fn evaluate(&self, point: F) -> F {
211          match self {
212              Sparse(p) => p.evaluate(point),
213              Dense(p) => p.evaluate(point),
214          }
215      }
216  
217      pub fn coeffs(&'a self) -> Box<dyn Iterator<Item = (usize, &'a F)> + 'a> {
218          match self {
219              Sparse(p) => Box::new(p.coeffs().map(|(c, f)| (*c, f))),
220              Dense(p) => Box::new(p.coeffs.iter().enumerate()),
221          }
222      }
223  
224      /// Divide self by another (sparse or dense) polynomial, and returns the
225      /// quotient and remainder.
226      pub fn divide_with_q_and_r(&self, divisor: &Self) -> Result<(DensePolynomial<F>, DensePolynomial<F>)> {
227          ensure!(!divisor.is_zero(), "Dividing by zero polynomial is undefined");
228  
229          if self.is_zero() {
230              Ok((DensePolynomial::zero(), DensePolynomial::zero()))
231          } else if self.degree() < divisor.degree() {
232              Ok((DensePolynomial::zero(), self.clone().into()))
233          } else {
234              // Now we know that self.degree() >= divisor.degree();
235              let mut quotient = vec![F::zero(); self.degree() - divisor.degree() + 1];
236              let mut remainder: DensePolynomial<F> = self.clone().into();
237              // Can unwrap here because we know self is not zero.
238              let divisor_leading_inv = divisor.leading_coefficient().unwrap().inverse().unwrap();
239              while !remainder.is_zero() && remainder.degree() >= divisor.degree() {
240                  let cur_q_coeff = *remainder.coeffs.last().unwrap() * divisor_leading_inv;
241                  let cur_q_degree = remainder.degree() - divisor.degree();
242                  quotient[cur_q_degree] = cur_q_coeff;
243  
244                  if let Sparse(p) = divisor {
245                      for (i, div_coeff) in p.coeffs() {
246                          remainder[cur_q_degree + i] -= &(cur_q_coeff * div_coeff);
247                      }
248                  } else if let Dense(p) = divisor {
249                      for (i, div_coeff) in p.iter().enumerate() {
250                          remainder[cur_q_degree + i] -= &(cur_q_coeff * div_coeff);
251                      }
252                  }
253  
254                  while let Some(true) = remainder.coeffs.last().map(|c| c.is_zero()) {
255                      remainder.coeffs.pop();
256                  }
257              }
258              Ok((DensePolynomial::from_coefficients_vec(quotient), remainder))
259          }
260      }
261  }
262  
263  impl<F: PrimeField> Polynomial<'_, F> {
264      /// Construct `Evaluations` by evaluating a polynomial over the domain
265      /// `domain`.
266      pub fn evaluate_over_domain(poly: impl Into<Self>, domain: EvaluationDomain<F>) -> Evaluations<F> {
267          let poly = poly.into();
268          poly.eval_over_domain_helper(domain)
269      }
270  
271      fn eval_over_domain_helper(self, domain: EvaluationDomain<F>) -> Evaluations<F> {
272          match self {
273              Sparse(Cow::Borrowed(s)) => {
274                  let evals = domain.elements().map(|elem| s.evaluate(elem)).collect();
275                  Evaluations::from_vec_and_domain(evals, domain)
276              }
277              Sparse(Cow::Owned(s)) => {
278                  let evals = domain.elements().map(|elem| s.evaluate(elem)).collect();
279                  Evaluations::from_vec_and_domain(evals, domain)
280              }
281              Dense(Cow::Borrowed(d)) => {
282                  if d.degree() >= domain.size() {
283                      d.coeffs
284                          .chunks(domain.size())
285                          .map(|d| Evaluations::from_vec_and_domain(domain.fft(d), domain))
286                          .fold(Evaluations::from_vec_and_domain(vec![F::zero(); domain.size()], domain), |mut acc, e| {
287                              cfg_iter_mut!(acc.evaluations).zip(e.evaluations).for_each(|(a, e)| *a += e);
288                              acc
289                          })
290                  } else {
291                      Evaluations::from_vec_and_domain(domain.fft(&d.coeffs), domain)
292                  }
293              }
294              Dense(Cow::Owned(mut d)) => {
295                  if d.degree() >= domain.size() {
296                      d.coeffs
297                          .chunks(domain.size())
298                          .map(|d| Evaluations::from_vec_and_domain(domain.fft(d), domain))
299                          .fold(Evaluations::from_vec_and_domain(vec![F::zero(); domain.size()], domain), |mut acc, e| {
300                              cfg_iter_mut!(acc.evaluations).zip(e.evaluations).for_each(|(a, e)| *a += e);
301                              acc
302                          })
303                  } else {
304                      domain.fft_in_place(&mut d.coeffs);
305                      Evaluations::from_vec_and_domain(d.coeffs, domain)
306                  }
307              }
308          }
309      }
310  }