* add domain and vp * add lagrange interpolator * add query position to coset * nostd * add test assertion * fmt * fix test * add Add and Sub arithmetic * add Add and Sub arithmetic * add unit test for mul/div arithmetic * add more doc for clarification * add test for native interpolate * add test for vp constraints * fix lagrange interpolate bug * comment cleanup + fmt * add CHANGELOG * fix a compile error * Update CHANGELOG.md * Update CHANGELOG.md * fix comment * doc fix * doc update 2 * doc update 3 * pub lagrange_interpolator * doc fix * rename `EvaluationDomain` to `Radix2Domain` * tweak * tweak Co-authored-by: weikeng <w.k@berkeley.edu>master
@ -0,0 +1,77 @@ |
|||||
|
use crate::boolean::Boolean;
|
||||
|
use crate::fields::fp::FpVar;
|
||||
|
use crate::fields::FieldVar;
|
||||
|
use ark_ff::PrimeField;
|
||||
|
use ark_relations::r1cs::SynthesisError;
|
||||
|
use ark_std::vec::Vec;
|
||||
|
|
||||
|
pub mod vanishing_poly;
|
||||
|
|
||||
|
#[derive(Copy, Clone, Hash, Eq, PartialEq, Debug)]
|
||||
|
/// Defines an evaluation domain over a prime field. The domain is a coset of size `1<<dim`.
|
||||
|
///
|
||||
|
/// Native code corresponds to `ark-poly::univariate::domain::radix2`, but `ark-poly` only supports
|
||||
|
/// subgroup for now.
|
||||
|
///
|
||||
|
/// TODO: support cosets in `ark-poly`.
|
||||
|
pub struct Radix2Domain<F: PrimeField> {
|
||||
|
/// generator of subgroup g
|
||||
|
pub gen: F,
|
||||
|
/// index of the quotient group (i.e. the `offset`)
|
||||
|
pub offset: F,
|
||||
|
/// dimension of evaluation domain
|
||||
|
pub dim: u64,
|
||||
|
}
|
||||
|
|
||||
|
impl<F: PrimeField> Radix2Domain<F> {
|
||||
|
/// order of the domain
|
||||
|
pub fn order(&self) -> usize {
|
||||
|
1 << self.dim
|
||||
|
}
|
||||
|
|
||||
|
/// Returns g, g^2, ..., g^{dim}
|
||||
|
fn powers_of_gen(&self, dim: usize) -> Vec<F> {
|
||||
|
let mut result = Vec::new();
|
||||
|
let mut cur = self.gen;
|
||||
|
for _ in 0..dim {
|
||||
|
result.push(cur);
|
||||
|
cur = cur * cur;
|
||||
|
}
|
||||
|
result
|
||||
|
}
|
||||
|
|
||||
|
/// For domain `h<g>` with dimension `n`, `position` represented by `query_pos` in big endian form,
|
||||
|
/// returns `h*g^{position}<g^{n-query_pos.len()}>`
|
||||
|
pub fn query_position_to_coset(
|
||||
|
&self,
|
||||
|
query_pos: &[Boolean<F>],
|
||||
|
coset_dim: u64,
|
||||
|
) -> Result<Vec<FpVar<F>>, SynthesisError> {
|
||||
|
let mut coset_index = query_pos;
|
||||
|
assert!(
|
||||
|
query_pos.len() == self.dim as usize
|
||||
|
|| query_pos.len() == (self.dim - coset_dim) as usize
|
||||
|
);
|
||||
|
if query_pos.len() == self.dim as usize {
|
||||
|
coset_index = &coset_index[0..(coset_index.len() - coset_dim as usize)];
|
||||
|
}
|
||||
|
let mut coset = Vec::new();
|
||||
|
let powers_of_g = &self.powers_of_gen(self.dim as usize)[(coset_dim as usize)..];
|
||||
|
|
||||
|
let mut first_point_in_coset: FpVar<F> = FpVar::zero();
|
||||
|
for i in 0..coset_index.len() {
|
||||
|
let term = coset_index[i].select(&FpVar::constant(powers_of_g[i]), &FpVar::zero())?;
|
||||
|
first_point_in_coset += &term;
|
||||
|
}
|
||||
|
|
||||
|
first_point_in_coset *= &FpVar::Constant(self.offset);
|
||||
|
|
||||
|
coset.push(first_point_in_coset);
|
||||
|
for i in 1..(1 << (coset_dim as usize)) {
|
||||
|
let new_elem = &coset[i - 1] * &FpVar::Constant(self.gen);
|
||||
|
coset.push(new_elem);
|
||||
|
}
|
||||
|
|
||||
|
Ok(coset)
|
||||
|
}
|
||||
|
}
|
@ -0,0 +1,79 @@ |
|||||
|
use crate::fields::fp::FpVar;
|
||||
|
use crate::fields::FieldVar;
|
||||
|
use ark_ff::{Field, PrimeField};
|
||||
|
use ark_relations::r1cs::SynthesisError;
|
||||
|
use ark_std::ops::Sub;
|
||||
|
|
||||
|
/// Struct describing vanishing polynomial for a multiplicative coset H where |H| is a power of 2.
|
||||
|
/// As H is a coset, every element can be described as h*g^i and therefore
|
||||
|
/// has vanishing polynomial Z_H(x) = x^|H| - h^|H|
|
||||
|
#[derive(Clone)]
|
||||
|
pub struct VanishingPolynomial<F: Field> {
|
||||
|
/// h^|H|
|
||||
|
pub constant_term: F,
|
||||
|
/// log_2(|H|)
|
||||
|
pub dim_h: u64,
|
||||
|
/// |H|
|
||||
|
pub order_h: u64,
|
||||
|
}
|
||||
|
|
||||
|
impl<F: PrimeField> VanishingPolynomial<F> {
|
||||
|
/// returns a VanishingPolynomial of coset `H = h<g>`.
|
||||
|
pub fn new(offset: F, dim_h: u64) -> Self {
|
||||
|
let order_h = 1 << dim_h;
|
||||
|
let vp = VanishingPolynomial {
|
||||
|
constant_term: offset.pow([order_h]),
|
||||
|
dim_h,
|
||||
|
order_h,
|
||||
|
};
|
||||
|
vp
|
||||
|
}
|
||||
|
|
||||
|
/// Evaluates the vanishing polynomial without generating the constraints.
|
||||
|
pub fn evaluate(&self, x: &F) -> F {
|
||||
|
let mut result = x.pow([self.order_h]);
|
||||
|
result -= &self.constant_term;
|
||||
|
result
|
||||
|
}
|
||||
|
|
||||
|
/// Evaluates the constraints and just gives you the gadget for the result.
|
||||
|
/// Caution for use in holographic lincheck: The output has 2 entries in one matrix
|
||||
|
pub fn evaluate_constraints(&self, x: &FpVar<F>) -> Result<FpVar<F>, SynthesisError> {
|
||||
|
if self.dim_h == 1 {
|
||||
|
let result = x.sub(x);
|
||||
|
return Ok(result);
|
||||
|
}
|
||||
|
|
||||
|
let mut cur = x.square()?;
|
||||
|
for _ in 1..self.dim_h {
|
||||
|
cur.square_in_place()?;
|
||||
|
}
|
||||
|
cur -= &FpVar::Constant(self.constant_term);
|
||||
|
Ok(cur)
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
#[cfg(test)]
|
||||
|
mod tests {
|
||||
|
use crate::alloc::AllocVar;
|
||||
|
use crate::fields::fp::FpVar;
|
||||
|
use crate::poly::domain::vanishing_poly::VanishingPolynomial;
|
||||
|
use crate::R1CSVar;
|
||||
|
use ark_relations::r1cs::ConstraintSystem;
|
||||
|
use ark_std::{test_rng, UniformRand};
|
||||
|
use ark_test_curves::bls12_381::Fr;
|
||||
|
|
||||
|
#[test]
|
||||
|
fn constraints_test() {
|
||||
|
let mut rng = test_rng();
|
||||
|
let offset = Fr::rand(&mut rng);
|
||||
|
let cs = ConstraintSystem::new_ref();
|
||||
|
let x = Fr::rand(&mut rng);
|
||||
|
let x_var = FpVar::new_witness(ns!(cs, "x_var"), || Ok(x)).unwrap();
|
||||
|
let vp = VanishingPolynomial::new(offset, 12);
|
||||
|
let native = vp.evaluate(&x);
|
||||
|
let result_var = vp.evaluate_constraints(&x_var).unwrap();
|
||||
|
assert!(cs.is_satisfied().unwrap());
|
||||
|
assert_eq!(result_var.value().unwrap(), native);
|
||||
|
}
|
||||
|
}
|
@ -0,0 +1 @@ |
|||||
|
pub mod univariate;
|
@ -0,0 +1,142 @@ |
|||||
|
use crate::poly::domain::vanishing_poly::VanishingPolynomial;
|
||||
|
use ark_ff::{batch_inversion, PrimeField};
|
||||
|
use ark_std::vec::Vec;
|
||||
|
/// Struct describing Lagrange interpolation for a multiplicative coset I,
|
||||
|
/// with |I| a power of 2.
|
||||
|
/// TODO: Pull in lagrange poly explanation from libiop
|
||||
|
#[derive(Clone)]
|
||||
|
pub struct LagrangeInterpolator<F: PrimeField> {
|
||||
|
pub(crate) domain_order: usize,
|
||||
|
pub(crate) all_domain_elems: Vec<F>,
|
||||
|
pub(crate) v_inv_elems: Vec<F>,
|
||||
|
pub(crate) domain_vp: VanishingPolynomial<F>,
|
||||
|
poly_evaluations: Vec<F>,
|
||||
|
}
|
||||
|
|
||||
|
impl<F: PrimeField> LagrangeInterpolator<F> {
|
||||
|
/// Returns a lagrange interpolator, given the domain specification.
|
||||
|
pub fn new(
|
||||
|
domain_offset: F,
|
||||
|
domain_generator: F,
|
||||
|
domain_dim: u64,
|
||||
|
poly_evaluations: Vec<F>,
|
||||
|
) -> Self {
|
||||
|
let domain_order = 1 << domain_dim;
|
||||
|
assert_eq!(poly_evaluations.len(), domain_order);
|
||||
|
let mut cur_elem = domain_offset;
|
||||
|
let mut all_domain_elems = vec![domain_offset];
|
||||
|
let mut v_inv_elems: Vec<F> = Vec::new();
|
||||
|
// Cache all elements in the domain
|
||||
|
for _ in 1..domain_order {
|
||||
|
cur_elem *= domain_generator;
|
||||
|
all_domain_elems.push(cur_elem);
|
||||
|
}
|
||||
|
/*
|
||||
|
By computing the following elements as constants,
|
||||
|
we can further reduce the interpolation costs.
|
||||
|
|
||||
|
m = order of the interpolation domain
|
||||
|
v_inv[i] = prod_{j != i} h(g^i - g^j)
|
||||
|
We use the following facts to compute this: |
||||
|
v_inv[0] = m*h^{m-1}
|
||||
|
v_inv[i] = g^{-1} * v_inv[i-1]
|
||||
|
*/
|
||||
|
// TODO: Include proof of the above two points
|
||||
|
let g_inv = domain_generator.inverse().unwrap();
|
||||
|
let m = F::from((1 << domain_dim) as u128);
|
||||
|
let mut v_inv_i = m * domain_offset.pow([(domain_order - 1) as u64]);
|
||||
|
for _ in 0..domain_order {
|
||||
|
v_inv_elems.push(v_inv_i);
|
||||
|
v_inv_i *= g_inv;
|
||||
|
}
|
||||
|
|
||||
|
// TODO: Cache the intermediate terms with Z_H(x) evaluations.
|
||||
|
let vp = VanishingPolynomial::new(domain_offset, domain_dim);
|
||||
|
|
||||
|
let lagrange_interpolation: LagrangeInterpolator<F> = LagrangeInterpolator {
|
||||
|
domain_order,
|
||||
|
all_domain_elems,
|
||||
|
v_inv_elems,
|
||||
|
domain_vp: vp,
|
||||
|
poly_evaluations,
|
||||
|
};
|
||||
|
lagrange_interpolation
|
||||
|
}
|
||||
|
|
||||
|
pub(crate) fn compute_lagrange_coefficients(&self, interpolation_point: F) -> Vec<F> {
|
||||
|
/*
|
||||
|
* Let t be the interpolation point, H be the multiplicative coset, with elements of the form h*g^i.
|
||||
|
Compute each L_{i,H}(t) as Z_{H}(t) * v_i / (t- h g^i)
|
||||
|
where: |
||||
|
- Z_{H}(t) = \prod_{j} (t-h*g^j) = (t^m-h^m), and
|
||||
|
- v_{i} = 1 / \prod_{j \neq i} h(g^i-g^j).
|
||||
|
Below we use the fact that v_{0} = 1/(m * h^(m-1)) and v_{i+1} = g * v_{i}.
|
||||
|
We compute the inverse of each coefficient, and then batch invert the entire result.
|
||||
|
*/
|
||||
|
let vp_t_inv = self
|
||||
|
.domain_vp
|
||||
|
.evaluate(&interpolation_point)
|
||||
|
.inverse()
|
||||
|
.unwrap();
|
||||
|
let mut inverted_lagrange_coeffs: Vec<F> = Vec::with_capacity(self.all_domain_elems.len());
|
||||
|
for i in 0..self.domain_order {
|
||||
|
let l = vp_t_inv * self.v_inv_elems[i];
|
||||
|
let r = self.all_domain_elems[i];
|
||||
|
inverted_lagrange_coeffs.push(l * (interpolation_point - r));
|
||||
|
}
|
||||
|
let lagrange_coeffs = inverted_lagrange_coeffs.as_mut_slice();
|
||||
|
batch_inversion::<F>(lagrange_coeffs);
|
||||
|
lagrange_coeffs.iter().cloned().collect()
|
||||
|
}
|
||||
|
|
||||
|
pub fn interpolate(&self, interpolation_point: F) -> F {
|
||||
|
let lagrange_coeffs = self.compute_lagrange_coefficients(interpolation_point);
|
||||
|
let mut interpolation = F::zero();
|
||||
|
for i in 0..self.domain_order {
|
||||
|
interpolation += lagrange_coeffs[i] * self.poly_evaluations[i];
|
||||
|
}
|
||||
|
interpolation
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
#[cfg(test)]
|
||||
|
mod tests {
|
||||
|
use crate::poly::domain::Radix2Domain;
|
||||
|
use crate::poly::evaluations::univariate::lagrange_interpolator::LagrangeInterpolator;
|
||||
|
use ark_ff::{FftField, Field, One};
|
||||
|
use ark_poly::univariate::DensePolynomial;
|
||||
|
use ark_poly::{Polynomial, UVPolynomial};
|
||||
|
use ark_std::{test_rng, UniformRand};
|
||||
|
use ark_test_curves::bls12_381::Fr;
|
||||
|
|
||||
|
#[test]
|
||||
|
pub fn test_native_interpolate() {
|
||||
|
let mut rng = test_rng();
|
||||
|
let poly = DensePolynomial::rand(15, &mut rng);
|
||||
|
let gen = Fr::get_root_of_unity(1 << 4).unwrap();
|
||||
|
assert_eq!(gen.pow(&[1 << 4]), Fr::one());
|
||||
|
let domain = Radix2Domain {
|
||||
|
gen,
|
||||
|
offset: Fr::multiplicative_generator(),
|
||||
|
dim: 4, // 2^4 = 16
|
||||
|
};
|
||||
|
// generate evaluations of `poly` on this domain
|
||||
|
let mut coset_point = domain.offset;
|
||||
|
let mut oracle_evals = Vec::new();
|
||||
|
for _ in 0..(1 << 4) {
|
||||
|
oracle_evals.push(poly.evaluate(&coset_point));
|
||||
|
coset_point *= gen;
|
||||
|
}
|
||||
|
|
||||
|
let interpolator =
|
||||
|
LagrangeInterpolator::new(domain.offset, domain.gen, domain.dim, oracle_evals);
|
||||
|
|
||||
|
// the point to evaluate at
|
||||
|
let interpolate_point = Fr::rand(&mut rng);
|
||||
|
|
||||
|
let expected = poly.evaluate(&interpolate_point);
|
||||
|
let actual = interpolator.interpolate(interpolate_point);
|
||||
|
|
||||
|
assert_eq!(actual, expected)
|
||||
|
}
|
||||
|
}
|
@ -0,0 +1,320 @@ |
|||||
|
pub mod lagrange_interpolator;
|
||||
|
|
||||
|
use crate::alloc::AllocVar;
|
||||
|
use crate::fields::fp::FpVar;
|
||||
|
use crate::fields::FieldVar;
|
||||
|
use crate::poly::domain::Radix2Domain;
|
||||
|
use crate::poly::evaluations::univariate::lagrange_interpolator::LagrangeInterpolator;
|
||||
|
use crate::R1CSVar;
|
||||
|
use ark_ff::{batch_inversion, PrimeField};
|
||||
|
use ark_relations::r1cs::SynthesisError;
|
||||
|
use ark_std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Sub, SubAssign};
|
||||
|
use ark_std::vec::Vec;
|
||||
|
|
||||
|
#[derive(Clone)]
|
||||
|
/// Stores a UV polynomial in evaluation form.
|
||||
|
pub struct EvaluationsVar<F: PrimeField> {
|
||||
|
/// Evaluations of univariate polynomial over domain
|
||||
|
pub evals: Vec<FpVar<F>>,
|
||||
|
/// Optional Lagrange Interpolator. Useful for lagrange interpolation.
|
||||
|
pub lagrange_interpolator: Option<LagrangeInterpolator<F>>,
|
||||
|
domain: Radix2Domain<F>,
|
||||
|
}
|
||||
|
|
||||
|
impl<F: PrimeField> EvaluationsVar<F> {
|
||||
|
/// Construct `Self` from evaluations and a domain.
|
||||
|
/// `interpolate` indicates if user wants to interpolate this polynomial
|
||||
|
/// using lagrange interpolation.
|
||||
|
pub fn from_vec_and_domain(
|
||||
|
evaluations: Vec<FpVar<F>>,
|
||||
|
domain: Radix2Domain<F>,
|
||||
|
interpolate: bool,
|
||||
|
) -> Self {
|
||||
|
assert_eq!(
|
||||
|
evaluations.len(),
|
||||
|
1 << domain.dim,
|
||||
|
"evaluations and domain has different dimensions"
|
||||
|
);
|
||||
|
|
||||
|
let mut ev = Self {
|
||||
|
evals: evaluations,
|
||||
|
lagrange_interpolator: None,
|
||||
|
domain,
|
||||
|
};
|
||||
|
if interpolate {
|
||||
|
ev.generate_lagrange_interpolator();
|
||||
|
}
|
||||
|
ev
|
||||
|
}
|
||||
|
|
||||
|
/// Generate lagrange interpolator and mark it ready to interpolate
|
||||
|
pub fn generate_lagrange_interpolator(&mut self) {
|
||||
|
let poly_evaluations_val: Vec<_> = self.evals.iter().map(|v| v.value().unwrap()).collect();
|
||||
|
let domain = &self.domain;
|
||||
|
let lagrange_interpolator =
|
||||
|
LagrangeInterpolator::new(domain.offset, domain.gen, domain.dim, poly_evaluations_val);
|
||||
|
self.lagrange_interpolator = Some(lagrange_interpolator)
|
||||
|
}
|
||||
|
|
||||
|
fn compute_lagrange_coefficients(
|
||||
|
&self,
|
||||
|
interpolation_point: &FpVar<F>,
|
||||
|
) -> Result<Vec<FpVar<F>>, SynthesisError> {
|
||||
|
// ref: https://github.com/alexchmit/perfect-constraints/blob/79692f2652a95a57f2c7187f5b5276345e680230/fractal/src/algebra/lagrange_interpolation.rs#L159
|
||||
|
let cs = interpolation_point.cs();
|
||||
|
let t = interpolation_point;
|
||||
|
let lagrange_interpolator = self
|
||||
|
.lagrange_interpolator
|
||||
|
.as_ref()
|
||||
|
.expect("lagrange interpolator has not been initialized. \
|
||||
|
Call `self.generate_lagrange_interpolator` first or set `interpolate` to true in constructor. ");
|
||||
|
let lagrange_coeffs =
|
||||
|
lagrange_interpolator.compute_lagrange_coefficients(t.value().unwrap());
|
||||
|
let mut lagrange_coeffs_fg = Vec::new();
|
||||
|
// Now we convert these lagrange coefficients to gadgets, and then constrain them.
|
||||
|
// The i-th lagrange coefficients constraint is:
|
||||
|
// (v_inv[i] * t - v_inv[i] * domain_elem[i]) * (coeff) = 1/Z_I(t)
|
||||
|
let vp_t = lagrange_interpolator.domain_vp.evaluate_constraints(t)?;
|
||||
|
// let inv_vp_t = vp_t.inverse()?;
|
||||
|
for i in 0..lagrange_interpolator.domain_order {
|
||||
|
let constant: F =
|
||||
|
(-lagrange_interpolator.all_domain_elems[i]) * lagrange_interpolator.v_inv_elems[i];
|
||||
|
let mut a_element: FpVar<F> =
|
||||
|
t * &FpVar::constant(lagrange_interpolator.v_inv_elems[i]);
|
||||
|
a_element += FpVar::constant(constant);
|
||||
|
|
||||
|
let lag_coeff: FpVar<F> =
|
||||
|
FpVar::new_witness(ns!(cs, "generate lagrange coefficient"), || {
|
||||
|
Ok(lagrange_coeffs[i])
|
||||
|
})?;
|
||||
|
// Enforce the actual constraint (A_element) * (lagrange_coeff) = 1/Z_I(t)
|
||||
|
assert_eq!(
|
||||
|
(lagrange_interpolator.v_inv_elems[i] * t.value().unwrap()
|
||||
|
- lagrange_interpolator.v_inv_elems[i]
|
||||
|
* lagrange_interpolator.all_domain_elems[i])
|
||||
|
* lagrange_coeffs[i],
|
||||
|
vp_t.value().unwrap()
|
||||
|
);
|
||||
|
a_element.mul_equals(&lag_coeff, &vp_t)?;
|
||||
|
lagrange_coeffs_fg.push(lag_coeff);
|
||||
|
}
|
||||
|
Ok(lagrange_coeffs_fg)
|
||||
|
}
|
||||
|
|
||||
|
/// Returns constraints for Interpolating and evaluating at `interpolation_point`
|
||||
|
pub fn interpolate_and_evaluate(
|
||||
|
&self,
|
||||
|
interpolation_point: &FpVar<F>,
|
||||
|
) -> Result<FpVar<F>, SynthesisError> {
|
||||
|
let lagrange_interpolator = self
|
||||
|
.lagrange_interpolator
|
||||
|
.as_ref()
|
||||
|
.expect("lagrange interpolator has not been initialized. ");
|
||||
|
let lagrange_coeffs = self.compute_lagrange_coefficients(interpolation_point)?;
|
||||
|
let mut interpolation: FpVar<F> = FpVar::zero();
|
||||
|
for i in 0..lagrange_interpolator.domain_order {
|
||||
|
let intermediate = &lagrange_coeffs[i] * &self.evals[i];
|
||||
|
interpolation += &intermediate
|
||||
|
}
|
||||
|
|
||||
|
Ok(interpolation)
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
impl<'a, 'b, F: PrimeField> Add<&'a EvaluationsVar<F>> for &'b EvaluationsVar<F> {
|
||||
|
type Output = EvaluationsVar<F>;
|
||||
|
|
||||
|
fn add(self, rhs: &'a EvaluationsVar<F>) -> Self::Output {
|
||||
|
let mut result = self.clone();
|
||||
|
result += rhs;
|
||||
|
result
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
impl<'a, F: PrimeField> AddAssign<&'a EvaluationsVar<F>> for EvaluationsVar<F> {
|
||||
|
fn add_assign(&mut self, other: &'a EvaluationsVar<F>) {
|
||||
|
assert_eq!(self.domain, other.domain, "domains are unequal");
|
||||
|
|
||||
|
self.lagrange_interpolator = None;
|
||||
|
self.evals
|
||||
|
.iter_mut()
|
||||
|
.zip(&other.evals)
|
||||
|
.for_each(|(a, b)| *a = &*a + b)
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
impl<'a, 'b, F: PrimeField> Sub<&'a EvaluationsVar<F>> for &'b EvaluationsVar<F> {
|
||||
|
type Output = EvaluationsVar<F>;
|
||||
|
|
||||
|
fn sub(self, rhs: &'a EvaluationsVar<F>) -> Self::Output {
|
||||
|
let mut result = self.clone();
|
||||
|
result -= rhs;
|
||||
|
result
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
impl<'a, F: PrimeField> SubAssign<&'a EvaluationsVar<F>> for EvaluationsVar<F> {
|
||||
|
fn sub_assign(&mut self, other: &'a EvaluationsVar<F>) {
|
||||
|
assert_eq!(self.domain, other.domain, "domains are unequal");
|
||||
|
|
||||
|
self.lagrange_interpolator = None;
|
||||
|
self.evals
|
||||
|
.iter_mut()
|
||||
|
.zip(&other.evals)
|
||||
|
.for_each(|(a, b)| *a = &*a - b)
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
impl<'a, 'b, F: PrimeField> Mul<&'a EvaluationsVar<F>> for &'b EvaluationsVar<F> {
|
||||
|
type Output = EvaluationsVar<F>;
|
||||
|
|
||||
|
fn mul(self, rhs: &'a EvaluationsVar<F>) -> Self::Output {
|
||||
|
let mut result = self.clone();
|
||||
|
result *= rhs;
|
||||
|
result
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
impl<'a, F: PrimeField> MulAssign<&'a EvaluationsVar<F>> for EvaluationsVar<F> {
|
||||
|
fn mul_assign(&mut self, other: &'a EvaluationsVar<F>) {
|
||||
|
assert_eq!(self.domain, other.domain, "domains are unequal");
|
||||
|
|
||||
|
self.lagrange_interpolator = None;
|
||||
|
self.evals
|
||||
|
.iter_mut()
|
||||
|
.zip(&other.evals)
|
||||
|
.for_each(|(a, b)| *a = &*a * b)
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
impl<'a, 'b, F: PrimeField> Div<&'a EvaluationsVar<F>> for &'b EvaluationsVar<F> {
|
||||
|
type Output = EvaluationsVar<F>;
|
||||
|
|
||||
|
fn div(self, rhs: &'a EvaluationsVar<F>) -> Self::Output {
|
||||
|
let mut result = self.clone();
|
||||
|
result /= rhs;
|
||||
|
result
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
impl<'a, F: PrimeField> DivAssign<&'a EvaluationsVar<F>> for EvaluationsVar<F> {
|
||||
|
fn div_assign(&mut self, other: &'a EvaluationsVar<F>) {
|
||||
|
assert_eq!(self.domain, other.domain, "domains are unequal");
|
||||
|
let cs = self.evals[0].cs();
|
||||
|
// the prover can generate result = (1 / other) * self offline
|
||||
|
let mut result_val: Vec<_> = other.evals.iter().map(|x| x.value().unwrap()).collect();
|
||||
|
batch_inversion(&mut result_val);
|
||||
|
result_val
|
||||
|
.iter_mut()
|
||||
|
.zip(&self.evals)
|
||||
|
.for_each(|(a, self_var)| *a *= self_var.value().unwrap());
|
||||
|
let result_var: Vec<_> = result_val
|
||||
|
.iter()
|
||||
|
.map(|x| FpVar::new_witness(ns!(cs, "div result"), || Ok(*x)).unwrap())
|
||||
|
.collect();
|
||||
|
// enforce constraint
|
||||
|
for i in 0..result_var.len() {
|
||||
|
result_var[i]
|
||||
|
.mul_equals(&other.evals[i], &self.evals[i])
|
||||
|
.unwrap();
|
||||
|
}
|
||||
|
|
||||
|
self.lagrange_interpolator = None;
|
||||
|
self.evals = result_var
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
#[cfg(test)]
|
||||
|
mod tests {
|
||||
|
use crate::alloc::AllocVar;
|
||||
|
use crate::fields::fp::FpVar;
|
||||
|
use crate::poly::domain::Radix2Domain;
|
||||
|
use crate::poly::evaluations::univariate::EvaluationsVar;
|
||||
|
use crate::R1CSVar;
|
||||
|
use ark_ff::{FftField, Field, One, UniformRand};
|
||||
|
use ark_poly::polynomial::univariate::DensePolynomial;
|
||||
|
use ark_poly::{Polynomial, UVPolynomial};
|
||||
|
use ark_relations::r1cs::ConstraintSystem;
|
||||
|
use ark_std::test_rng;
|
||||
|
use ark_test_curves::bls12_381::Fr;
|
||||
|
|
||||
|
#[test]
|
||||
|
fn test_interpolate() {
|
||||
|
let mut rng = test_rng();
|
||||
|
let poly = DensePolynomial::rand(15, &mut rng);
|
||||
|
let gen = Fr::get_root_of_unity(1 << 4).unwrap();
|
||||
|
assert_eq!(gen.pow(&[1 << 4]), Fr::one());
|
||||
|
let domain = Radix2Domain {
|
||||
|
gen,
|
||||
|
offset: Fr::multiplicative_generator(),
|
||||
|
dim: 4, // 2^4 = 16
|
||||
|
};
|
||||
|
let mut coset_point = domain.offset;
|
||||
|
let mut oracle_evals = Vec::new();
|
||||
|
for _ in 0..(1 << 4) {
|
||||
|
oracle_evals.push(poly.evaluate(&coset_point));
|
||||
|
coset_point *= gen;
|
||||
|
}
|
||||
|
let cs = ConstraintSystem::new_ref();
|
||||
|
let evaluations_fp: Vec<_> = oracle_evals
|
||||
|
.iter()
|
||||
|
.map(|x| FpVar::new_input(ns!(cs, "evaluations"), || Ok(x)).unwrap())
|
||||
|
.collect();
|
||||
|
let evaluations_var = EvaluationsVar::from_vec_and_domain(evaluations_fp, domain, true);
|
||||
|
|
||||
|
let interpolate_point = Fr::rand(&mut rng);
|
||||
|
let interpolate_point_fp =
|
||||
|
FpVar::new_input(ns!(cs, "interpolate point"), || Ok(interpolate_point)).unwrap();
|
||||
|
|
||||
|
let expected = poly.evaluate(&interpolate_point);
|
||||
|
|
||||
|
let actual = evaluations_var
|
||||
|
.interpolate_and_evaluate(&interpolate_point_fp)
|
||||
|
.unwrap()
|
||||
|
.value()
|
||||
|
.unwrap();
|
||||
|
|
||||
|
assert_eq!(actual, expected);
|
||||
|
assert!(cs.is_satisfied().unwrap());
|
||||
|
}
|
||||
|
|
||||
|
#[test]
|
||||
|
fn test_division() {
|
||||
|
let mut rng = test_rng();
|
||||
|
let gen = Fr::get_root_of_unity(1 << 4).unwrap();
|
||||
|
assert_eq!(gen.pow(&[1 << 4]), Fr::one());
|
||||
|
let domain = Radix2Domain {
|
||||
|
gen,
|
||||
|
offset: Fr::multiplicative_generator(),
|
||||
|
dim: 4, // 2^4 = 16
|
||||
|
};
|
||||
|
|
||||
|
let cs = ConstraintSystem::new_ref();
|
||||
|
|
||||
|
let ev_a = EvaluationsVar::from_vec_and_domain(
|
||||
|
(0..16)
|
||||
|
.map(|_| FpVar::new_input(ns!(cs, "poly_a"), || Ok(Fr::rand(&mut rng))).unwrap())
|
||||
|
.collect(),
|
||||
|
domain.clone(),
|
||||
|
false,
|
||||
|
);
|
||||
|
let ev_b = EvaluationsVar::from_vec_and_domain(
|
||||
|
(0..16)
|
||||
|
.map(|_| FpVar::new_input(ns!(cs, "poly_a"), || Ok(Fr::rand(&mut rng))).unwrap())
|
||||
|
.collect(),
|
||||
|
domain.clone(),
|
||||
|
false,
|
||||
|
);
|
||||
|
|
||||
|
let a_div_b = (&ev_a) / (&ev_b);
|
||||
|
assert!(cs.is_satisfied().unwrap());
|
||||
|
let b_div_a = (&ev_b) / (&ev_a);
|
||||
|
|
||||
|
let one = &a_div_b * &b_div_a;
|
||||
|
for ev in one.evals.iter() {
|
||||
|
assert!(Fr::is_one(&ev.value().unwrap()))
|
||||
|
}
|
||||
|
|
||||
|
assert!(cs.is_satisfied().unwrap());
|
||||
|
}
|
||||
|
}
|
@ -1,2 +1,4 @@ |
|||||
|
pub mod domain;
|
||||
|
pub mod evaluations;
|
||||
/// Modules for working with polynomials in coefficient forms.
|
/// Modules for working with polynomials in coefficient forms.
|
||||
pub mod polynomial;
|
pub mod polynomial;
|