ark_r1cs_std/poly/evaluations/univariate/
mod.rs

1pub mod lagrange_interpolator;
2
3use crate::{
4    alloc::AllocVar,
5    eq::EqGadget,
6    fields::{fp::FpVar, FieldVar},
7    poly::{
8        domain::Radix2DomainVar,
9        evaluations::univariate::lagrange_interpolator::LagrangeInterpolator,
10    },
11    R1CSVar,
12};
13use ark_ff::{batch_inversion, PrimeField};
14use ark_relations::r1cs::SynthesisError;
15use ark_std::{
16    ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Sub, SubAssign},
17    vec::Vec,
18};
19
20#[derive(Clone)]
21/// Stores a UV polynomial in evaluation form.
22pub struct EvaluationsVar<F: PrimeField> {
23    /// Evaluations of univariate polynomial over domain
24    pub evals: Vec<FpVar<F>>,
25    /// Optional Lagrange Interpolator. Useful for lagrange interpolation.
26    pub lagrange_interpolator: Option<LagrangeInterpolator<F>>,
27    domain: Radix2DomainVar<F>,
28    /// Contains all domain elements of `domain.base_domain`.
29    ///
30    /// This is a cache for lagrange interpolation when offset is non-constant.
31    /// Will be `None` if offset is constant or `interpolate` is set to
32    /// `false`.
33    subgroup_points: Option<Vec<F>>,
34}
35
36impl<F: PrimeField> EvaluationsVar<F> {
37    /// Construct `Self` from evaluations and a domain.
38    /// `interpolate` indicates if user wants to interpolate this polynomial
39    /// using lagrange interpolation.
40    pub fn from_vec_and_domain(
41        evaluations: Vec<FpVar<F>>,
42        domain: Radix2DomainVar<F>,
43        interpolate: bool,
44    ) -> Self {
45        assert_eq!(
46            evaluations.len(),
47            1 << domain.dim,
48            "evaluations and domain has different dimensions"
49        );
50
51        let mut ev = Self {
52            evals: evaluations,
53            lagrange_interpolator: None,
54            domain,
55            subgroup_points: None,
56        };
57        if interpolate {
58            ev.generate_interpolation_cache();
59        }
60        ev
61    }
62
63    /// Precompute necessary calculation for lagrange interpolation and mark it
64    /// ready to interpolate
65    pub fn generate_interpolation_cache(&mut self) {
66        if self.domain.offset().is_constant() {
67            let poly_evaluations_val: Vec<_> = self
68                .evals
69                .iter()
70                .map(|v| v.value().unwrap_or_default())
71                .collect();
72            let domain = &self.domain;
73            let lagrange_interpolator = if let &FpVar::Constant(x) = domain.offset() {
74                LagrangeInterpolator::new(x, domain.gen, domain.dim, poly_evaluations_val)
75            } else {
76                panic!("Domain offset needs to be constant.")
77            };
78            self.lagrange_interpolator = Some(lagrange_interpolator)
79        } else {
80            // calculate all elements of base subgroup so that in later part we don't need
81            // to calculate the exponents again
82            let mut subgroup_points = Vec::with_capacity(self.domain.size() as usize);
83            subgroup_points.push(F::one());
84            for i in 1..self.domain.size() as usize {
85                subgroup_points.push(subgroup_points[i - 1] * self.domain.gen)
86            }
87            self.subgroup_points = Some(subgroup_points)
88        }
89    }
90
91    /// Compute lagrange coefficients for each evaluation, given
92    /// `interpolation_point`. Only valid if the domain offset is constant.
93    fn compute_lagrange_coefficients(
94        &self,
95        interpolation_point: &FpVar<F>,
96    ) -> Result<Vec<FpVar<F>>, SynthesisError> {
97        // ref: https://github.com/alexchmit/perfect-constraints/blob/79692f2652a95a57f2c7187f5b5276345e680230/fractal/src/algebra/lagrange_interpolation.rs#L159
98        let cs = interpolation_point.cs();
99        let t = interpolation_point;
100        let lagrange_interpolator = self
101            .lagrange_interpolator
102            .as_ref()
103            .expect("lagrange interpolator has not been initialized. \
104            Call `self.generate_interpolation_cache` first or set `interpolate` to true in constructor. ");
105        let lagrange_coeffs =
106            lagrange_interpolator.compute_lagrange_coefficients(t.value().unwrap_or_default());
107        let mut lagrange_coeffs_fg = Vec::new();
108        // Now we convert these lagrange coefficients to gadgets, and then constrain
109        // them. The i-th lagrange coefficients constraint is:
110        // (v_inv[i] * t - v_inv[i] * domain_elem[i]) * (coeff) = 1/Z_I(t)
111        let vp_t = lagrange_interpolator.domain_vp.evaluate_constraints(t)?;
112        // let inv_vp_t = vp_t.inverse()?;
113        for i in 0..lagrange_interpolator.domain_order {
114            let constant: F =
115                (-lagrange_interpolator.all_domain_elems[i]) * lagrange_interpolator.v_inv_elems[i];
116            let mut a_element: FpVar<F> =
117                t * &FpVar::constant(lagrange_interpolator.v_inv_elems[i]);
118            a_element += FpVar::constant(constant);
119
120            let lag_coeff: FpVar<F> =
121                FpVar::new_witness(ns!(cs, "generate lagrange coefficient"), || {
122                    Ok(lagrange_coeffs[i])
123                })?;
124            // Enforce the actual constraint (A_element) * (lagrange_coeff) = 1/Z_I(t)
125            if !cs.is_in_setup_mode() {
126                assert_eq!(
127                    (lagrange_interpolator.v_inv_elems[i] * t.value().unwrap_or_default()
128                        - lagrange_interpolator.v_inv_elems[i]
129                            * lagrange_interpolator.all_domain_elems[i])
130                        * lagrange_coeffs[i],
131                    vp_t.value().unwrap_or_default()
132                );
133            }
134            a_element.mul_equals(&lag_coeff, &vp_t)?;
135            lagrange_coeffs_fg.push(lag_coeff);
136        }
137        Ok(lagrange_coeffs_fg)
138    }
139
140    /// Returns constraints for Interpolating and evaluating at
141    /// `interpolation_point`
142    pub fn interpolate_and_evaluate(
143        &self,
144        interpolation_point: &FpVar<F>,
145    ) -> Result<FpVar<F>, SynthesisError> {
146        // specialize: if domain offset is constant, we can optimize to have fewer
147        // constraints
148        if self.domain.offset().is_constant() {
149            self.lagrange_interpolate_with_constant_offset(interpolation_point)
150        } else {
151            // if domain offset is not constant, then we use standard lagrange interpolation
152            // code
153            self.lagrange_interpolate_with_non_constant_offset(interpolation_point)
154        }
155    }
156
157    fn lagrange_interpolate_with_constant_offset(
158        &self,
159        interpolation_point: &FpVar<F>,
160    ) -> Result<FpVar<F>, SynthesisError> {
161        let lagrange_interpolator = self
162            .lagrange_interpolator
163            .as_ref()
164            .expect("lagrange interpolator has not been initialized. ");
165        let lagrange_coeffs = self.compute_lagrange_coefficients(interpolation_point)?;
166
167        let interpolation = lagrange_coeffs
168            .iter()
169            .zip(&self.evals)
170            .take(lagrange_interpolator.domain_order)
171            .map(|(coeff, eval)| coeff * eval)
172            .sum::<FpVar<F>>();
173
174        Ok(interpolation)
175    }
176
177    /// Generate interpolation constraints. We assume at compile time we know
178    /// the base coset (i.e. `gen`) but not know `offset`.
179    fn lagrange_interpolate_with_non_constant_offset(
180        &self,
181        interpolation_point: &FpVar<F>, // = alpha in the following code
182    ) -> Result<FpVar<F>, SynthesisError> {
183        // first, make sure `subgroup_points` is made
184        let subgroup_points = self.subgroup_points.as_ref()
185            .expect("lagrange interpolator has not been initialized. \
186            Call `self.generate_interpolation_cache` first or set `interpolate` to true in constructor. ");
187        // Let denote interpolation_point as alpha.
188        // Lagrange polynomial for coset element `a` is
189        // \frac{1}{size * offset ^ size} * \frac{alpha^size - offset^size}{alpha *
190        // a^{-1} - 1} Notice that a = (offset * a') where a' is the
191        // corresponding element of base coset
192
193        // let `lhs` be \frac{alpha^size - offset^size}{size * offset ^ size}. This part
194        // is shared by all lagrange polynomials
195        let coset_offset_to_size = self
196            .domain
197            .offset()
198            .pow_by_constant(&[self.domain.size()])?; // offset^size
199        let alpha_to_size = interpolation_point.pow_by_constant(&[self.domain.size()])?;
200        let lhs_numerator = &alpha_to_size - &coset_offset_to_size;
201        // This enforces that `alpha` is not in the coset.
202        // This also means that the denominator is
203        lhs_numerator.enforce_not_equal(&FpVar::zero())?;
204
205        // `domain.offset()` is non-zero by construction, so `coset_offset_to_size` is
206        // also non-zero, which means `lhs_denominator` is non-zero
207        let lhs_denominator = &coset_offset_to_size * FpVar::constant(F::from(self.domain.size()));
208
209        // unchecked is okay because the denominator is non-zero.
210        let lhs = lhs_numerator.mul_by_inverse_unchecked(&lhs_denominator)?;
211
212        // `rhs` for coset element `a` is \frac{1}{alpha * a^{-1} - 1} = \frac{1}{alpha
213        // * offset^{-1} * a'^{-1} - 1} domain.offset() is non-zero by
214        // construction.
215        let alpha_coset_offset_inv =
216            interpolation_point.mul_by_inverse_unchecked(&self.domain.offset())?;
217
218        let domain_size = self.domain.size() as usize;
219
220        // `evals` stores all lagrange polynomials evaluated at alpha
221        let evals = (0..domain_size)
222            .map(|i| {
223                // a'^{-1} where a is the base coset element
224                let subgroup_point_inv = subgroup_points[(domain_size - i) % domain_size];
225                debug_assert_eq!(subgroup_points[i] * subgroup_point_inv, F::one());
226                // alpha * offset^{-1} * a'^{-1} - 1
227                let lag_denom = &alpha_coset_offset_inv * subgroup_point_inv - F::one();
228                // lag_denom cannot be zero, so we use `unchecked`.
229                //
230                // Proof: lag_denom is zero if and only if alpha * (coset_offset *
231                // subgroup_point)^{-1} == 1. This can happen only if `alpha` is
232                // itself in the coset.
233                //
234                // Earlier we asserted that `lhs_numerator` is not zero.
235                // Since `lhs_numerator` is just the vanishing polynomial for the coset
236                // evaluated at `alpha`, and since this is non-zero, `alpha` is not
237                // in the coset.
238                let lag_coeff = lhs.mul_by_inverse_unchecked(&lag_denom)?;
239
240                Ok(&self.evals[i] * lag_coeff)
241            })
242            .collect::<Result<Vec<_>, _>>()?;
243
244        let res = evals.iter().sum();
245
246        Ok(res)
247    }
248}
249
250impl<'a, 'b, F: PrimeField> Add<&'a EvaluationsVar<F>> for &'b EvaluationsVar<F> {
251    type Output = EvaluationsVar<F>;
252
253    fn add(self, rhs: &'a EvaluationsVar<F>) -> Self::Output {
254        let mut result = self.clone();
255        result += rhs;
256        result
257    }
258}
259
260impl<'a, F: PrimeField> AddAssign<&'a EvaluationsVar<F>> for EvaluationsVar<F> {
261    /// Performs the `+=` operations, assuming `domain.offset` is equal.
262    fn add_assign(&mut self, other: &'a EvaluationsVar<F>) {
263        // offset might be unknown at compile time, so we assume offset is equal
264        assert!(
265            self.domain.gen == other.domain.gen && self.domain.dim == other.domain.dim,
266            "domains are unequal"
267        );
268
269        self.lagrange_interpolator = None;
270        self.evals
271            .iter_mut()
272            .zip(&other.evals)
273            .for_each(|(a, b)| *a = &*a + b)
274    }
275}
276
277impl<'a, 'b, F: PrimeField> Sub<&'a EvaluationsVar<F>> for &'b EvaluationsVar<F> {
278    type Output = EvaluationsVar<F>;
279
280    fn sub(self, rhs: &'a EvaluationsVar<F>) -> Self::Output {
281        let mut result = self.clone();
282        result -= rhs;
283        result
284    }
285}
286
287impl<'a, F: PrimeField> SubAssign<&'a EvaluationsVar<F>> for EvaluationsVar<F> {
288    /// Performs the `-=` operations, assuming `domain.offset` is equal.
289    fn sub_assign(&mut self, other: &'a EvaluationsVar<F>) {
290        // offset might be unknown at compile time, so we assume offset is equal
291        assert!(
292            self.domain.gen == other.domain.gen && self.domain.dim == other.domain.dim,
293            "domains are unequal"
294        );
295
296        self.lagrange_interpolator = None;
297        self.evals
298            .iter_mut()
299            .zip(&other.evals)
300            .for_each(|(a, b)| *a = &*a - b)
301    }
302}
303
304impl<'a, 'b, F: PrimeField> Mul<&'a EvaluationsVar<F>> for &'b EvaluationsVar<F> {
305    type Output = EvaluationsVar<F>;
306
307    /// Performs the `*` operations, assuming `domain.offset` is equal.
308    fn mul(self, rhs: &'a EvaluationsVar<F>) -> Self::Output {
309        let mut result = self.clone();
310        result *= rhs;
311        result
312    }
313}
314
315impl<'a, F: PrimeField> MulAssign<&'a EvaluationsVar<F>> for EvaluationsVar<F> {
316    /// Performs the `*=` operations, assuming `domain.offset` is equal.
317    fn mul_assign(&mut self, other: &'a EvaluationsVar<F>) {
318        // offset might be unknown at compile time, so we assume offset is equal
319        assert!(
320            self.domain.gen == other.domain.gen && self.domain.dim == other.domain.dim,
321            "domains are unequal"
322        );
323
324        self.lagrange_interpolator = None;
325        self.evals
326            .iter_mut()
327            .zip(&other.evals)
328            .for_each(|(a, b)| *a = &*a * b)
329    }
330}
331
332impl<'a, 'b, F: PrimeField> Div<&'a EvaluationsVar<F>> for &'b EvaluationsVar<F> {
333    type Output = EvaluationsVar<F>;
334
335    fn div(self, rhs: &'a EvaluationsVar<F>) -> Self::Output {
336        let mut result = self.clone();
337        result /= rhs;
338        result
339    }
340}
341
342impl<'a, F: PrimeField> DivAssign<&'a EvaluationsVar<F>> for EvaluationsVar<F> {
343    /// Performs the `/=` operations, assuming `domain.offset` is equal.
344    fn div_assign(&mut self, other: &'a EvaluationsVar<F>) {
345        // offset might be unknown at compile time, so we assume offset is equal
346        assert!(
347            self.domain.gen == other.domain.gen && self.domain.dim == other.domain.dim,
348            "domains are unequal"
349        );
350        let cs = self.evals[0].cs();
351        // the prover can generate result = (1 / other) * self offline
352        let mut result_val: Vec<_> = other
353            .evals
354            .iter()
355            .map(|x| x.value().unwrap_or_default())
356            .collect();
357        batch_inversion(&mut result_val);
358        result_val
359            .iter_mut()
360            .zip(&self.evals)
361            .for_each(|(a, self_var)| *a *= self_var.value().unwrap_or_default());
362        let result_var: Vec<_> = result_val
363            .iter()
364            .map(|x| FpVar::new_witness(ns!(cs, "div result"), || Ok(*x)).unwrap())
365            .collect();
366        // enforce constraint
367        for i in 0..result_var.len() {
368            result_var[i]
369                .mul_equals(&other.evals[i], &self.evals[i])
370                .unwrap();
371        }
372
373        self.lagrange_interpolator = None;
374        self.evals = result_var
375    }
376}
377
378#[cfg(test)]
379mod tests {
380    use crate::{
381        alloc::AllocVar,
382        fields::{fp::FpVar, FieldVar},
383        poly::{domain::Radix2DomainVar, evaluations::univariate::EvaluationsVar},
384        R1CSVar,
385    };
386    use ark_ff::{FftField, Field, One, UniformRand};
387    use ark_poly::{polynomial::univariate::DensePolynomial, DenseUVPolynomial, Polynomial};
388    use ark_relations::r1cs::ConstraintSystem;
389    use ark_std::test_rng;
390    use ark_test_curves::bls12_381::Fr;
391
392    #[test]
393    fn test_interpolate_constant_offset() {
394        for n in [11, 12, 13, 14] {
395            let mut rng = test_rng();
396
397            let poly = DensePolynomial::rand((1 << n) - 1, &mut rng);
398            let gen = Fr::get_root_of_unity(1 << n).unwrap();
399            assert_eq!(gen.pow(&[1 << n]), Fr::one());
400            let domain = Radix2DomainVar::new(gen, n, FpVar::constant(Fr::rand(&mut rng))).unwrap();
401            let mut coset_point = domain.offset().value().unwrap();
402            let mut oracle_evals = Vec::new();
403            for _ in 0..(1 << n) {
404                oracle_evals.push(poly.evaluate(&coset_point));
405                coset_point *= gen;
406            }
407            let cs = ConstraintSystem::new_ref();
408            let evaluations_fp: Vec<_> = oracle_evals
409                .iter()
410                .map(|x| FpVar::new_input(ns!(cs, "evaluations"), || Ok(x)).unwrap())
411                .collect();
412            let evaluations_var = EvaluationsVar::from_vec_and_domain(evaluations_fp, domain, true);
413
414            let interpolate_point = Fr::rand(&mut rng);
415            let interpolate_point_fp =
416                FpVar::new_input(ns!(cs, "interpolate point"), || Ok(interpolate_point)).unwrap();
417
418            let expected = poly.evaluate(&interpolate_point);
419
420            let actual = evaluations_var
421                .interpolate_and_evaluate(&interpolate_point_fp)
422                .unwrap()
423                .value()
424                .unwrap();
425
426            assert_eq!(actual, expected);
427            assert!(cs.is_satisfied().unwrap());
428            println!("number of constraints: {}", cs.num_constraints());
429        }
430    }
431
432    #[test]
433    fn test_interpolate_non_constant_offset() {
434        for n in [11, 12, 13, 14] {
435            let mut rng = test_rng();
436            let poly = DensePolynomial::rand((1 << n) - 1, &mut rng);
437            let gen = Fr::get_root_of_unity(1 << n).unwrap();
438            assert_eq!(gen.pow(&[1 << n]), Fr::one());
439            let cs = ConstraintSystem::new_ref();
440            let domain = Radix2DomainVar::new(
441                gen,
442                n,
443                FpVar::new_witness(ns!(cs, "offset"), || Ok(Fr::rand(&mut rng))).unwrap(),
444            )
445            .unwrap();
446            let mut coset_point = domain.offset().value().unwrap();
447            let mut oracle_evals = Vec::new();
448            for _ in 0..(1 << n) {
449                oracle_evals.push(poly.evaluate(&coset_point));
450                coset_point *= gen;
451            }
452
453            let evaluations_fp: Vec<_> = oracle_evals
454                .iter()
455                .map(|x| FpVar::new_input(ns!(cs, "evaluations"), || Ok(x)).unwrap())
456                .collect();
457            let evaluations_var = EvaluationsVar::from_vec_and_domain(evaluations_fp, domain, true);
458
459            let interpolate_point = Fr::rand(&mut rng);
460            let interpolate_point_fp =
461                FpVar::new_input(ns!(cs, "interpolate point"), || Ok(interpolate_point)).unwrap();
462
463            let expected = poly.evaluate(&interpolate_point);
464
465            let actual = evaluations_var
466                .interpolate_and_evaluate(&interpolate_point_fp)
467                .unwrap()
468                .value()
469                .unwrap();
470
471            assert_eq!(actual, expected);
472            assert!(cs.is_satisfied().unwrap());
473            println!("number of constraints: {}", cs.num_constraints());
474        }
475    }
476
477    #[test]
478    fn test_division() {
479        let mut rng = test_rng();
480        let gen = Fr::get_root_of_unity(1 << 4).unwrap();
481        assert_eq!(gen.pow(&[1 << 4]), Fr::one());
482        let domain = Radix2DomainVar::new(
483            gen,
484            4, // 2^4 = 16
485            FpVar::constant(Fr::GENERATOR),
486        )
487        .unwrap();
488        let cs = ConstraintSystem::new_ref();
489
490        let ev_a = EvaluationsVar::from_vec_and_domain(
491            (0..16)
492                .map(|_| FpVar::new_input(ns!(cs, "poly_a"), || Ok(Fr::rand(&mut rng))).unwrap())
493                .collect(),
494            domain.clone(),
495            false,
496        );
497        let ev_b = EvaluationsVar::from_vec_and_domain(
498            (0..16)
499                .map(|_| FpVar::new_input(ns!(cs, "poly_a"), || Ok(Fr::rand(&mut rng))).unwrap())
500                .collect(),
501            domain.clone(),
502            false,
503        );
504
505        let a_div_b = (&ev_a) / (&ev_b);
506        assert!(cs.is_satisfied().unwrap());
507        let b_div_a = (&ev_b) / (&ev_a);
508
509        let one = &a_div_b * &b_div_a;
510        for ev in one.evals.iter() {
511            assert!(Fr::is_one(&ev.value().unwrap()))
512        }
513
514        assert!(cs.is_satisfied().unwrap());
515    }
516}