Skip to content

Commit 54f402e

Browse files
committed
added a proof verifier that works with borrowed proofs
1 parent 6a8ca00 commit 54f402e

File tree

3 files changed

+245
-2
lines changed

3 files changed

+245
-2
lines changed

plonky2/src/fri/verifier.rs

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,146 @@ fn fri_verifier_query_round<
240240
Ok(())
241241
}
242242

243+
pub fn verify_fri_proof_v2<
244+
F: RichField + Extendable<D>,
245+
C: GenericConfig<D, F = F>,
246+
const D: usize,
247+
>(
248+
instance: &FriInstanceInfo<F, D>,
249+
openings: &FriOpenings<F, D>,
250+
challenges: &FriChallenges<F, D>,
251+
initial_merkle_caps: &[&MerkleCap<F, C::Hasher>],
252+
proof: &FriProof<F, C::Hasher, D>,
253+
params: &FriParams,
254+
) -> Result<()> {
255+
validate_fri_proof_shape::<F, C, D>(proof, instance, params)?;
256+
257+
// Size of the LDE domain.
258+
let n = params.lde_size();
259+
260+
// Check PoW.
261+
fri_verify_proof_of_work(challenges.fri_pow_response, &params.config)?;
262+
263+
// Check that parameters are coherent.
264+
ensure!(
265+
params.config.num_query_rounds == proof.query_round_proofs.len(),
266+
"Number of query rounds does not match config."
267+
);
268+
269+
let precomputed_reduced_evals =
270+
PrecomputedReducedOpenings::from_os_and_alpha(openings, challenges.fri_alpha);
271+
for (&x_index, round_proof) in challenges
272+
.fri_query_indices
273+
.iter()
274+
.zip(&proof.query_round_proofs)
275+
{
276+
fri_verifier_query_round_v2::<F, C, D>(
277+
instance,
278+
challenges,
279+
&precomputed_reduced_evals,
280+
initial_merkle_caps,
281+
proof,
282+
x_index,
283+
n,
284+
round_proof,
285+
params,
286+
)?;
287+
}
288+
289+
Ok(())
290+
}
291+
292+
fn fri_verify_initial_proof_v2<F: RichField, H: Hasher<F>>(
293+
x_index: usize,
294+
proof: &FriInitialTreeProof<F, H>,
295+
initial_merkle_caps: &[&MerkleCap<F, H>],
296+
) -> Result<()> {
297+
for ((evals, merkle_proof), cap) in proof.evals_proofs.iter().zip(initial_merkle_caps) {
298+
verify_merkle_proof_to_cap::<F, H>(evals.clone(), x_index, cap, merkle_proof)?;
299+
}
300+
301+
Ok(())
302+
}
303+
304+
fn fri_verifier_query_round_v2<
305+
F: RichField + Extendable<D>,
306+
C: GenericConfig<D, F = F>,
307+
const D: usize,
308+
>(
309+
instance: &FriInstanceInfo<F, D>,
310+
challenges: &FriChallenges<F, D>,
311+
precomputed_reduced_evals: &PrecomputedReducedOpenings<F, D>,
312+
initial_merkle_caps: &[&MerkleCap<F, C::Hasher>],
313+
proof: &FriProof<F, C::Hasher, D>,
314+
mut x_index: usize,
315+
n: usize,
316+
round_proof: &FriQueryRound<F, C::Hasher, D>,
317+
params: &FriParams,
318+
) -> Result<()> {
319+
fri_verify_initial_proof_v2::<F, C::Hasher>(
320+
x_index,
321+
&round_proof.initial_trees_proof,
322+
initial_merkle_caps,
323+
)?;
324+
// `subgroup_x` is `subgroup[x_index]`, i.e., the actual field element in the domain.
325+
let log_n = log2_strict(n);
326+
let mut subgroup_x = F::MULTIPLICATIVE_GROUP_GENERATOR
327+
* F::primitive_root_of_unity(log_n).exp_u64(reverse_bits(x_index, log_n) as u64);
328+
329+
// old_eval is the last derived evaluation; it will be checked for consistency with its
330+
// committed "parent" value in the next iteration.
331+
let mut old_eval = fri_combine_initial::<F, C, D>(
332+
instance,
333+
&round_proof.initial_trees_proof,
334+
challenges.fri_alpha,
335+
subgroup_x,
336+
precomputed_reduced_evals,
337+
params,
338+
);
339+
340+
for (i, &arity_bits) in params.reduction_arity_bits.iter().enumerate() {
341+
let arity = 1 << arity_bits;
342+
let evals = &round_proof.steps[i].evals;
343+
344+
// Split x_index into the index of the coset x is in, and the index of x within that coset.
345+
let coset_index = x_index >> arity_bits;
346+
let x_index_within_coset = x_index & (arity - 1);
347+
348+
// Check consistency with our old evaluation from the previous round.
349+
ensure!(evals[x_index_within_coset] == old_eval);
350+
351+
// Infer P(y) from {P(x)}_{x^arity=y}.
352+
old_eval = compute_evaluation(
353+
subgroup_x,
354+
x_index_within_coset,
355+
arity_bits,
356+
evals,
357+
challenges.fri_betas[i],
358+
);
359+
360+
verify_merkle_proof_to_cap::<F, C::Hasher>(
361+
flatten(evals),
362+
coset_index,
363+
&proof.commit_phase_merkle_caps[i],
364+
&round_proof.steps[i].merkle_proof,
365+
)?;
366+
367+
// Update the point x to x^arity.
368+
subgroup_x = subgroup_x.exp_power_of_2(arity_bits);
369+
370+
x_index = coset_index;
371+
}
372+
373+
// Final check of FRI. After all the reductions, we check that the final polynomial is equal
374+
// to the one sent by the prover.
375+
ensure!(
376+
proof.final_poly.eval(subgroup_x.into()) == old_eval,
377+
"Final polynomial evaluation is invalid."
378+
);
379+
380+
Ok(())
381+
}
382+
243383
/// For each opening point, holds the reduced (by `alpha`) evaluations of each polynomial that's
244384
/// opened at that point.
245385
#[derive(Clone, Debug)]

