@ -3,7 +3,7 @@ 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 ::domain ::Radix2DomainVar ;
use crate ::poly ::evaluations ::univariate ::lagrange_interpolator ::LagrangeInterpolator ;
use crate ::R1CSVar ;
use ark_ff ::{ batch_inversion , PrimeField } ;
@ -18,7 +18,12 @@ pub struct EvaluationsVar {
pub evals : Vec < FpVar < F > > ,
/// Optional Lagrange Interpolator. Useful for lagrange interpolation.
pub lagrange_interpolator : Option < LagrangeInterpolator < F > > ,
domain : Radix2Domain < F > ,
domain : Radix2DomainVar < F > ,
/// Contains all domain elements of `domain.base_domain`.
///
/// This is a cache for lagrange interpolation when offset is non-constant. Will be `None` if offset is constant
/// or `interpolate` is set to `false`.
subgroup_points : Option < Vec < F > > ,
}
impl < F : PrimeField > EvaluationsVar < F > {
@ -27,7 +32,7 @@ impl EvaluationsVar {
/// using lagrange interpolation.
pub fn from_vec_and_domain (
evaluations : Vec < FpVar < F > > ,
domain : Radix2Domain < F > ,
domain : Radix2DomainVar < F > ,
interpolate : bool ,
) -> Self {
assert_eq ! (
@ -40,22 +45,39 @@ impl EvaluationsVar {
evals : evaluations ,
lagrange_interpolator : None ,
domain ,
subgroup_points : None ,
} ;
if interpolate {
ev . generate_lagrange_interpolator ( ) ;
ev . generate_interpolation_cache ( ) ;
}
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 )
/// Precompute necessary calculation for lagrange interpolation and mark it ready to interpolate
pub fn generate_interpolation_cache ( & mut self ) {
if self . domain . offset . is_constant ( ) {
let poly_evaluations_val : Vec < _ > =
self . evals . iter ( ) . map ( | v | v . value ( ) . unwrap ( ) ) . collect ( ) ;
let domain = & self . domain ;
let lagrange_interpolator = if let FpVar ::Constant ( x ) = domain . offset {
LagrangeInterpolator ::new ( x , domain . gen , domain . dim , poly_evaluations_val )
} else {
panic ! ( "Domain offset needs to be constant." )
} ;
self . lagrange_interpolator = Some ( lagrange_interpolator )
} else {
// calculate all elements of base subgroup so that in later part we don't need to calculate the exponents again
let mut subgroup_points = Vec ::with_capacity ( self . domain . size ( ) as usize ) ;
subgroup_points . push ( F ::one ( ) ) ;
for i in 1 . . self . domain . size ( ) as usize {
subgroup_points . push ( subgroup_points [ i - 1 ] * self . domain . gen )
}
self . subgroup_points = Some ( subgroup_points )
}
}
/// Compute lagrange coefficients for each evaluation, given `interpolation_point`.
/// Only valid if the domain offset is constant.
fn compute_lagrange_coefficients (
& self ,
interpolation_point : & FpVar < F > ,
@ -67,7 +89,7 @@ impl EvaluationsVar {
. lagrange_interpolator
. as_ref ( )
. expect ( " lagrange interpolator has not been initialized . \
Call ` self . generate_lagrange_interpolator ` first or set ` interpolate ` to true in constructor . " ) ;
Call ` self . generate_interpolation_cache ` 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 ( ) ;
@ -105,6 +127,19 @@ impl EvaluationsVar {
pub fn interpolate_and_evaluate (
& self ,
interpolation_point : & FpVar < F > ,
) -> Result < FpVar < F > , SynthesisError > {
// specialize: if domain offset is constant, we can optimize to have fewer constraints
if self . domain . offset . is_constant ( ) {
self . lagrange_interpolate_with_constant_offset ( interpolation_point )
} else {
// if domain offset is not constant, then we use standard lagrange interpolation code
self . lagrange_interpolate_with_non_constant_offset ( interpolation_point )
}
}
fn lagrange_interpolate_with_constant_offset (
& self ,
interpolation_point : & FpVar < F > ,
) -> Result < FpVar < F > , SynthesisError > {
let lagrange_interpolator = self
. lagrange_interpolator
@ -119,6 +154,50 @@ impl EvaluationsVar {
Ok ( interpolation )
}
/// Generate interpolation constraints. We assume at compile time we know the base coset (i.e. `gen`) but not know `offset`.
fn lagrange_interpolate_with_non_constant_offset (
& self ,
interpolation_point : & FpVar < F > ,
) -> Result < FpVar < F > , SynthesisError > {
// first, make sure `subgroup_points` is made
let subgroup_points = self . subgroup_points . as_ref ( )
. expect ( " lagrange interpolator has not been initialized . \
Call ` self . generate_interpolation_cache ` first or set ` interpolate ` to true in constructor . " ) ;
// Let denote interpolation_point as alpha.
// Lagrange polynomial for coset element `a` is
// \frac{1}{size * offset ^ size} * \frac{alpha^size - offset^size}{alpha * a^{-1} - 1}
// Notice that a = (offset * a') where a' is the corresponding element of base coset
// let `lhs` become \frac{alpha^size - offset^size}{size * offset ^ size}. This part is shared by all lagrange polynomials
let coset_offset_to_size = self . domain . offset . pow_by_constant ( & [ self . domain . size ( ) ] ) ? ; // offset^size
let alpha_to_s = interpolation_point . pow_by_constant ( & [ self . domain . size ( ) ] ) ? ;
let lhs_numerator = & alpha_to_s - & coset_offset_to_size ;
let lhs_denominator = & coset_offset_to_size * FpVar ::constant ( F ::from ( self . domain . size ( ) ) ) ;
let lhs = lhs_numerator . mul_by_inverse ( & lhs_denominator ) ? ;
// `rhs` for coset element `a` is \frac{1}{alpha * a^{-1} - 1} = \frac{1}{alpha * offset^{-1} * a'^{-1} - 1}
let alpha_coset_offset_inv = interpolation_point . mul_by_inverse ( & self . domain . offset ) ? ;
// `res` stores the sum of all lagrange polynomials evaluated at alpha
let mut res = FpVar ::< F > ::zero ( ) ;
let domain_size = self . domain . size ( ) as usize ;
for i in 0 . . domain_size {
// a'^{-1} where a is the base coset element
let subgroup_point_inv = subgroup_points [ ( domain_size - i ) % domain_size ] ;
debug_assert_eq ! ( subgroup_points [ i ] * subgroup_point_inv , F ::one ( ) ) ;
// alpha * offset^{-1} * a'^{-1} - 1
let lag_donom = & alpha_coset_offset_inv * subgroup_point_inv - F ::one ( ) ;
let lag_coeff = lhs . mul_by_inverse ( & lag_donom ) ? ;
let lag_interpoland = & self . evals [ i ] * lag_coeff ;
res + = lag_interpoland
}
Ok ( res )
}
}
impl < 'a , 'b , F : PrimeField > Add < & 'a EvaluationsVar < F > > for & 'b EvaluationsVar < F > {
@ -132,8 +211,13 @@ impl<'a, 'b, F: PrimeField> Add<&'a EvaluationsVar> for &'b EvaluationsVar
}
impl < 'a , F : PrimeField > AddAssign < & 'a EvaluationsVar < F > > for EvaluationsVar < F > {
/// Performs the `+=` operations, assuming `domain.offset` is equal.
fn add_assign ( & mut self , other : & 'a EvaluationsVar < F > ) {
assert_eq ! ( self . domain , other . domain , "domains are unequal" ) ;
// offset might be unknown at compile time, so we assume offset is equal
assert ! (
self . domain . gen = = other . domain . gen & & self . domain . dim = = other . domain . dim ,
"domains are unequal"
) ;
self . lagrange_interpolator = None ;
self . evals
@ -154,8 +238,13 @@ impl<'a, 'b, F: PrimeField> Sub<&'a EvaluationsVar> for &'b EvaluationsVar
}
impl < 'a , F : PrimeField > SubAssign < & 'a EvaluationsVar < F > > for EvaluationsVar < F > {
/// Performs the `-=` operations, assuming `domain.offset` is equal.
fn sub_assign ( & mut self , other : & 'a EvaluationsVar < F > ) {
assert_eq ! ( self . domain , other . domain , "domains are unequal" ) ;
// offset might be unknown at compile time, so we assume offset is equal
assert ! (
self . domain . gen = = other . domain . gen & & self . domain . dim = = other . domain . dim ,
"domains are unequal"
) ;
self . lagrange_interpolator = None ;
self . evals
@ -168,6 +257,7 @@ impl<'a, F: PrimeField> SubAssign<&'a EvaluationsVar> for EvaluationsVar {
impl < 'a , 'b , F : PrimeField > Mul < & 'a EvaluationsVar < F > > for & 'b EvaluationsVar < F > {
type Output = EvaluationsVar < F > ;
/// Performs the `*` operations, assuming `domain.offset` is equal.
fn mul ( self , rhs : & 'a EvaluationsVar < F > ) -> Self ::Output {
let mut result = self . clone ( ) ;
result * = rhs ;
@ -176,8 +266,13 @@ impl<'a, 'b, F: PrimeField> Mul<&'a EvaluationsVar> for &'b EvaluationsVar
}
impl < 'a , F : PrimeField > MulAssign < & 'a EvaluationsVar < F > > for EvaluationsVar < F > {
/// Performs the `*=` operations, assuming `domain.offset` is equal.
fn mul_assign ( & mut self , other : & 'a EvaluationsVar < F > ) {
assert_eq ! ( self . domain , other . domain , "domains are unequal" ) ;
// offset might be unknown at compile time, so we assume offset is equal
assert ! (
self . domain . gen = = other . domain . gen & & self . domain . dim = = other . domain . dim ,
"domains are unequal"
) ;
self . lagrange_interpolator = None ;
self . evals
@ -198,8 +293,13 @@ impl<'a, 'b, F: PrimeField> Div<&'a EvaluationsVar> for &'b EvaluationsVar
}
impl < 'a , F : PrimeField > DivAssign < & 'a EvaluationsVar < F > > for EvaluationsVar < F > {
/// Performs the `/=` operations, assuming `domain.offset` is equal.
fn div_assign ( & mut self , other : & 'a EvaluationsVar < F > ) {
assert_eq ! ( self . domain , other . domain , "domains are unequal" ) ;
// offset might be unknown at compile time, so we assume offset is equal
assert ! (
self . domain . gen = = other . domain . gen & & self . domain . dim = = other . domain . dim ,
"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 ( ) ;
@ -228,7 +328,8 @@ impl<'a, F: PrimeField> DivAssign<&'a EvaluationsVar> for EvaluationsVar {
mod tests {
use crate ::alloc ::AllocVar ;
use crate ::fields ::fp ::FpVar ;
use crate ::poly ::domain ::Radix2Domain ;
use crate ::fields ::FieldVar ;
use crate ::poly ::domain ::Radix2DomainVar ;
use crate ::poly ::evaluations ::univariate ::EvaluationsVar ;
use crate ::R1CSVar ;
use ark_ff ::{ FftField , Field , One , UniformRand } ;
@ -239,17 +340,17 @@ mod tests {
use ark_test_curves ::bls12_381 ::Fr ;
#[ test ]
fn test_interpolate ( ) {
fn test_interpolate_constant_offset ( ) {
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 {
let domain = Radix2DomainVar {
gen ,
offset : Fr ::multiplicative_generator ( ) ,
offset : FpVar ::constant ( Fr ::rand ( & mut rng ) ) ,
dim : 4 , // 2^4 = 16
} ;
let mut coset_point = domain . offset ;
let mut coset_point = domain . offset . value ( ) . unwrap ( ) ;
let mut oracle_evals = Vec ::new ( ) ;
for _ in 0 . . ( 1 < < 4 ) {
oracle_evals . push ( poly . evaluate ( & coset_point ) ) ;
@ -276,6 +377,49 @@ mod tests {
assert_eq ! ( actual , expected ) ;
assert ! ( cs . is_satisfied ( ) . unwrap ( ) ) ;
println ! ( "number of constraints: {}" , cs . num_constraints ( ) )
}
#[ test ]
fn test_interpolate_non_constant_offset ( ) {
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 cs = ConstraintSystem ::new_ref ( ) ;
let domain = Radix2DomainVar {
gen ,
offset : FpVar ::new_witness ( ns ! ( cs , "offset" ) , | | Ok ( Fr ::rand ( & mut rng ) ) ) . unwrap ( ) ,
dim : 4 , // 2^4 = 16
} ;
let mut coset_point = domain . offset . value ( ) . unwrap ( ) ;
let mut oracle_evals = Vec ::new ( ) ;
for _ in 0 . . ( 1 < < 4 ) {
oracle_evals . push ( poly . evaluate ( & coset_point ) ) ;
coset_point * = gen ;
}
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 ( ) ) ;
println ! ( "number of constraints: {}" , cs . num_constraints ( ) )
}
#[ test ]
@ -283,9 +427,9 @@ mod tests {
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 {
let domain = Radix2DomainVar {
gen ,
offset : Fr ::multiplicative_generator ( ) ,
offset : FpVar ::constant ( F r ::multiplicative_generator ( ) ) ,
dim : 4 , // 2^4 = 16
} ;