2020-09-07 09:22:25 -07:00
|
|
|
//! Contains utilities for performing polynomial arithmetic over an evaluation
|
|
|
|
//! domain that is of a suitable size for the application.
|
|
|
|
|
2021-06-11 09:34:25 -07:00
|
|
|
use crate::{
|
2021-09-30 14:35:33 -07:00
|
|
|
arithmetic::{best_fft, parallelize, FieldExt, Group},
|
2021-06-11 09:34:25 -07:00
|
|
|
plonk::Assigned,
|
|
|
|
};
|
2020-08-22 13:15:39 -07:00
|
|
|
|
2020-12-23 08:45:16 -08:00
|
|
|
use super::{Coeff, ExtendedLagrangeCoeff, LagrangeCoeff, Polynomial, Rotation};
|
2020-11-12 16:08:08 -08:00
|
|
|
|
2021-09-30 14:35:33 -07:00
|
|
|
use group::ff::{BatchInvert, Field, PrimeField};
|
|
|
|
|
2020-09-07 09:22:25 -07:00
|
|
|
use std::marker::PhantomData;
|
2021-02-17 12:53:31 -08:00
|
|
|
|
2020-08-22 13:15:39 -07:00
|
|
|
/// This structure contains precomputed constants and other details needed for
|
2020-09-07 09:22:25 -07:00
|
|
|
/// performing operations on an evaluation domain of size $2^k$ and an extended
|
|
|
|
/// domain of size $2^{k} * j$ with $j \neq 0$.
|
2022-04-26 04:53:44 -07:00
|
|
|
#[derive(Clone, Debug)]
|
2020-08-22 13:15:39 -07:00
|
|
|
pub struct EvaluationDomain<G: Group> {
|
|
|
|
n: u64,
|
|
|
|
k: u32,
|
|
|
|
extended_k: u32,
|
2020-08-24 12:50:52 -07:00
|
|
|
omega: G::Scalar,
|
2020-08-22 13:15:39 -07:00
|
|
|
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>,
|
2020-09-02 12:15:40 -07:00
|
|
|
barycentric_weight: G::Scalar,
|
2020-08-22 13:15:39 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
impl<G: Group> EvaluationDomain<G> {
|
2020-09-07 09:22:25 -07:00
|
|
|
/// This constructs a new evaluation domain object based on the provided
|
|
|
|
/// values $j, k$.
|
|
|
|
pub fn new(j: u32, k: u32) -> Self {
|
2020-08-22 13:15:39 -07:00
|
|
|
// quotient_poly_degree * params.n - 1 is the degree of the quotient polynomial
|
2020-09-07 09:22:25 -07:00
|
|
|
let quotient_poly_degree = (j - 1) as u64;
|
2020-08-22 13:15:39 -07:00
|
|
|
|
2020-11-09 21:44:52 -08:00
|
|
|
// n = 2^k
|
2020-08-22 13:15:39 -07:00
|
|
|
let n = 1u64 << k;
|
|
|
|
|
2020-11-09 21:44:52 -08:00
|
|
|
// We need to work within an extended domain, not params.k but params.k + i
|
|
|
|
// for some integer i such that 2^(params.k + i) is sufficiently large to
|
|
|
|
// describe the quotient polynomial.
|
2020-08-22 13:15:39 -07:00
|
|
|
let mut extended_k = k;
|
|
|
|
while (1 << extended_k) < (n * quotient_poly_degree) {
|
|
|
|
extended_k += 1;
|
|
|
|
}
|
|
|
|
|
2021-09-30 15:05:47 -07:00
|
|
|
let mut extended_omega = G::Scalar::root_of_unity();
|
2020-11-09 21:44:52 -08:00
|
|
|
|
|
|
|
// Get extended_omega, the 2^{extended_k}'th root of unity
|
|
|
|
// The loop computes extended_omega = omega^{2 ^ (S - extended_k)}
|
|
|
|
// Notice that extended_omega ^ {2 ^ extended_k} = omega ^ {2^S} = 1.
|
2020-08-22 13:15:39 -07:00
|
|
|
for _ in extended_k..G::Scalar::S {
|
|
|
|
extended_omega = extended_omega.square();
|
|
|
|
}
|
2020-11-09 21:44:52 -08:00
|
|
|
let extended_omega = extended_omega;
|
2020-09-06 11:44:36 -07:00
|
|
|
let mut extended_omega_inv = extended_omega; // Inversion computed later
|
2020-08-22 13:15:39 -07:00
|
|
|
|
2020-11-09 21:44:52 -08:00
|
|
|
// Get omega, the 2^{k}'th root of unity (i.e. n'th root of unity)
|
|
|
|
// The loop computes omega = extended_omega ^ {2 ^ (extended_k - k)}
|
|
|
|
// = (omega^{2 ^ (S - extended_k)}) ^ {2 ^ (extended_k - k)}
|
|
|
|
// = omega ^ {2 ^ (S - k)}.
|
|
|
|
// Notice that omega ^ {2^k} = omega ^ {2^S} = 1.
|
2020-08-22 13:15:39 -07:00
|
|
|
let mut omega = extended_omega;
|
|
|
|
for _ in k..extended_k {
|
|
|
|
omega = omega.square();
|
|
|
|
}
|
2020-11-09 21:44:52 -08:00
|
|
|
let omega = omega;
|
2020-09-06 11:44:36 -07:00
|
|
|
let mut omega_inv = omega; // Inversion computed later
|
2020-08-22 13:15:39 -07:00
|
|
|
|
|
|
|
// We use zeta here because we know it generates a coset, and it's available
|
|
|
|
// already.
|
2020-11-09 21:44:52 -08:00
|
|
|
// The coset evaluation domain is:
|
|
|
|
// zeta {1, extended_omega, extended_omega^2, ..., extended_omega^{(2^extended_k) - 1}}
|
2020-08-22 13:15:39 -07:00
|
|
|
let g_coset = G::Scalar::ZETA;
|
|
|
|
let g_coset_inv = g_coset.square();
|
|
|
|
|
|
|
|
let mut t_evaluations = Vec::with_capacity(1 << (extended_k - k));
|
|
|
|
{
|
2020-11-09 21:44:52 -08:00
|
|
|
// Compute the evaluations of t(X) = X^n - 1 in the coset evaluation domain.
|
2020-08-22 13:15:39 -07:00
|
|
|
// 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.
|
2020-09-06 11:44:36 -07:00
|
|
|
// We invert in a batch, below.
|
2020-08-22 13:15:39 -07:00
|
|
|
}
|
|
|
|
|
2021-09-30 14:51:58 -07:00
|
|
|
let mut ifft_divisor = G::Scalar::from(1 << k); // Inversion computed later
|
|
|
|
let mut extended_ifft_divisor = G::Scalar::from(1 << extended_k); // Inversion computed later
|
2020-09-06 11:44:36 -07:00
|
|
|
|
2020-09-02 12:15:40 -07:00
|
|
|
// The barycentric weight of 1 over the evaluation domain
|
|
|
|
// 1 / \prod_{i != 0} (1 - omega^i)
|
2020-09-06 11:44:36 -07:00
|
|
|
let mut barycentric_weight = G::Scalar::from(n); // Inversion computed later
|
|
|
|
|
|
|
|
// Compute batch inversion
|
|
|
|
t_evaluations
|
|
|
|
.iter_mut()
|
|
|
|
.chain(Some(&mut ifft_divisor))
|
|
|
|
.chain(Some(&mut extended_ifft_divisor))
|
|
|
|
.chain(Some(&mut barycentric_weight))
|
|
|
|
.chain(Some(&mut extended_omega_inv))
|
|
|
|
.chain(Some(&mut omega_inv))
|
|
|
|
.batch_invert();
|
2020-09-02 12:15:40 -07:00
|
|
|
|
2020-08-22 13:15:39 -07:00
|
|
|
EvaluationDomain {
|
|
|
|
n,
|
|
|
|
k,
|
|
|
|
extended_k,
|
2020-08-24 12:50:52 -07:00
|
|
|
omega,
|
2020-08-22 13:15:39 -07:00
|
|
|
omega_inv,
|
|
|
|
extended_omega,
|
|
|
|
extended_omega_inv,
|
|
|
|
g_coset,
|
|
|
|
g_coset_inv,
|
|
|
|
quotient_poly_degree,
|
|
|
|
ifft_divisor,
|
|
|
|
extended_ifft_divisor,
|
|
|
|
t_evaluations,
|
2020-09-02 12:15:40 -07:00
|
|
|
barycentric_weight,
|
2020-08-22 13:15:39 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-07 09:22:25 -07:00
|
|
|
/// Obtains a polynomial in Lagrange form when given a vector of Lagrange
|
|
|
|
/// coefficients of size `n`; panics if the provided vector is the wrong
|
|
|
|
/// length.
|
|
|
|
pub fn lagrange_from_vec(&self, values: Vec<G>) -> Polynomial<G, LagrangeCoeff> {
|
|
|
|
assert_eq!(values.len(), self.n as usize);
|
|
|
|
|
|
|
|
Polynomial {
|
|
|
|
values,
|
|
|
|
_marker: PhantomData,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Obtains a polynomial in coefficient form when given a vector of
|
|
|
|
/// coefficients of size `n`; panics if the provided vector is the wrong
|
|
|
|
/// length.
|
|
|
|
pub fn coeff_from_vec(&self, values: Vec<G>) -> Polynomial<G, Coeff> {
|
|
|
|
assert_eq!(values.len(), self.n as usize);
|
|
|
|
|
|
|
|
Polynomial {
|
|
|
|
values,
|
|
|
|
_marker: PhantomData,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns an empty (zero) polynomial in the coefficient basis
|
|
|
|
pub fn empty_coeff(&self) -> Polynomial<G, Coeff> {
|
|
|
|
Polynomial {
|
|
|
|
values: vec![G::group_zero(); self.n as usize],
|
|
|
|
_marker: PhantomData,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns an empty (zero) polynomial in the Lagrange coefficient basis
|
|
|
|
pub fn empty_lagrange(&self) -> Polynomial<G, LagrangeCoeff> {
|
|
|
|
Polynomial {
|
|
|
|
values: vec![G::group_zero(); self.n as usize],
|
|
|
|
_marker: PhantomData,
|
|
|
|
}
|
|
|
|
}
|
2021-06-11 09:34:25 -07:00
|
|
|
|
|
|
|
/// Returns an empty (zero) polynomial in the Lagrange coefficient basis, with
|
|
|
|
/// deferred inversions.
|
|
|
|
pub(crate) fn empty_lagrange_assigned(&self) -> Polynomial<Assigned<G>, LagrangeCoeff>
|
|
|
|
where
|
|
|
|
G: Field,
|
|
|
|
{
|
|
|
|
Polynomial {
|
|
|
|
values: vec![G::group_zero().into(); self.n as usize],
|
|
|
|
_marker: PhantomData,
|
|
|
|
}
|
|
|
|
}
|
2020-09-07 09:22:25 -07:00
|
|
|
|
2021-02-13 02:36:29 -08:00
|
|
|
/// Returns a constant polynomial in the Lagrange coefficient basis
|
2021-02-13 16:48:44 -08:00
|
|
|
pub fn constant_lagrange(&self, scalar: G) -> Polynomial<G, LagrangeCoeff> {
|
2021-02-13 02:36:29 -08:00
|
|
|
Polynomial {
|
|
|
|
values: vec![scalar; self.n as usize],
|
|
|
|
_marker: PhantomData,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-07 09:22:25 -07:00
|
|
|
/// Returns an empty (zero) polynomial in the extended Lagrange coefficient
|
|
|
|
/// basis
|
|
|
|
pub fn empty_extended(&self) -> Polynomial<G, ExtendedLagrangeCoeff> {
|
|
|
|
Polynomial {
|
|
|
|
values: vec![G::group_zero(); self.extended_len()],
|
|
|
|
_marker: PhantomData,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-13 02:36:29 -08:00
|
|
|
/// Returns a constant polynomial in the extended Lagrange coefficient
|
|
|
|
/// basis
|
2021-02-13 16:48:44 -08:00
|
|
|
pub fn constant_extended(&self, scalar: G) -> Polynomial<G, ExtendedLagrangeCoeff> {
|
2021-02-13 02:36:29 -08:00
|
|
|
Polynomial {
|
|
|
|
values: vec![scalar; self.extended_len()],
|
|
|
|
_marker: PhantomData,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-24 12:50:52 -07:00
|
|
|
/// This takes us from an n-length vector into the coefficient form.
|
2020-08-22 13:15:39 -07:00
|
|
|
///
|
|
|
|
/// This function will panic if the provided vector is not the correct
|
|
|
|
/// length.
|
2020-09-07 09:22:25 -07:00
|
|
|
pub fn lagrange_to_coeff(&self, mut a: Polynomial<G, LagrangeCoeff>) -> Polynomial<G, Coeff> {
|
|
|
|
assert_eq!(a.values.len(), 1 << self.k);
|
2020-08-22 13:15:39 -07:00
|
|
|
|
|
|
|
// Perform inverse FFT to obtain the polynomial in coefficient form
|
2020-09-07 09:22:25 -07:00
|
|
|
Self::ifft(&mut a.values, self.omega_inv, self.k, self.ifft_divisor);
|
2020-08-22 13:15:39 -07:00
|
|
|
|
2020-09-07 09:22:25 -07:00
|
|
|
Polynomial {
|
|
|
|
values: a.values,
|
|
|
|
_marker: PhantomData,
|
|
|
|
}
|
2020-08-24 12:50:52 -07:00
|
|
|
}
|
2020-08-22 13:15:39 -07:00
|
|
|
|
2020-11-09 21:44:52 -08:00
|
|
|
/// This takes us from an n-length coefficient vector into a coset of the extended
|
2020-08-27 12:27:24 -07:00
|
|
|
/// evaluation domain, rotating by `rotation` if desired.
|
2020-09-07 09:22:25 -07:00
|
|
|
pub fn coeff_to_extended(
|
|
|
|
&self,
|
|
|
|
mut a: Polynomial<G, Coeff>,
|
|
|
|
) -> Polynomial<G, ExtendedLagrangeCoeff> {
|
|
|
|
assert_eq!(a.values.len(), 1 << self.k);
|
2020-08-22 13:15:39 -07:00
|
|
|
|
2021-07-13 09:10:03 -07:00
|
|
|
self.distribute_powers_zeta(&mut a.values, true);
|
2020-09-07 09:22:25 -07:00
|
|
|
a.values.resize(self.extended_len(), G::group_zero());
|
|
|
|
best_fft(&mut a.values, self.extended_omega, self.extended_k);
|
|
|
|
|
|
|
|
Polynomial {
|
|
|
|
values: a.values,
|
|
|
|
_marker: PhantomData,
|
2020-08-24 12:50:52 -07:00
|
|
|
}
|
2020-08-22 13:15:39 -07:00
|
|
|
}
|
|
|
|
|
2021-07-12 10:44:09 -07:00
|
|
|
/// Rotate the extended domain polynomial over the original domain.
|
|
|
|
pub fn rotate_extended(
|
|
|
|
&self,
|
|
|
|
poly: &Polynomial<G, ExtendedLagrangeCoeff>,
|
|
|
|
rotation: Rotation,
|
|
|
|
) -> Polynomial<G, ExtendedLagrangeCoeff> {
|
|
|
|
let new_rotation = ((1 << (self.extended_k - self.k)) * rotation.0.abs()) as usize;
|
|
|
|
|
|
|
|
let mut poly = poly.clone();
|
|
|
|
|
|
|
|
if rotation.0 >= 0 {
|
|
|
|
poly.values.rotate_left(new_rotation);
|
|
|
|
} else {
|
|
|
|
poly.values.rotate_right(new_rotation);
|
|
|
|
}
|
|
|
|
|
|
|
|
poly
|
|
|
|
}
|
|
|
|
|
2020-09-07 09:22:25 -07:00
|
|
|
/// This takes us from the extended evaluation domain and gets us the
|
|
|
|
/// quotient polynomial coefficients.
|
2020-08-22 13:15:39 -07:00
|
|
|
///
|
|
|
|
/// This function will panic if the provided vector is not the correct
|
|
|
|
/// length.
|
2020-09-07 09:22:25 -07:00
|
|
|
// TODO/FIXME: caller should be responsible for truncating
|
|
|
|
pub fn extended_to_coeff(&self, mut a: Polynomial<G, ExtendedLagrangeCoeff>) -> Vec<G> {
|
|
|
|
assert_eq!(a.values.len(), self.extended_len());
|
2020-08-22 13:15:39 -07:00
|
|
|
|
|
|
|
// Inverse FFT
|
|
|
|
Self::ifft(
|
2020-09-07 09:22:25 -07:00
|
|
|
&mut a.values,
|
2020-08-22 13:15:39 -07:00
|
|
|
self.extended_omega_inv,
|
|
|
|
self.extended_k,
|
|
|
|
self.extended_ifft_divisor,
|
|
|
|
);
|
|
|
|
|
|
|
|
// Distribute powers to move from coset; opposite from the
|
|
|
|
// transformation we performed earlier.
|
2021-07-13 09:10:03 -07:00
|
|
|
self.distribute_powers_zeta(&mut a.values, false);
|
2020-08-22 13:15:39 -07:00
|
|
|
|
|
|
|
// 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.
|
2020-09-07 09:22:25 -07:00
|
|
|
a.values
|
|
|
|
.truncate((&self.n * self.quotient_poly_degree) as usize);
|
2020-08-22 13:15:39 -07:00
|
|
|
|
2020-09-07 09:22:25 -07:00
|
|
|
a.values
|
2020-08-22 13:15:39 -07:00
|
|
|
}
|
|
|
|
|
2020-09-07 09:22:25 -07:00
|
|
|
/// This divides the polynomial (in the extended domain) by the vanishing
|
|
|
|
/// polynomial of the $2^k$ size domain.
|
|
|
|
pub fn divide_by_vanishing_poly(
|
|
|
|
&self,
|
|
|
|
mut a: Polynomial<G, ExtendedLagrangeCoeff>,
|
|
|
|
) -> Polynomial<G, ExtendedLagrangeCoeff> {
|
|
|
|
assert_eq!(a.values.len(), self.extended_len());
|
2020-08-22 13:15:39 -07:00
|
|
|
|
|
|
|
// Divide to obtain the quotient polynomial in the coset evaluation
|
|
|
|
// domain.
|
2020-09-07 09:22:25 -07:00
|
|
|
parallelize(&mut a.values, |h, mut index| {
|
2020-08-22 13:15:39 -07:00
|
|
|
for h in h {
|
|
|
|
h.group_scale(&self.t_evaluations[index % self.t_evaluations.len()]);
|
|
|
|
index += 1;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2020-09-07 09:22:25 -07:00
|
|
|
Polynomial {
|
|
|
|
values: a.values,
|
|
|
|
_marker: PhantomData,
|
|
|
|
}
|
2020-08-22 13:15:39 -07:00
|
|
|
}
|
|
|
|
|
2021-07-13 09:10:03 -07:00
|
|
|
/// Given a slice of group elements `[a_0, a_1, a_2, ...]`, this returns
|
|
|
|
/// `[a_0, [zeta]a_1, [zeta^2]a_2, a_3, [zeta]a_4, [zeta^2]a_5, a_6, ...]`,
|
|
|
|
/// where zeta is a cube root of unity in the multiplicative subgroup with
|
|
|
|
/// order (p - 1), i.e. zeta^3 = 1.
|
2021-07-19 08:08:24 -07:00
|
|
|
///
|
|
|
|
/// `into_coset` should be set to `true` when moving into the coset,
|
|
|
|
/// and `false` when moving out. This toggles the choice of `zeta`.
|
2021-11-23 14:38:48 -08:00
|
|
|
fn distribute_powers_zeta(&self, a: &mut [G], into_coset: bool) {
|
2021-07-13 09:10:03 -07:00
|
|
|
let coset_powers = if into_coset {
|
|
|
|
[self.g_coset, self.g_coset_inv]
|
2021-07-12 10:44:09 -07:00
|
|
|
} else {
|
2021-07-13 09:10:03 -07:00
|
|
|
[self.g_coset_inv, self.g_coset]
|
2021-07-12 10:44:09 -07:00
|
|
|
};
|
2021-11-23 14:38:48 -08:00
|
|
|
parallelize(a, |a, mut index| {
|
2020-08-22 13:15:39 -07:00
|
|
|
for a in a {
|
2021-07-12 10:44:09 -07:00
|
|
|
// Distribute powers to move into/from coset
|
2020-08-22 13:15:39 -07:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
2020-08-27 09:10:55 -07:00
|
|
|
|
2020-09-07 09:22:25 -07:00
|
|
|
/// Get the size of the extended domain
|
|
|
|
pub fn extended_len(&self) -> usize {
|
2020-08-27 09:10:55 -07:00
|
|
|
1 << self.extended_k
|
|
|
|
}
|
|
|
|
|
2020-09-07 09:22:25 -07:00
|
|
|
/// Get $\omega$, the generator of the $2^k$ order multiplicative subgroup.
|
2020-08-27 09:10:55 -07:00
|
|
|
pub fn get_omega(&self) -> G::Scalar {
|
|
|
|
self.omega
|
|
|
|
}
|
|
|
|
|
2020-09-07 09:22:25 -07:00
|
|
|
/// Get $\omega^{-1}$, the inverse of the generator of the $2^k$ order
|
|
|
|
/// multiplicative subgroup.
|
2020-08-27 09:10:55 -07:00
|
|
|
pub fn get_omega_inv(&self) -> G::Scalar {
|
|
|
|
self.omega_inv
|
|
|
|
}
|
2020-08-27 12:27:24 -07:00
|
|
|
|
2020-09-07 09:22:25 -07:00
|
|
|
/// Get the generator of the extended domain's multiplicative subgroup.
|
|
|
|
pub fn get_extended_omega(&self) -> G::Scalar {
|
|
|
|
self.extended_omega
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Multiplies a value by some power of $\omega$, essentially rotating over
|
|
|
|
/// the domain.
|
|
|
|
pub fn rotate_omega(&self, value: G::Scalar, rotation: Rotation) -> G::Scalar {
|
|
|
|
let mut point = value;
|
2020-08-27 12:27:24 -07:00
|
|
|
if rotation.0 >= 0 {
|
2021-03-02 11:40:29 -08:00
|
|
|
point *= &self.get_omega().pow_vartime(&[rotation.0 as u64]);
|
2020-08-27 12:27:24 -07:00
|
|
|
} else {
|
|
|
|
point *= &self
|
|
|
|
.get_omega_inv()
|
2021-03-02 11:40:29 -08:00
|
|
|
.pow_vartime(&[(rotation.0 as i64).abs() as u64]);
|
2020-08-27 12:27:24 -07:00
|
|
|
}
|
|
|
|
point
|
|
|
|
}
|
2020-09-02 12:15:40 -07:00
|
|
|
|
2021-03-02 11:40:29 -08:00
|
|
|
/// Computes evaluations (at the point `x`, where `xn = x^n`) of Lagrange
|
|
|
|
/// basis polynomials `l_i(X)` defined such that `l_i(omega^i) = 1` and
|
|
|
|
/// `l_i(omega^j) = 0` for all `j != i` at each provided rotation `i`.
|
2021-07-14 08:05:40 -07:00
|
|
|
///
|
|
|
|
/// # Implementation
|
|
|
|
///
|
|
|
|
/// The polynomial
|
|
|
|
/// $$\prod_{j=0,j \neq i}^{n - 1} (X - \omega^j)$$
|
|
|
|
/// has a root at all points in the domain except $\omega^i$, where it evaluates to
|
|
|
|
/// $$\prod_{j=0,j \neq i}^{n - 1} (\omega^i - \omega^j)$$
|
|
|
|
/// and so we divide that polynomial by this value to obtain $l_i(X)$. Since
|
|
|
|
/// $$\prod_{j=0,j \neq i}^{n - 1} (X - \omega^j)
|
|
|
|
/// = \frac{X^n - 1}{X - \omega^i}$$
|
|
|
|
/// then $l_i(x)$ for some $x$ is evaluated as
|
|
|
|
/// $$\left(\frac{x^n - 1}{x - \omega^i}\right)
|
|
|
|
/// \cdot \left(\frac{1}{\prod_{j=0,j \neq i}^{n - 1} (\omega^i - \omega^j)}\right).$$
|
|
|
|
/// We refer to
|
|
|
|
/// $$1 \over \prod_{j=0,j \neq i}^{n - 1} (\omega^i - \omega^j)$$
|
|
|
|
/// as the barycentric weight of $\omega^i$.
|
|
|
|
///
|
|
|
|
/// We know that for $i = 0$
|
|
|
|
/// $$\frac{1}{\prod_{j=0,j \neq i}^{n - 1} (\omega^i - \omega^j)} = \frac{1}{n}.$$
|
|
|
|
///
|
2021-07-14 08:19:56 -07:00
|
|
|
/// If we multiply $(1 / n)$ by $\omega^i$ then we obtain
|
|
|
|
/// $$\frac{1}{\prod_{j=0,j \neq 0}^{n - 1} (\omega^i - \omega^j)}
|
|
|
|
/// = \frac{1}{\prod_{j=0,j \neq i}^{n - 1} (\omega^i - \omega^j)}$$
|
|
|
|
/// which is the barycentric weight of $\omega^i$.
|
2021-03-02 11:40:29 -08:00
|
|
|
pub fn l_i_range<I: IntoIterator<Item = i32> + Clone>(
|
|
|
|
&self,
|
|
|
|
x: G::Scalar,
|
|
|
|
xn: G::Scalar,
|
|
|
|
rotations: I,
|
|
|
|
) -> Vec<G::Scalar> {
|
|
|
|
let mut results;
|
|
|
|
{
|
|
|
|
let rotations = rotations.clone().into_iter();
|
|
|
|
results = Vec::with_capacity(rotations.size_hint().1.unwrap_or(0));
|
|
|
|
for rotation in rotations {
|
|
|
|
let rotation = Rotation(rotation);
|
|
|
|
let result = x - self.rotate_omega(G::Scalar::one(), rotation);
|
|
|
|
results.push(result);
|
|
|
|
}
|
|
|
|
results.iter_mut().batch_invert();
|
|
|
|
}
|
|
|
|
|
|
|
|
let common = (xn - G::Scalar::one()) * self.barycentric_weight;
|
|
|
|
for (rotation, result) in rotations.into_iter().zip(results.iter_mut()) {
|
|
|
|
let rotation = Rotation(rotation);
|
2021-07-14 08:07:21 -07:00
|
|
|
*result = self.rotate_omega(*result * common, rotation);
|
2021-03-02 11:40:29 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
results
|
2020-09-02 12:15:40 -07:00
|
|
|
}
|
2020-12-22 07:38:22 -08:00
|
|
|
|
|
|
|
/// Gets the quotient polynomial's degree (as a multiple of n)
|
|
|
|
pub fn get_quotient_poly_degree(&self) -> usize {
|
|
|
|
self.quotient_poly_degree as usize
|
|
|
|
}
|
2021-02-11 06:30:26 -08:00
|
|
|
|
2021-02-17 14:15:08 -08:00
|
|
|
/// Obtain a pinned version of this evaluation domain; a structure with the
|
|
|
|
/// minimal parameters needed to determine the rest of the evaluation
|
|
|
|
/// domain.
|
|
|
|
pub fn pinned(&self) -> PinnedEvaluationDomain<'_, G> {
|
|
|
|
PinnedEvaluationDomain {
|
|
|
|
k: &self.k,
|
|
|
|
extended_k: &self.extended_k,
|
|
|
|
omega: &self.omega,
|
|
|
|
}
|
2021-02-11 06:30:26 -08:00
|
|
|
}
|
2020-08-22 13:15:39 -07:00
|
|
|
}
|
2021-02-17 14:15:08 -08:00
|
|
|
|
|
|
|
/// Represents the minimal parameters that determine an `EvaluationDomain`.
|
2021-11-23 14:36:27 -08:00
|
|
|
#[allow(dead_code)]
|
2021-02-17 14:15:08 -08:00
|
|
|
#[derive(Debug)]
|
|
|
|
pub struct PinnedEvaluationDomain<'a, G: Group> {
|
|
|
|
k: &'a u32,
|
|
|
|
extended_k: &'a u32,
|
|
|
|
omega: &'a G::Scalar,
|
|
|
|
}
|
2021-02-18 15:25:00 -08:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_rotate() {
|
2022-01-20 08:16:48 -08:00
|
|
|
use rand_core::OsRng;
|
2021-12-25 05:36:41 -08:00
|
|
|
|
2021-02-18 15:25:00 -08:00
|
|
|
use crate::arithmetic::eval_polynomial;
|
|
|
|
use crate::pasta::pallas::Scalar;
|
2021-12-25 05:36:41 -08:00
|
|
|
|
2021-02-18 15:25:00 -08:00
|
|
|
let domain = EvaluationDomain::<Scalar>::new(1, 3);
|
2021-12-25 05:36:41 -08:00
|
|
|
let rng = OsRng;
|
2021-02-18 15:25:00 -08:00
|
|
|
|
|
|
|
let mut poly = domain.empty_lagrange();
|
|
|
|
assert_eq!(poly.len(), 8);
|
|
|
|
for value in poly.iter_mut() {
|
2021-12-25 05:36:41 -08:00
|
|
|
*value = Scalar::random(rng);
|
2021-02-18 15:25:00 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
let poly_rotated_cur = poly.rotate(Rotation::cur());
|
|
|
|
let poly_rotated_next = poly.rotate(Rotation::next());
|
|
|
|
let poly_rotated_prev = poly.rotate(Rotation::prev());
|
|
|
|
|
|
|
|
let poly = domain.lagrange_to_coeff(poly);
|
|
|
|
let poly_rotated_cur = domain.lagrange_to_coeff(poly_rotated_cur);
|
|
|
|
let poly_rotated_next = domain.lagrange_to_coeff(poly_rotated_next);
|
|
|
|
let poly_rotated_prev = domain.lagrange_to_coeff(poly_rotated_prev);
|
|
|
|
|
2021-12-25 05:36:41 -08:00
|
|
|
let x = Scalar::random(rng);
|
2021-02-18 15:25:00 -08:00
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
eval_polynomial(&poly[..], x),
|
|
|
|
eval_polynomial(&poly_rotated_cur[..], x)
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
eval_polynomial(&poly[..], x * domain.omega),
|
|
|
|
eval_polynomial(&poly_rotated_next[..], x)
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
eval_polynomial(&poly[..], x * domain.omega_inv),
|
|
|
|
eval_polynomial(&poly_rotated_prev[..], x)
|
|
|
|
);
|
|
|
|
}
|
2021-03-02 11:40:29 -08:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_l_i() {
|
2022-01-20 08:16:48 -08:00
|
|
|
use rand_core::OsRng;
|
2021-12-25 05:36:41 -08:00
|
|
|
|
2021-03-02 11:40:29 -08:00
|
|
|
use crate::arithmetic::{eval_polynomial, lagrange_interpolate};
|
|
|
|
use crate::pasta::pallas::Scalar;
|
|
|
|
let domain = EvaluationDomain::<Scalar>::new(1, 3);
|
|
|
|
|
|
|
|
let mut l = vec![];
|
|
|
|
let mut points = vec![];
|
|
|
|
for i in 0..8 {
|
|
|
|
points.push(domain.omega.pow(&[i, 0, 0, 0]));
|
|
|
|
}
|
|
|
|
for i in 0..8 {
|
|
|
|
let mut l_i = vec![Scalar::zero(); 8];
|
|
|
|
l_i[i] = Scalar::one();
|
|
|
|
let l_i = lagrange_interpolate(&points[..], &l_i[..]);
|
|
|
|
l.push(l_i);
|
|
|
|
}
|
|
|
|
|
2021-12-25 05:36:41 -08:00
|
|
|
let x = Scalar::random(OsRng);
|
2021-03-02 11:40:29 -08:00
|
|
|
let xn = x.pow(&[8, 0, 0, 0]);
|
|
|
|
|
|
|
|
let evaluations = domain.l_i_range(x, xn, -7..=7);
|
|
|
|
for i in 0..8 {
|
|
|
|
assert_eq!(eval_polynomial(&l[i][..], x), evaluations[7 + i]);
|
|
|
|
assert_eq!(eval_polynomial(&l[(8 - i) % 8][..], x), evaluations[7 - i]);
|
|
|
|
}
|
|
|
|
}
|