plonky2/src/plonk/validate_shape.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use crate::plonk::circuit_data::CommonCircuitData;
66
use crate::plonk::config::GenericConfig;
77
use crate::plonk::proof::{OpeningSet, Proof, ProofWithPublicInputs};
88

9-
pub(crate) fn validate_proof_with_pis_shape<F, C, const D: usize>(
9+
pub fn validate_proof_with_pis_shape<F, C, const D: usize>(
1010
proof_with_pis: &ProofWithPublicInputs<F, C, D>,
1111
common_data: &CommonCircuitData<F, D>,
1212
) -> anyhow::Result<()>

plonky2/src/plonk/verifier.rs

Lines changed: 104 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use anyhow::{ensure, Result};
44

55
use crate::field::extension::Extendable;
66
use crate::field::types::Field;
7-
use crate::fri::verifier::verify_fri_proof;
7+
use crate::fri::verifier::{verify_fri_proof, verify_fri_proof_v2};
88
use crate::hash::hash_types::RichField;
99
use crate::plonk::circuit_data::{CommonCircuitData, VerifierOnlyCircuitData};
1010
use crate::plonk::config::{GenericConfig, Hasher};
@@ -14,6 +14,28 @@ use crate::plonk::validate_shape::validate_proof_with_pis_shape;
1414
use crate::plonk::vanishing_poly::eval_vanishing_poly;
1515
use crate::plonk::vars::EvaluationVars;
1616

17+
pub fn verify_standard_proof<F: RichField + Extendable<D>, C: GenericConfig<D, F = F>, const D: usize>(
18+
proof_with_pis: &ProofWithPublicInputs<F, C, D>,
19+
verifier_data: &VerifierOnlyCircuitData<C, D>,
20+
common_data: &CommonCircuitData<F, D>,
21+
) -> Result<()> {
22+
validate_proof_with_pis_shape(&proof_with_pis, common_data)?;
23+
24+
let public_inputs_hash = proof_with_pis.get_public_inputs_hash();
25+
let challenges = proof_with_pis.get_challenges(
26+
public_inputs_hash,
27+
&verifier_data.circuit_digest,
28+
common_data,
29+
)?;
30+
31+
verify_with_challenges_v2::<F, C, D>(
32+
&proof_with_pis.proof,
33+
public_inputs_hash,
34+
challenges,
35+
verifier_data,
36+
common_data,
37+
)
38+
}
1739
pub(crate) fn verify<F: RichField + Extendable<D>, C: GenericConfig<D, F = F>, const D: usize>(
1840
proof_with_pis: ProofWithPublicInputs<F, C, D>,
1941
verifier_data: &VerifierOnlyCircuitData<C, D>,
@@ -116,3 +138,84 @@ pub(crate) fn verify_with_challenges<
116138

117139
Ok(())
118140
}
141+
142+
143+
144+
145+
pub fn verify_with_challenges_v2<
146+
F: RichField + Extendable<D>,
147+
C: GenericConfig<D, F = F>,
148+
const D: usize,
149+
>(
150+
proof: &Proof<F, C, D>,
151+
public_inputs_hash: <<C as GenericConfig<D>>::InnerHasher as Hasher<F>>::Hash,
152+
challenges: ProofChallenges<F, D>,
153+
verifier_data: &VerifierOnlyCircuitData<C, D>,
154+
common_data: &CommonCircuitData<F, D>,
155+
) -> Result<()> {
156+
let local_constants = &proof.openings.constants;
157+
let local_wires = &proof.openings.wires;
158+
let vars = EvaluationVars {
159+
local_constants,
160+
local_wires,
161+
public_inputs_hash: &public_inputs_hash,
162+
};
163+
let local_zs = &proof.openings.plonk_zs;
164+
let next_zs = &proof.openings.plonk_zs_next;
165+
let local_lookup_zs = &proof.openings.lookup_zs;
166+
let next_lookup_zs = &proof.openings.lookup_zs_next;
167+
let s_sigmas = &proof.openings.plonk_sigmas;
168+
let partial_products = &proof.openings.partial_products;
169+
170+
// Evaluate the vanishing polynomial at our challenge point, zeta.
171+
let vanishing_polys_zeta = eval_vanishing_poly::<F, D>(
172+
common_data,
173+
challenges.plonk_zeta,
174+
vars,
175+
local_zs,
176+
next_zs,
177+
local_lookup_zs,
178+
next_lookup_zs,
179+
partial_products,
180+
s_sigmas,
181+
&challenges.plonk_betas,
182+
&challenges.plonk_gammas,
183+
&challenges.plonk_alphas,
184+
&challenges.plonk_deltas,
185+
);
186+
187+
// Check each polynomial identity, of the form `vanishing(x) = Z_H(x) quotient(x)`, at zeta.
188+
let quotient_polys_zeta = &proof.openings.quotient_polys;
189+
let zeta_pow_deg = challenges
190+
.plonk_zeta
191+
.exp_power_of_2(common_data.degree_bits());
192+
let z_h_zeta = zeta_pow_deg - F::Extension::ONE;
193+
// `quotient_polys_zeta` holds `num_challenges * quotient_degree_factor` evaluations.
194+
// Each chunk of `quotient_degree_factor` holds the evaluations of `t_0(zeta),...,t_{quotient_degree_factor-1}(zeta)`
195+
// where the "real" quotient polynomial is `t(X) = t_0(X) + t_1(X)*X^n + t_2(X)*X^{2n} + ...`.
196+
// So to reconstruct `t(zeta)` we can compute `reduce_with_powers(chunk, zeta^n)` for each
197+
// `quotient_degree_factor`-sized chunk of the original evaluations.
198+
for (i, chunk) in quotient_polys_zeta
199+
.chunks(common_data.quotient_degree_factor)
200+
.enumerate()
201+
{
202+
ensure!(vanishing_polys_zeta[i] == z_h_zeta * reduce_with_powers(chunk, zeta_pow_deg));
203+
}
204+
205+
206+
verify_fri_proof_v2::<F, C, D>(
207+
&common_data.get_fri_instance(challenges.plonk_zeta),
208+
&proof.openings.to_fri_openings(),
209+
&challenges.fri_challenges,
210+
&[
211+
&verifier_data.constants_sigmas_cap,
212+
&proof.wires_cap,
213+
&proof.plonk_zs_partial_products_cap,
214+
&proof.quotient_polys_cap,
215+
],
216+
&proof.opening_proof,
217+
&common_data.fri_params,
218+
)?;
219+
220+
Ok(())
221+
}

0 commit comments

Comments
 (0)