203 lines
7.0 KiB
Rust
203 lines
7.0 KiB
Rust
use crate::arithmetic::{best_fft, parallelize, Field, Group};
|
|
|
|
/// This structure contains precomputed constants and other details needed for
|
|
/// performing operations on an evaluation domain of size $2^k$ in the context
|
|
/// of PLONK.
|
|
#[derive(Debug)]
|
|
pub struct EvaluationDomain<G: Group> {
|
|
n: u64,
|
|
k: u32,
|
|
extended_k: u32,
|
|
omega_inv: G::Scalar,
|
|
extended_omega: G::Scalar,
|
|
extended_omega_inv: G::Scalar,
|
|
g_coset: G::Scalar,
|
|
g_coset_inv: G::Scalar,
|
|
quotient_poly_degree: u64,
|
|
ifft_divisor: G::Scalar,
|
|
extended_ifft_divisor: G::Scalar,
|
|
t_evaluations: Vec<G::Scalar>,
|
|
}
|
|
|
|
impl<G: Group> EvaluationDomain<G> {
|
|
/// This constructs a new evaluation domain object (containing precomputed
|
|
/// constants) for operating on an evaluation domain of size $2^k$ and for
|
|
/// some operations over an extended domain of size $2^{k + j}$ where $j$ is
|
|
/// sufficiently large to describe the quotient polynomial depending on the
|
|
/// maximum degree of all PLONK gates.
|
|
pub fn new(gate_degree: u32, k: u32) -> Self {
|
|
// quotient_poly_degree * params.n - 1 is the degree of the quotient polynomial
|
|
let quotient_poly_degree = (gate_degree - 1) as u64;
|
|
|
|
let n = 1u64 << k;
|
|
|
|
// We need to work within an extended domain, not params.k but params.k + j
|
|
// such that 2^(params.k + j) is sufficiently large to describe the quotient
|
|
// polynomial.
|
|
let mut extended_k = k;
|
|
while (1 << extended_k) < (n * quotient_poly_degree) {
|
|
extended_k += 1;
|
|
}
|
|
|
|
let mut extended_omega = G::Scalar::ROOT_OF_UNITY;
|
|
for _ in extended_k..G::Scalar::S {
|
|
extended_omega = extended_omega.square();
|
|
}
|
|
let extended_omega = extended_omega; // 2^{j+k}'th root of unity
|
|
let extended_omega_inv = extended_omega.invert().unwrap();
|
|
|
|
let mut omega = extended_omega;
|
|
for _ in k..extended_k {
|
|
omega = omega.square();
|
|
}
|
|
let omega = omega; // 2^{k}'th root of unity
|
|
let omega_inv = omega.invert().unwrap();
|
|
|
|
// We use zeta here because we know it generates a coset, and it's available
|
|
// already.
|
|
let g_coset = G::Scalar::ZETA;
|
|
let g_coset_inv = g_coset.square();
|
|
|
|
// TODO: merge these inversions together with t_evaluations batch inversion?
|
|
let ifft_divisor = G::Scalar::from_u64(1 << k).invert().unwrap();
|
|
let extended_ifft_divisor = G::Scalar::from_u64(1 << extended_k).invert().unwrap();
|
|
|
|
let mut t_evaluations = Vec::with_capacity(1 << (extended_k - k));
|
|
{
|
|
// Compute the evaluations of t(X) in the coset evaluation domain.
|
|
// We don't have to compute all of them, because it will repeat.
|
|
let orig = G::Scalar::ZETA.pow_vartime(&[n as u64, 0, 0, 0]);
|
|
let step = extended_omega.pow_vartime(&[n as u64, 0, 0, 0]);
|
|
let mut cur = orig;
|
|
loop {
|
|
t_evaluations.push(cur);
|
|
cur *= &step;
|
|
if cur == orig {
|
|
break;
|
|
}
|
|
}
|
|
assert_eq!(t_evaluations.len(), 1 << (extended_k - k));
|
|
|
|
// Subtract 1 from each to give us t_evaluations[i] = t(zeta * extended_omega^i)
|
|
for coeff in &mut t_evaluations {
|
|
*coeff -= &G::Scalar::one();
|
|
}
|
|
|
|
// Invert, because we're dividing by this polynomial.
|
|
G::Scalar::batch_invert(&mut t_evaluations);
|
|
}
|
|
|
|
EvaluationDomain {
|
|
n,
|
|
k,
|
|
extended_k,
|
|
omega_inv,
|
|
extended_omega,
|
|
extended_omega_inv,
|
|
g_coset,
|
|
g_coset_inv,
|
|
quotient_poly_degree,
|
|
ifft_divisor,
|
|
extended_ifft_divisor,
|
|
t_evaluations,
|
|
}
|
|
}
|
|
|
|
/// This takes us from an n-length vector into the coset evaluation domain.
|
|
/// Also returns the polynomial.
|
|
///
|
|
/// This function will panic if the provided vector is not the correct
|
|
/// length.
|
|
pub fn obtain_coset(&self, mut a: Vec<G>) -> (Vec<G>, Vec<G>) {
|
|
assert_eq!(a.len(), 1 << self.k);
|
|
|
|
// Perform inverse FFT to obtain the polynomial in coefficient form
|
|
Self::ifft(&mut a, self.omega_inv, self.k, self.ifft_divisor);
|
|
|
|
// Keep this polynomial around; we'll need to evaluate it at arbitrary
|
|
// points later.
|
|
let old = a.clone();
|
|
|
|
// Distributes powers so that an FFT will move us into the coset
|
|
// evaluation domain.
|
|
Self::distribute_powers(&mut a, self.g_coset);
|
|
|
|
// Resize to account for the quotient polynomial's size
|
|
a.resize(1 << self.extended_k, G::group_zero());
|
|
|
|
// Move into coset evaluation domain
|
|
best_fft(&mut a, self.extended_omega, self.extended_k);
|
|
|
|
(a, old)
|
|
}
|
|
|
|
/// This takes us from the coset evaluation domain and gets us the quotient
|
|
/// polynomial coefficients.
|
|
///
|
|
/// This function will panic if the provided vector is not the correct
|
|
/// length.
|
|
pub fn from_coset(&self, mut a: Vec<G>) -> Vec<G> {
|
|
assert_eq!(a.len(), 1 << self.extended_k);
|
|
|
|
// Inverse FFT
|
|
Self::ifft(
|
|
&mut a,
|
|
self.extended_omega_inv,
|
|
self.extended_k,
|
|
self.extended_ifft_divisor,
|
|
);
|
|
|
|
// Distribute powers to move from coset; opposite from the
|
|
// transformation we performed earlier.
|
|
Self::distribute_powers(&mut a, self.g_coset_inv);
|
|
|
|
// Truncate it to match the size of the quotient polynomial; the
|
|
// evaluation domain might be slightly larger than necessary because
|
|
// it always lies on a power-of-two boundary.
|
|
a.truncate((&self.n * self.quotient_poly_degree) as usize);
|
|
|
|
a
|
|
}
|
|
|
|
/// This divides the polynomial (in the coset domain) by the vanishing
|
|
/// polynomial.
|
|
pub fn divide_by_vanishing_poly(&self, mut h_poly: Vec<G>) -> Vec<G> {
|
|
assert_eq!(h_poly.len(), 1 << self.extended_k);
|
|
|
|
// Divide to obtain the quotient polynomial in the coset evaluation
|
|
// domain.
|
|
parallelize(&mut h_poly, |h, mut index| {
|
|
for h in h {
|
|
h.group_scale(&self.t_evaluations[index % self.t_evaluations.len()]);
|
|
index += 1;
|
|
}
|
|
});
|
|
|
|
h_poly
|
|
}
|
|
|
|
fn distribute_powers(mut a: &mut [G], g: G::Scalar) {
|
|
let coset_powers = [g, g.square()];
|
|
parallelize(&mut a, |a, mut index| {
|
|
for a in a {
|
|
// Distribute powers to move into coset
|
|
let i = index % (coset_powers.len() + 1);
|
|
if i != 0 {
|
|
a.group_scale(&coset_powers[i - 1]);
|
|
}
|
|
index += 1;
|
|
}
|
|
});
|
|
}
|
|
|
|
fn ifft(a: &mut [G], omega_inv: G::Scalar, log_n: u32, divisor: G::Scalar) {
|
|
best_fft(a, omega_inv, log_n);
|
|
parallelize(a, |a, _| {
|
|
for a in a {
|
|
// Finish iFFT
|
|
a.group_scale(&divisor);
|
|
}
|
|
});
|
|
}
|
|
}
|