Skip to content

Commit 499ba16

Browse files
huitseekerporcuquinempenciakadr1anh
authored
refactor: Improve Spartan SNARK polynomial computations and evaluations (#293)
This gathers the changes to the pre-processing SNARK excerpted from the Supernova implementation in PR #283. The main changes extracted here are the introduction of the masked eq polynomial (and its use fixing [this issue](https://hackmd.io/@adr1anh/Sy08YaVBa)), additional sum-check tooling, removal of two calls to evaluation argument. Main reference Arecibo PRs: - lurk-lang/arecibo#106 - lurk-lang/arecibo#131 - lurk-lang/arecibo#174 - lurk-lang/arecibo#182 - Enhancement of polynomial related code in Spartan protocol including new polynomial types, modified existing types, better evaluation methods, and improved polynomial operations. - Introduction of `squares` function and change in the generation of `t_pow` in Spartan. - Addition of a new polynomial type through `MaskedEqPolynomial` with methods for its creation and evaluation. - Enhancements in `UniPoly` struct by addition of `PartialEq`, and `Eq` traits. - Improvements in `snark.rs` for proving and verifying batch evaluation claims, leveraging `gamma` and `rho` for random linear combinations, and optimizing various variable computations. - Updates in `multilinear.rs` with refactoring, optimization, error handling, and supplementing unit tests. - Refactor in `spartan/mod.rs` with import updates, function overhauls, struct visibility changes, and asynchronous operations for efficient calculations. - Additions and amendments in `sumcheck.rs` for batch verification, handling of vectors of claims, handling of cubic bounds with additive terms, visibility adjustments, and typo fixes. - Modifications in `eq.rs` including a debug derive for `EqPolynomial`, enhanced visibility of `r` vector, provision of `evals_from_points` for enhanced evaluation, and addition of `FromIterator` implementation. Co-authored-by: porcuquine <[email protected]> Co-authored-by: Matej Penciak <[email protected]> Co-authored-by: Adrian Hamelink <[email protected]>
1 parent 8244d00 commit 499ba16

File tree

10 files changed

+818
-345
lines changed

10 files changed

+818
-345
lines changed

src/spartan/mod.rs

Lines changed: 143 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -5,24 +5,29 @@
55
//! We also provide direct.rs that allows proving a step circuit directly with either of the two SNARKs.
66
//!
77
//! In polynomial.rs we also provide foundational types and functions for manipulating multilinear polynomials.
8+
pub mod direct;
89
#[macro_use]
910
mod macros;
10-
pub mod direct;
1111
pub(crate) mod math;
1212
pub mod polys;
1313
pub mod ppsnark;
1414
pub mod snark;
1515
mod sumcheck;
1616

17-
use crate::{traits::Engine, Commitment};
17+
use crate::{
18+
r1cs::{R1CSShape, SparseMatrix},
19+
traits::Engine,
20+
Commitment,
21+
};
1822
use ff::Field;
1923
use itertools::Itertools as _;
2024
use polys::multilinear::SparsePolynomial;
2125
use rayon::{iter::IntoParallelRefIterator, prelude::*};
2226

27+
// Creates a vector of the first `n` powers of `s`.
2328
fn powers<E: Engine>(s: &E::Scalar, n: usize) -> Vec<E::Scalar> {
2429
assert!(n >= 1);
25-
let mut powers = Vec::new();
30+
let mut powers = Vec::with_capacity(n);
2631
powers.push(E::Scalar::ONE);
2732
for i in 1..n {
2833
powers.push(powers[i - 1] * s);
@@ -31,35 +36,60 @@ fn powers<E: Engine>(s: &E::Scalar, n: usize) -> Vec<E::Scalar> {
3136
}
3237

3338
/// A type that holds a witness to a polynomial evaluation instance
34-
pub struct PolyEvalWitness<E: Engine> {
39+
struct PolyEvalWitness<E: Engine> {
3540
p: Vec<E::Scalar>, // polynomial
3641
}
3742

3843
impl<E: Engine> PolyEvalWitness<E> {
39-
fn pad(mut W: Vec<PolyEvalWitness<E>>) -> Vec<PolyEvalWitness<E>> {
40-
// determine the maximum size
41-
if let Some(n) = W.iter().map(|w| w.p.len()).max() {
42-
W.iter_mut().for_each(|w| {
43-
w.p.resize(n, E::Scalar::ZERO);
44-
});
45-
W
46-
} else {
47-
Vec::new()
48-
}
49-
}
44+
/// Given [Pᵢ] and s, compute P = ∑ᵢ sⁱ⋅Pᵢ
45+
///
46+
/// # Details
47+
///
48+
/// We allow the input polynomials to have different sizes, and interpret smaller ones as
49+
/// being padded with 0 to the maximum size of all polynomials.
50+
fn batch_diff_size(W: Vec<PolyEvalWitness<E>>, s: E::Scalar) -> PolyEvalWitness<E> {
51+
let powers = powers::<E>(&s, W.len());
52+
53+
let size_max = W.iter().map(|w| w.p.len()).max().unwrap();
54+
// Scale the input polynomials by the power of s
55+
let p = W
56+
.into_par_iter()
57+
.zip_eq(powers.par_iter())
58+
.map(|(mut w, s)| {
59+
if *s != E::Scalar::ONE {
60+
w.p.par_iter_mut().for_each(|e| *e *= s);
61+
}
62+
w.p
63+
})
64+
.reduce(
65+
|| vec![E::Scalar::ZERO; size_max],
66+
|left, right| {
67+
// Sum into the largest polynomial
68+
let (mut big, small) = if left.len() > right.len() {
69+
(left, right)
70+
} else {
71+
(right, left)
72+
};
73+
74+
big
75+
.par_iter_mut()
76+
.zip(small.par_iter())
77+
.for_each(|(b, s)| *b += s);
78+
79+
big
80+
},
81+
);
5082

51-
fn weighted_sum(W: &[PolyEvalWitness<E>], s: &[E::Scalar]) -> PolyEvalWitness<E> {
52-
assert_eq!(W.len(), s.len());
53-
let mut p = vec![E::Scalar::ZERO; W[0].p.len()];
54-
for i in 0..W.len() {
55-
for j in 0..W[i].p.len() {
56-
p[j] += W[i].p[j] * s[i]
57-
}
58-
}
5983
PolyEvalWitness { p }
6084
}
6185

62-
// This method panics unless all vectors in p_vec are of the same length
86+
/// Given a set of polynomials \[Pᵢ\] and a scalar `s`, this method computes the weighted sum
87+
/// of the polynomials, where each polynomial Pᵢ is scaled by sⁱ. The method handles polynomials
88+
/// of different sizes by padding smaller ones with zeroes up to the size of the largest polynomial.
89+
///
90+
/// # Panics
91+
///
92+
/// This method panics if the polynomials in `p_vec` are not all of the same length.
6393
fn batch(p_vec: &[&Vec<E::Scalar>], s: &E::Scalar) -> PolyEvalWitness<E> {
6494
p_vec
6595
.iter()
@@ -69,7 +99,7 @@ impl<E: Engine> PolyEvalWitness<E> {
6999

70100
let p = zip_with!(par_iter, (p_vec, powers_of_s), |v, weight| {
71101
// compute the weighted sum for each vector
72-
v.iter().map(|&x| x * weight).collect::<Vec<E::Scalar>>()
102+
v.iter().map(|&x| x * *weight).collect::<Vec<E::Scalar>>()
73103
})
74104
.reduce(
75105
|| vec![E::Scalar::ZERO; p_vec[0].len()],
@@ -84,25 +114,54 @@ impl<E: Engine> PolyEvalWitness<E> {
84114
}
85115

86116
/// A type that holds a polynomial evaluation instance
87-
pub struct PolyEvalInstance<E: Engine> {
117+
struct PolyEvalInstance<E: Engine> {
88118
c: Commitment<E>, // commitment to the polynomial
89119
x: Vec<E::Scalar>, // evaluation point
90120
e: E::Scalar, // claimed evaluation
91121
}
92122

93123
impl<E: Engine> PolyEvalInstance<E> {
94-
fn pad(U: Vec<PolyEvalInstance<E>>) -> Vec<PolyEvalInstance<E>> {
95-
// determine the maximum size
96-
if let Some(ell) = U.iter().map(|u| u.x.len()).max() {
97-
U.into_iter()
98-
.map(|mut u| {
99-
let mut x = vec![E::Scalar::ZERO; ell - u.x.len()];
100-
x.append(&mut u.x);
101-
PolyEvalInstance { x, ..u }
102-
})
103-
.collect()
104-
} else {
105-
Vec::new()
124+
fn batch_diff_size(
125+
c_vec: &[Commitment<E>],
126+
e_vec: &[E::Scalar],
127+
num_vars: &[usize],
128+
x: Vec<E::Scalar>,
129+
s: E::Scalar,
130+
) -> PolyEvalInstance<E> {
131+
let num_instances = num_vars.len();
132+
assert_eq!(c_vec.len(), num_instances);
133+
assert_eq!(e_vec.len(), num_instances);
134+
135+
let num_vars_max = x.len();
136+
let powers: Vec<E::Scalar> = powers::<E>(&s, num_instances);
137+
// Rescale evaluations by the first Lagrange polynomial,
138+
// so that we can check its evaluation against x
139+
let evals_scaled = zip_with!(iter, (e_vec, num_vars), |eval, num_rounds| {
140+
// x_lo = [ x[0] , ..., x[n-nᵢ-1] ]
141+
// x_hi = [ x[n-nᵢ], ..., x[n] ]
142+
let (r_lo, _r_hi) = x.split_at(num_vars_max - num_rounds);
143+
// Compute L₀(x_lo)
144+
let lagrange_eval = r_lo
145+
.iter()
146+
.map(|r| E::Scalar::ONE - r)
147+
.product::<E::Scalar>();
148+
149+
// vᵢ = L₀(x_lo)⋅Pᵢ(x_hi)
150+
lagrange_eval * eval
151+
})
152+
.collect::<Vec<_>>();
153+
154+
// C = ∑ᵢ γⁱ⋅Cᵢ
155+
let comm_joint = zip_with!(iter, (c_vec, powers), |c, g_i| *c * *g_i)
156+
.fold(Commitment::<E>::default(), |acc, item| acc + item);
157+
158+
// v = ∑ᵢ γⁱ⋅vᵢ
159+
let eval_joint = zip_with!((evals_scaled.into_iter(), powers.iter()), |e, g_i| e * g_i).sum();
160+
161+
PolyEvalInstance {
162+
c: comm_joint,
163+
x,
164+
e: eval_joint,
106165
}
107166
}
108167

@@ -112,8 +171,13 @@ impl<E: Engine> PolyEvalInstance<E> {
112171
e_vec: &[E::Scalar],
113172
s: &E::Scalar,
114173
) -> PolyEvalInstance<E> {
115-
let powers_of_s = powers::<E>(s, c_vec.len());
174+
let num_instances = c_vec.len();
175+
assert_eq!(e_vec.len(), num_instances);
176+
177+
let powers_of_s = powers::<E>(s, num_instances);
178+
// Weighted sum of evaluations
116179
let e = zip_with!(par_iter, (e_vec, powers_of_s), |e, p| *e * p).sum();
180+
// Weighted sum of commitments
117181
let c = zip_with!(par_iter, (c_vec, powers_of_s), |c, p| *c * *p)
118182
.reduce(Commitment::<E>::default, |acc, item| acc + item);
119183

@@ -124,3 +188,43 @@ impl<E: Engine> PolyEvalInstance<E> {
124188
}
125189
}
126190
}
191+
192+
/// Bounds "row" variables of (A, B, C) matrices viewed as 2d multilinear polynomials
193+
fn compute_eval_table_sparse<E: Engine>(
194+
S: &R1CSShape<E>,
195+
rx: &[E::Scalar],
196+
) -> (Vec<E::Scalar>, Vec<E::Scalar>, Vec<E::Scalar>) {
197+
assert_eq!(rx.len(), S.num_cons);
198+
199+
let inner = |M: &SparseMatrix<E::Scalar>, M_evals: &mut Vec<E::Scalar>| {
200+
for (row_idx, ptrs) in M.indptr.windows(2).enumerate() {
201+
for (val, col_idx) in M.get_row_unchecked(ptrs.try_into().unwrap()) {
202+
M_evals[*col_idx] += rx[row_idx] * val;
203+
}
204+
}
205+
};
206+
207+
let (A_evals, (B_evals, C_evals)) = rayon::join(
208+
|| {
209+
let mut A_evals: Vec<E::Scalar> = vec![E::Scalar::ZERO; 2 * S.num_vars];
210+
inner(&S.A, &mut A_evals);
211+
A_evals
212+
},
213+
|| {
214+
rayon::join(
215+
|| {
216+
let mut B_evals: Vec<E::Scalar> = vec![E::Scalar::ZERO; 2 * S.num_vars];
217+
inner(&S.B, &mut B_evals);
218+
B_evals
219+
},
220+
|| {
221+
let mut C_evals: Vec<E::Scalar> = vec![E::Scalar::ZERO; 2 * S.num_vars];
222+
inner(&S.C, &mut C_evals);
223+
C_evals
224+
},
225+
)
226+
},
227+
);
228+
229+
(A_evals, B_evals, C_evals)
230+
}

src/spartan/polys/eq.rs

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,9 @@ use rayon::prelude::{IndexedParallelIterator, IntoParallelRefMutIterator, Parall
1414
/// This polynomial evaluates to 1 if every component $x_i$ equals its corresponding $e_i$, and 0 otherwise.
1515
///
1616
/// For instance, for e = 6 (with a binary representation of 0b110), the vector r would be [1, 1, 0].
17+
#[derive(Debug)]
1718
pub struct EqPolynomial<Scalar: PrimeField> {
18-
r: Vec<Scalar>,
19+
pub(in crate::spartan::polys) r: Vec<Scalar>,
1920
}
2021

2122
impl<Scalar: PrimeField> EqPolynomial<Scalar> {
@@ -43,12 +44,20 @@ impl<Scalar: PrimeField> EqPolynomial<Scalar> {
4344
///
4445
/// Returns a vector of Scalars, each corresponding to the polynomial evaluation at a specific point.
4546
pub fn evals(&self) -> Vec<Scalar> {
46-
let ell = self.r.len();
47+
Self::evals_from_points(&self.r)
48+
}
49+
50+
/// Evaluates the `EqPolynomial` from the `2^|r|` points in its domain, without creating an intermediate polynomial
51+
/// representation.
52+
///
53+
/// Returns a vector of Scalars, each corresponding to the polynomial evaluation at a specific point.
54+
pub fn evals_from_points(r: &[Scalar]) -> Vec<Scalar> {
55+
let ell = r.len();
4756
let mut evals: Vec<Scalar> = vec![Scalar::ZERO; (2_usize).pow(ell as u32)];
4857
let mut size = 1;
4958
evals[0] = Scalar::ONE;
5059

51-
for r in self.r.iter().rev() {
60+
for r in r.iter().rev() {
5261
let (evals_left, evals_right) = evals.split_at_mut(size);
5362
let (evals_right, _) = evals_right.split_at_mut(size);
5463

@@ -64,6 +73,13 @@ impl<Scalar: PrimeField> EqPolynomial<Scalar> {
6473
}
6574
}
6675

76+
impl<Scalar: PrimeField> FromIterator<Scalar> for EqPolynomial<Scalar> {
77+
fn from_iter<I: IntoIterator<Item = Scalar>>(iter: I) -> Self {
78+
let r: Vec<_> = iter.into_iter().collect();
79+
EqPolynomial { r }
80+
}
81+
}
82+
6783
#[cfg(test)]
6884
mod tests {
6985
use crate::provider;

0 commit comments

Comments
 (0)