1use std::rc::Rc;
2
3use ark_ff::fields::arithmetic::InvalidBigInt;
4use ark_poly::{EvaluationDomain, Radix2EvaluationDomain};
5use ark_serialize::Write;
6use itertools::Itertools;
7use poly_commitment::srs::SRS;
8
9use crate::{
10 proofs::{
11 accumulator_check,
12 step::{expand_deferred, StatementProofState},
13 unfinalized::AllEvals,
14 verifiers::make_zkapp_verifier_index,
15 wrap::Domain,
16 BACKEND_TICK_ROUNDS_N,
17 },
18 scan_state::{
19 protocol_state::MinaHash,
20 scan_state::transaction_snark::{SokDigest, Statement},
21 transaction_logic::{local_state::LazyValue, zkapp_statement::ZkappStatement},
22 },
23 VerificationKey,
24};
25
26use super::{
27 block::ProtocolState,
28 field::FieldWitness,
29 public_input::plonk_checks::make_shifts,
30 step::{step_verifier::PlonkDomain, ExpandDeferredParams},
31 to_field_elements::ToFieldElements,
32 transaction::{InnerCurve, PlonkVerificationKeyEvals},
33 util::{extract_bulletproof, extract_polynomial_commitment, two_u64_to_field},
34 wrap::expand_feature_flags,
35 ProverProof, VerifierIndex,
36};
37use kimchi::{
38 circuits::{expr::RowOffset, wires::PERMUTS},
39 error::VerifyError,
40 mina_curves::pasta::Pallas,
41 proof::{PointEvaluations, ProofEvaluations},
42};
43use mina_curves::pasta::{Fp, Fq, Vesta};
44use mina_p2p_messages::{
45 bigint::BigInt,
46 binprot::BinProtWrite,
47 v2::{
48 self, CompositionTypesDigestConstantStableV1, MinaBlockHeaderStableV2,
49 PicklesProofProofsVerified2ReprStableV2,
50 PicklesProofProofsVerified2ReprStableV2MessagesForNextStepProof,
51 PicklesProofProofsVerified2ReprStableV2MessagesForNextWrapProof,
52 PicklesProofProofsVerified2ReprStableV2PrevEvalsEvalsEvals,
53 PicklesProofProofsVerified2ReprStableV2StatementProofStateDeferredValuesPlonkFeatureFlags,
54 TransactionSnarkProofStableV2,
55 },
56};
57
58use super::prover::make_padded_proof_from_p2p;
59
60use super::public_input::{
61 messages::{MessagesForNextStepProof, MessagesForNextWrapProof},
62 plonk_checks::{PlonkMinimal, ScalarsEnv},
63 prepared_statement::{DeferredValues, PreparedStatement, ProofState},
64};
65
66#[cfg(target_family = "wasm")]
67#[cfg(test)]
68mod wasm {
69 use wasm_bindgen_test::*;
70 wasm_bindgen_test_configure!(run_in_browser);
71}
72
73fn validate_feature_flags(
74 feature_flags: &PicklesProofProofsVerified2ReprStableV2StatementProofStateDeferredValuesPlonkFeatureFlags,
75 evals: &PicklesProofProofsVerified2ReprStableV2PrevEvalsEvalsEvals,
76) -> bool {
77 let PicklesProofProofsVerified2ReprStableV2PrevEvalsEvalsEvals {
78 w: _,
79 coefficients: _,
80 z: _,
81 s: _,
82 generic_selector: _,
83 poseidon_selector: _,
84 complete_add_selector: _,
85 mul_selector: _,
86 emul_selector: _,
87 endomul_scalar_selector: _,
88 range_check0_selector,
89 range_check1_selector,
90 foreign_field_add_selector,
91 foreign_field_mul_selector,
92 xor_selector,
93 rot_selector,
94 lookup_aggregation,
95 lookup_table,
96 lookup_sorted,
97 runtime_lookup_table,
98 runtime_lookup_table_selector,
99 xor_lookup_selector,
100 lookup_gate_lookup_selector,
101 range_check_lookup_selector,
102 foreign_field_mul_lookup_selector,
103 } = evals;
104
105 fn enable_if<T>(x: &Option<T>, flag: bool) -> bool {
106 x.is_some() == flag
107 }
108
109 let f = feature_flags;
110 let range_check_lookup = f.range_check0 || f.range_check1 || f.rot;
111 let lookups_per_row_4 = f.xor || range_check_lookup || f.foreign_field_mul;
112 let lookups_per_row_3 = lookups_per_row_4 || f.lookup;
113 let lookups_per_row_2 = lookups_per_row_3;
114
115 [
116 enable_if(range_check0_selector, f.range_check0),
117 enable_if(range_check1_selector, f.range_check1),
118 enable_if(foreign_field_add_selector, f.foreign_field_add),
119 enable_if(foreign_field_mul_selector, f.foreign_field_mul),
120 enable_if(xor_selector, f.xor),
121 enable_if(rot_selector, f.rot),
122 enable_if(lookup_aggregation, lookups_per_row_2),
123 enable_if(lookup_table, lookups_per_row_2),
124 lookup_sorted.iter().enumerate().fold(true, |acc, (i, x)| {
125 let flag = match i {
126 0..=2 => lookups_per_row_2,
127 3 => lookups_per_row_3,
128 4 => lookups_per_row_4,
129 _ => panic!(),
130 };
131 acc && enable_if(x, flag)
132 }),
133 enable_if(runtime_lookup_table, f.runtime_tables),
134 enable_if(runtime_lookup_table_selector, f.runtime_tables),
135 enable_if(xor_lookup_selector, f.xor),
136 enable_if(lookup_gate_lookup_selector, f.lookup),
137 enable_if(range_check_lookup_selector, range_check_lookup),
138 enable_if(foreign_field_mul_lookup_selector, f.foreign_field_mul),
139 ]
140 .iter()
141 .all(|b| *b)
142}
143
144pub fn prev_evals_from_p2p<F: FieldWitness>(
145 evals: &PicklesProofProofsVerified2ReprStableV2PrevEvalsEvalsEvals,
146) -> Result<ProofEvaluations<PointEvaluations<Vec<F>>>, InvalidBigInt> {
147 let PicklesProofProofsVerified2ReprStableV2PrevEvalsEvalsEvals {
148 w,
149 coefficients,
150 z,
151 s,
152 generic_selector,
153 poseidon_selector,
154 complete_add_selector,
155 mul_selector,
156 emul_selector,
157 endomul_scalar_selector,
158 range_check0_selector,
159 range_check1_selector,
160 foreign_field_add_selector,
161 foreign_field_mul_selector,
162 xor_selector,
163 rot_selector,
164 lookup_aggregation,
165 lookup_table,
166 lookup_sorted,
167 runtime_lookup_table,
168 runtime_lookup_table_selector,
169 xor_lookup_selector,
170 lookup_gate_lookup_selector,
171 range_check_lookup_selector,
172 foreign_field_mul_lookup_selector,
173 } = evals;
174
175 fn of<'a, F: FieldWitness, I: IntoIterator<Item = &'a BigInt>>(
176 zeta: I,
177 zeta_omega: I,
178 ) -> Result<PointEvaluations<Vec<F>>, InvalidBigInt> {
179 Ok(PointEvaluations {
180 zeta: zeta
181 .into_iter()
182 .map(BigInt::to_field)
183 .collect::<Result<_, _>>()?,
184 zeta_omega: zeta_omega
185 .into_iter()
186 .map(BigInt::to_field)
187 .collect::<Result<_, _>>()?,
188 })
189 }
190
191 let of = |(zeta, zeta_omega): &(_, _)| -> Result<PointEvaluations<Vec<F>>, _> {
192 of(zeta, zeta_omega)
193 };
194 let of_opt = |v: &Option<(_, _)>| match v.as_ref() {
195 Some(v) => Ok(Some(of(v)?)),
196 None => Ok(None),
197 };
198
199 Ok(ProofEvaluations {
200 public: None,
201 w: crate::try_array_into_with(w, of)?,
202 z: of(z)?,
203 s: crate::try_array_into_with(s, of)?,
204 coefficients: crate::try_array_into_with(coefficients, of)?,
205 generic_selector: of(generic_selector)?,
206 poseidon_selector: of(poseidon_selector)?,
207 complete_add_selector: of(complete_add_selector)?,
208 mul_selector: of(mul_selector)?,
209 emul_selector: of(emul_selector)?,
210 endomul_scalar_selector: of(endomul_scalar_selector)?,
211 range_check0_selector: of_opt(range_check0_selector)?,
212 range_check1_selector: of_opt(range_check1_selector)?,
213 foreign_field_add_selector: of_opt(foreign_field_add_selector)?,
214 foreign_field_mul_selector: of_opt(foreign_field_mul_selector)?,
215 xor_selector: of_opt(xor_selector)?,
216 rot_selector: of_opt(rot_selector)?,
217 lookup_aggregation: of_opt(lookup_aggregation)?,
218 lookup_table: of_opt(lookup_table)?,
219 lookup_sorted: crate::try_array_into_with(lookup_sorted, of_opt)?,
220 runtime_lookup_table: of_opt(runtime_lookup_table)?,
221 runtime_lookup_table_selector: of_opt(runtime_lookup_table_selector)?,
222 xor_lookup_selector: of_opt(xor_lookup_selector)?,
223 lookup_gate_lookup_selector: of_opt(lookup_gate_lookup_selector)?,
224 range_check_lookup_selector: of_opt(range_check_lookup_selector)?,
225 foreign_field_mul_lookup_selector: of_opt(foreign_field_mul_lookup_selector)?,
226 })
227}
228
229pub fn prev_evals_to_p2p(
230 evals: &ProofEvaluations<PointEvaluations<Vec<Fp>>>,
231) -> PicklesProofProofsVerified2ReprStableV2PrevEvalsEvalsEvals {
232 let ProofEvaluations {
233 public: _,
234 w,
235 coefficients,
236 z,
237 s,
238 generic_selector,
239 poseidon_selector,
240 complete_add_selector,
241 mul_selector,
242 emul_selector,
243 endomul_scalar_selector,
244 range_check0_selector,
245 range_check1_selector,
246 foreign_field_add_selector,
247 foreign_field_mul_selector,
248 xor_selector,
249 rot_selector,
250 lookup_aggregation,
251 lookup_table,
252 lookup_sorted,
253 runtime_lookup_table,
254 runtime_lookup_table_selector,
255 xor_lookup_selector,
256 lookup_gate_lookup_selector,
257 range_check_lookup_selector,
258 foreign_field_mul_lookup_selector,
259 } = evals;
260
261 use mina_p2p_messages::pseq::PaddedSeq;
262
263 let of = |PointEvaluations { zeta, zeta_omega }: &PointEvaluations<Vec<Fp>>| {
264 (
265 zeta.iter().map(Into::into).collect(),
266 zeta_omega.iter().map(Into::into).collect(),
267 )
268 };
269
270 let of_opt = |v: &Option<PointEvaluations<Vec<Fp>>>| v.as_ref().map(of);
271
272 PicklesProofProofsVerified2ReprStableV2PrevEvalsEvalsEvals {
273 w: PaddedSeq(w.each_ref().map(of)),
274 z: of(z),
275 s: PaddedSeq(s.each_ref().map(of)),
276 coefficients: PaddedSeq(coefficients.each_ref().map(of)),
277 generic_selector: of(generic_selector),
278 poseidon_selector: of(poseidon_selector),
279 complete_add_selector: of(complete_add_selector),
280 mul_selector: of(mul_selector),
281 emul_selector: of(emul_selector),
282 endomul_scalar_selector: of(endomul_scalar_selector),
283 range_check0_selector: of_opt(range_check0_selector),
284 range_check1_selector: of_opt(range_check1_selector),
285 foreign_field_add_selector: of_opt(foreign_field_add_selector),
286 foreign_field_mul_selector: of_opt(foreign_field_mul_selector),
287 xor_selector: of_opt(xor_selector),
288 rot_selector: of_opt(rot_selector),
289 lookup_aggregation: of_opt(lookup_aggregation),
290 lookup_table: of_opt(lookup_table),
291 lookup_sorted: PaddedSeq(lookup_sorted.each_ref().map(of_opt)),
292 runtime_lookup_table: of_opt(runtime_lookup_table),
293 runtime_lookup_table_selector: of_opt(runtime_lookup_table_selector),
294 xor_lookup_selector: of_opt(xor_lookup_selector),
295 lookup_gate_lookup_selector: of_opt(lookup_gate_lookup_selector),
296 range_check_lookup_selector: of_opt(range_check_lookup_selector),
297 foreign_field_mul_lookup_selector: of_opt(foreign_field_mul_lookup_selector),
298 }
299}
300
301struct LimitedDomain<F: FieldWitness> {
302 domain: Radix2EvaluationDomain<F>,
303 shifts: kimchi::circuits::polynomials::permutation::Shifts<F>,
304}
305
306impl<F: FieldWitness> PlonkDomain<F> for LimitedDomain<F> {
307 fn vanishing_polynomial(&self, _x: F, _w: &mut super::witness::Witness<F>) -> F {
308 unimplemented!() }
310 fn generator(&self) -> F {
311 self.domain.group_gen
312 }
313 fn shifts(&self) -> &[F; PERMUTS] {
314 self.shifts.shifts()
315 }
316 fn log2_size(&self) -> u64 {
317 unimplemented!() }
319}
320
321pub fn make_scalars_env<F: FieldWitness, const NLIMB: usize>(
323 minimal: &PlonkMinimal<F, NLIMB>,
324 domain_log2: u8,
325 srs_length_log2: u64,
326 zk_rows: u64,
327) -> ScalarsEnv<F> {
328 let domain: Radix2EvaluationDomain<F> =
329 Radix2EvaluationDomain::new(1 << domain_log2 as u64).unwrap();
330
331 let zeta_to_n_minus_1 = domain.evaluate_vanishing_polynomial(minimal.zeta);
332
333 let (
334 omega_to_zk_minus_1,
335 omega_to_zk,
336 omega_to_intermediate_powers,
337 omega_to_zk_plus_1,
338 omega_to_minus_1,
339 ) = {
340 let gen = domain.group_gen;
341 let omega_to_minus_1 = F::one() / gen;
342 let omega_to_minus_2 = omega_to_minus_1.square();
343 let (omega_to_intermediate_powers, omega_to_zk_plus_1) = {
344 let mut next_term = omega_to_minus_2;
345 let omega_to_intermediate_powers = (0..(zk_rows.checked_sub(3).unwrap()))
346 .map(|_| {
347 let term = next_term;
348 next_term = term * omega_to_minus_1;
349 term
350 })
351 .collect::<Vec<_>>();
352 (omega_to_intermediate_powers, next_term)
353 };
354 let omega_to_zk = omega_to_zk_plus_1 * omega_to_minus_1;
355 let omega_to_zk_minus_1 = move || omega_to_zk * omega_to_minus_1;
356
357 (
358 omega_to_zk_minus_1,
359 omega_to_zk,
360 omega_to_intermediate_powers,
361 omega_to_zk_plus_1,
362 omega_to_minus_1,
363 )
364 };
365
366 let zk_polynomial = (minimal.zeta - omega_to_minus_1)
367 * (minimal.zeta - omega_to_zk_plus_1)
368 * (minimal.zeta - omega_to_zk);
369
370 let shifts = make_shifts(&domain);
371 let domain = Rc::new(LimitedDomain { domain, shifts });
372
373 let vanishes_on_zero_knowledge_and_previous_rows = match minimal.joint_combiner {
374 None => F::one(),
375 Some(_) => omega_to_intermediate_powers.iter().fold(
376 zk_polynomial * (minimal.zeta - omega_to_zk_minus_1()),
378 |acc, omega_pow| acc * (minimal.zeta - omega_pow),
380 ),
381 };
382
383 let zeta_clone = minimal.zeta;
384 let zeta_to_srs_length =
385 LazyValue::make(move |_| (0..srs_length_log2).fold(zeta_clone, |acc, _| acc * acc));
386
387 let feature_flags = minimal
388 .joint_combiner
389 .map(|_| expand_feature_flags::<F>(&minimal.feature_flags.to_boolean()));
390
391 let unnormalized_lagrange_basis = match minimal.joint_combiner {
392 None => None,
393 Some(_) => {
394 use crate::proofs::witness::Witness;
395
396 let zeta = minimal.zeta;
397 let generator = domain.generator();
398 let omega_to_zk_minus_1_clone = omega_to_zk_minus_1();
399 let fun: Box<dyn Fn(RowOffset, &mut Witness<F>) -> F> =
400 Box::new(move |i: RowOffset, w: &mut Witness<F>| {
401 let w_to_i = match (i.zk_rows, i.offset) {
402 (false, 0) => F::one(),
403 (false, 1) => generator,
404 (false, -1) => omega_to_minus_1,
405 (false, -2) => omega_to_zk_plus_1,
406 (false, -3) | (true, 0) => omega_to_zk,
407 (true, -1) => omega_to_zk_minus_1_clone,
408 _ => todo!(),
409 };
410 crate::proofs::field::field::div_by_inv(zeta_to_n_minus_1, zeta - w_to_i, w)
411 });
412 Some(fun)
413 }
414 };
415
416 ScalarsEnv {
417 zk_polynomial,
418 zeta_to_n_minus_1,
419 srs_length_log2,
420 domain,
421 omega_to_minus_zk_rows: omega_to_zk,
422 feature_flags,
423 unnormalized_lagrange_basis,
424 vanishes_on_zero_knowledge_and_previous_rows,
425 zeta_to_srs_length,
426 }
427}
428
429fn get_message_for_next_step_proof<'a, AppState>(
430 messages_for_next_step_proof: &PicklesProofProofsVerified2ReprStableV2MessagesForNextStepProof,
431 commitments: &'a PlonkVerificationKeyEvals<Fp>,
432 app_state: &'a AppState,
433) -> Result<MessagesForNextStepProof<'a, AppState>, InvalidBigInt>
434where
435 AppState: ToFieldElements<Fp>,
436{
437 let PicklesProofProofsVerified2ReprStableV2MessagesForNextStepProof {
438 app_state: _, challenge_polynomial_commitments,
440 old_bulletproof_challenges,
441 } = messages_for_next_step_proof;
442
443 let challenge_polynomial_commitments: Vec<InnerCurve<Fp>> =
444 extract_polynomial_commitment(challenge_polynomial_commitments)?;
445 let old_bulletproof_challenges: Vec<[Fp; 16]> = extract_bulletproof(old_bulletproof_challenges);
446 let dlog_plonk_index = commitments;
447
448 Ok(MessagesForNextStepProof {
449 app_state,
450 dlog_plonk_index,
451 challenge_polynomial_commitments,
452 old_bulletproof_challenges,
453 })
454}
455
456fn get_message_for_next_wrap_proof(
457 PicklesProofProofsVerified2ReprStableV2MessagesForNextWrapProof {
458 challenge_polynomial_commitment,
459 old_bulletproof_challenges,
460 }: &PicklesProofProofsVerified2ReprStableV2MessagesForNextWrapProof,
461) -> Result<MessagesForNextWrapProof, InvalidBigInt> {
462 let challenge_polynomial_commitments: Vec<InnerCurve<Fq>> =
463 extract_polynomial_commitment(&[challenge_polynomial_commitment.clone()])?;
464
465 let old_bulletproof_challenges: Vec<[Fq; 15]> = extract_bulletproof(&[
466 old_bulletproof_challenges[0].0.clone(),
467 old_bulletproof_challenges[1].0.clone(),
468 ]);
469
470 Ok(MessagesForNextWrapProof {
471 challenge_polynomial_commitment: challenge_polynomial_commitments[0].clone(),
472 old_bulletproof_challenges,
473 })
474}
475
476fn get_prepared_statement<AppState>(
477 message_for_next_step_proof: &MessagesForNextStepProof<AppState>,
478 message_for_next_wrap_proof: &MessagesForNextWrapProof,
479 deferred_values: DeferredValues<Fp>,
480 sponge_digest_before_evaluations: &CompositionTypesDigestConstantStableV1,
481) -> PreparedStatement
482where
483 AppState: ToFieldElements<Fp>,
484{
485 let digest = sponge_digest_before_evaluations;
486 let sponge_digest_before_evaluations: [u64; 4] = digest.each_ref().map(|v| v.as_u64());
487
488 PreparedStatement {
489 proof_state: ProofState {
490 deferred_values,
491 sponge_digest_before_evaluations,
492 messages_for_next_wrap_proof: message_for_next_wrap_proof.hash(),
493 },
494 messages_for_next_step_proof: message_for_next_step_proof.hash(),
495 }
496}
497
498fn verify_with(
499 verifier_index: &VerifierIndex<Fq>,
500 proof: &ProverProof<Fq>,
501 public_input: &[Fq],
502) -> Result<(), VerifyError> {
503 use kimchi::{groupmap::GroupMap, mina_curves::pasta::PallasParameters};
504 use mina_poseidon::sponge::{DefaultFqSponge, DefaultFrSponge};
505 use poly_commitment::evaluation_proof::OpeningProof;
506
507 type SpongeParams = mina_poseidon::constants::PlonkSpongeConstantsKimchi;
508 type EFqSponge = DefaultFqSponge<PallasParameters, SpongeParams>;
509 type EFrSponge = DefaultFrSponge<Fq, SpongeParams>;
510
511 let group_map = GroupMap::<Fp>::setup();
512
513 kimchi::verifier::verify::<Pallas, EFqSponge, EFrSponge, OpeningProof<Pallas>>(
514 &group_map,
515 verifier_index,
516 proof,
517 public_input,
518 )
519}
520
521pub struct VerificationContext<'a> {
522 pub verifier_index: &'a VerifierIndex<Fq>,
523 pub proof: &'a ProverProof<Fq>,
524 pub public_input: &'a [Fq],
525}
526
527fn batch_verify(proofs: &[VerificationContext]) -> Result<(), VerifyError> {
528 use kimchi::{groupmap::GroupMap, mina_curves::pasta::PallasParameters, verifier::Context};
529 use mina_poseidon::sponge::{DefaultFqSponge, DefaultFrSponge};
530 use poly_commitment::evaluation_proof::OpeningProof;
531
532 type SpongeParams = mina_poseidon::constants::PlonkSpongeConstantsKimchi;
533 type EFqSponge = DefaultFqSponge<PallasParameters, SpongeParams>;
534 type EFrSponge = DefaultFrSponge<Fq, SpongeParams>;
535
536 let group_map = GroupMap::<Fp>::setup();
537 let proofs = proofs
538 .iter()
539 .map(|p| Context {
540 verifier_index: p.verifier_index,
541 proof: p.proof,
542 public_input: p.public_input,
543 })
544 .collect_vec();
545
546 kimchi::verifier::batch_verify::<Pallas, EFqSponge, EFrSponge, OpeningProof<Pallas>>(
547 &group_map, &proofs,
548 )
549}
550
551fn run_checks(
552 proof: &PicklesProofProofsVerified2ReprStableV2,
553 verifier_index: &VerifierIndex<Fq>,
554) -> bool {
555 let mut errors: Vec<String> = vec![];
556 let mut checks = |condition: bool, s: &str| {
557 if !condition {
558 errors.push(s.to_string())
559 }
560 };
561
562 let non_chunking = {
563 let PicklesProofProofsVerified2ReprStableV2PrevEvalsEvalsEvals {
564 w,
565 coefficients,
566 z,
567 s,
568 generic_selector,
569 poseidon_selector,
570 complete_add_selector,
571 mul_selector,
572 emul_selector,
573 endomul_scalar_selector,
574 range_check0_selector,
575 range_check1_selector,
576 foreign_field_add_selector,
577 foreign_field_mul_selector,
578 xor_selector,
579 rot_selector,
580 lookup_aggregation,
581 lookup_table,
582 lookup_sorted,
583 runtime_lookup_table,
584 runtime_lookup_table_selector,
585 xor_lookup_selector,
586 lookup_gate_lookup_selector,
587 range_check_lookup_selector,
588 foreign_field_mul_lookup_selector,
589 } = &proof.prev_evals.evals.evals;
590
591 let mut iter = w
592 .iter()
593 .chain(coefficients.iter())
594 .chain([z])
595 .chain(s.iter())
596 .chain([
597 generic_selector,
598 poseidon_selector,
599 complete_add_selector,
600 mul_selector,
601 emul_selector,
602 endomul_scalar_selector,
603 ])
604 .chain(range_check0_selector.iter())
605 .chain(range_check1_selector.iter())
606 .chain(foreign_field_add_selector.iter())
607 .chain(foreign_field_mul_selector.iter())
608 .chain(xor_selector.iter())
609 .chain(rot_selector.iter())
610 .chain(lookup_aggregation.iter())
611 .chain(lookup_table.iter())
612 .chain(lookup_sorted.iter().flatten())
613 .chain(runtime_lookup_table.iter())
614 .chain(runtime_lookup_table_selector.iter())
615 .chain(xor_lookup_selector.iter())
616 .chain(lookup_gate_lookup_selector.iter())
617 .chain(range_check_lookup_selector.iter())
618 .chain(foreign_field_mul_lookup_selector.iter());
619
620 iter.all(|(a, b)| a.len() <= 1 && b.len() <= 1)
621 };
622
623 checks(non_chunking, "only uses single chunks");
624
625 checks(
626 validate_feature_flags(
627 &proof
628 .statement
629 .proof_state
630 .deferred_values
631 .plonk
632 .feature_flags,
633 &proof.prev_evals.evals.evals,
634 ),
635 "feature flags are consistent with evaluations",
636 );
637
638 let branch_data = &proof.statement.proof_state.deferred_values.branch_data;
639 let step_domain: u8 = branch_data.domain_log2.as_u8();
640 let step_domain = Domain::Pow2RootsOfUnity(step_domain as u64);
641
642 checks(
643 step_domain.log2_size() as usize <= BACKEND_TICK_ROUNDS_N,
644 "domain size is small enough",
645 );
646
647 {
648 let all_possible_domains = [13, 14, 15];
650 let [greatest_wrap_domain, _, least_wrap_domain] = all_possible_domains;
651
652 let actual_wrap_domain = verifier_index.domain.log_size_of_group;
653 checks(
654 actual_wrap_domain <= least_wrap_domain,
655 "invalid actual_wrap_domain (least_wrap_domain)",
656 );
657 checks(
658 actual_wrap_domain >= greatest_wrap_domain,
659 "invalid actual_wrap_domain (greatest_wrap_domain)",
660 );
661 }
662
663 for e in &errors {
664 eprintln!("{:?}", e);
665 }
666
667 errors.is_empty()
668}
669
670fn compute_deferred_values(
671 proof: &PicklesProofProofsVerified2ReprStableV2,
672) -> anyhow::Result<DeferredValues<Fp>> {
673 let bulletproof_challenges: Vec<Fp> = proof
674 .statement
675 .proof_state
676 .deferred_values
677 .bulletproof_challenges
678 .iter()
679 .map(|chal| {
680 let prechallenge = &chal.prechallenge.inner;
681 let prechallenge: [u64; 2] = prechallenge.each_ref().map(|v| v.as_u64());
682 two_u64_to_field(&prechallenge)
683 })
684 .collect();
685
686 let deferred_values = {
687 let old_bulletproof_challenges: Vec<[Fp; 16]> = proof
688 .statement
689 .messages_for_next_step_proof
690 .old_bulletproof_challenges
691 .iter()
692 .map(|v| {
693 v.0.clone()
694 .map(|v| two_u64_to_field(&v.prechallenge.inner.0.map(|v| v.as_u64())))
695 })
696 .collect();
697 let proof_state: StatementProofState = (&proof.statement.proof_state).try_into()?;
698 let evals: AllEvals<Fp> = (&proof.prev_evals).try_into()?;
699
700 let zk_rows = 3;
701 expand_deferred(ExpandDeferredParams {
702 evals: &evals,
703 old_bulletproof_challenges: &old_bulletproof_challenges,
704 proof_state: &proof_state,
705 zk_rows,
706 })?
707 };
708
709 Ok(DeferredValues {
710 bulletproof_challenges,
711 ..deferred_values
712 })
713}
714
715pub struct VK<'a> {
717 pub commitments: PlonkVerificationKeyEvals<Fp>,
718 pub index: &'a VerifierIndex<Fq>,
719 pub data: (), }
721
722pub fn verify_block(
723 header: &MinaBlockHeaderStableV2,
724 verifier_index: &VerifierIndex<Fq>,
725 srs: &SRS<Vesta>,
726) -> bool {
727 let MinaBlockHeaderStableV2 {
728 protocol_state,
729 protocol_state_proof,
730 ..
731 } = &header;
732
733 let vk = VK {
734 commitments: PlonkVerificationKeyEvals::from(verifier_index),
735 index: verifier_index,
736 data: (),
737 };
738
739 let Ok(protocol_state) = ProtocolState::try_from(protocol_state) else {
740 openmina_core::warn!(
741 message = format!("verify_block: Protocol state contains invalid field")
742 );
743 return false; };
745 let protocol_state_hash = MinaHash::hash(&protocol_state);
746
747 let accum_check =
748 accumulator_check::accumulator_check(srs, &[protocol_state_proof]).unwrap_or(false);
749 let verified = verify_impl(&protocol_state_hash, protocol_state_proof, &vk).unwrap_or(false);
750 let ok = accum_check && verified;
751
752 openmina_core::info!(message = format!("verify_block OK={ok:?}"));
753
754 if !ok {
755 on_fail::dump_block_verification(header);
756 }
757
758 ok
759}
760
761pub fn verify_transaction<'a>(
762 proofs: impl IntoIterator<Item = (&'a Statement<SokDigest>, &'a TransactionSnarkProofStableV2)>,
763 verifier_index: &VerifierIndex<Fq>,
764 srs: &SRS<Vesta>,
765) -> bool {
766 let vk = VK {
767 commitments: PlonkVerificationKeyEvals::from(verifier_index),
768 index: verifier_index,
769 data: (),
770 };
771
772 let mut inputs: Vec<(
773 &Statement<SokDigest>,
774 &PicklesProofProofsVerified2ReprStableV2,
775 &VK,
776 )> = Vec::with_capacity(128);
777
778 let mut accum_check_proofs: Vec<&PicklesProofProofsVerified2ReprStableV2> =
779 Vec::with_capacity(128);
780
781 proofs
782 .into_iter()
783 .for_each(|(statement, transaction_proof)| {
784 accum_check_proofs.push(transaction_proof);
785 inputs.push((statement, transaction_proof, &vk));
786 });
787
788 let accum_check =
789 accumulator_check::accumulator_check(srs, &accum_check_proofs).unwrap_or(false);
790 let verified = batch_verify_impl(inputs.as_slice()).unwrap_or(false);
791 let ok = accum_check && verified;
792
793 openmina_core::info!(message = format!("verify_transactions OK={ok:?}"));
794
795 if !ok {
796 on_fail::dump_tx_verification(&inputs);
797 }
798
799 ok
800}
801
802pub fn verify_zkapp(
804 verification_key: &VerificationKey,
805 zkapp_statement: &ZkappStatement,
806 sideloaded_proof: &PicklesProofProofsVerified2ReprStableV2,
807 srs: &SRS<Vesta>,
808) -> bool {
809 let verifier_index = make_zkapp_verifier_index(verification_key);
810 let vk = VK {
812 commitments: *verification_key.wrap_index.clone(),
813 index: &verifier_index,
814 data: (),
815 };
816
817 let accum_check =
818 accumulator_check::accumulator_check(srs, &[sideloaded_proof]).unwrap_or(false);
819 let verified = verify_impl(&zkapp_statement, sideloaded_proof, &vk).unwrap_or(false);
820
821 let ok = accum_check && verified;
822
823 openmina_core::info!(message = format!("verify_zkapp OK={ok:?}"));
824
825 if !ok {
826 on_fail::dump_zkapp_verification(verification_key, zkapp_statement, sideloaded_proof);
827 }
828
829 ok
830}
831
832fn verify_impl<AppState>(
833 app_state: &AppState,
834 proof: &PicklesProofProofsVerified2ReprStableV2,
835 vk: &VK,
836) -> anyhow::Result<bool>
837where
838 AppState: ToFieldElements<Fp>,
839{
840 let deferred_values = compute_deferred_values(proof)?;
841 let checks = run_checks(proof, vk.index);
842
843 let message_for_next_step_proof = get_message_for_next_step_proof(
844 &proof.statement.messages_for_next_step_proof,
845 &vk.commitments,
846 app_state,
847 )?;
848
849 let message_for_next_wrap_proof =
850 get_message_for_next_wrap_proof(&proof.statement.proof_state.messages_for_next_wrap_proof)?;
851
852 let prepared_statement = get_prepared_statement(
853 &message_for_next_step_proof,
854 &message_for_next_wrap_proof,
855 deferred_values,
856 &proof.statement.proof_state.sponge_digest_before_evaluations,
857 );
858
859 let npublic_input = vk.index.public;
860 let public_inputs = prepared_statement.to_public_input(npublic_input)?;
861 let proof = make_padded_proof_from_p2p(proof)?;
862
863 let result = verify_with(vk.index, &proof, &public_inputs);
864
865 if let Err(e) = result {
866 eprintln!("verify error={:?}", e);
867 };
868
869 Ok(result.is_ok() && checks)
870}
871
872fn batch_verify_impl<AppState>(
873 proofs: &[(&AppState, &PicklesProofProofsVerified2ReprStableV2, &VK)],
874) -> anyhow::Result<bool>
875where
876 AppState: ToFieldElements<Fp>,
877{
878 let mut verification_contexts = Vec::with_capacity(proofs.len());
879 let mut checks = true;
880
881 for (app_state, proof, vk) in proofs {
882 let deferred_values = compute_deferred_values(proof)?;
883 checks = checks && run_checks(proof, vk.index);
884
885 let message_for_next_step_proof = get_message_for_next_step_proof(
886 &proof.statement.messages_for_next_step_proof,
887 &vk.commitments,
888 app_state,
889 )?;
890
891 let message_for_next_wrap_proof = get_message_for_next_wrap_proof(
892 &proof.statement.proof_state.messages_for_next_wrap_proof,
893 )?;
894
895 let prepared_statement = get_prepared_statement(
896 &message_for_next_step_proof,
897 &message_for_next_wrap_proof,
898 deferred_values,
899 &proof.statement.proof_state.sponge_digest_before_evaluations,
900 );
901
902 let npublic_input = vk.index.public;
903 let public_inputs = prepared_statement.to_public_input(npublic_input)?;
904 let proof_padded = make_padded_proof_from_p2p(proof)?;
905
906 verification_contexts.push((vk.index, proof_padded, public_inputs));
907 }
908
909 let proofs: Vec<VerificationContext> = verification_contexts
910 .iter()
911 .map(|(vk, proof, public_input)| VerificationContext {
912 verifier_index: vk,
913 proof,
914 public_input,
915 })
916 .collect();
917
918 let result = batch_verify(&proofs);
919
920 Ok(result.is_ok() && checks)
921}
922
923mod on_fail {
925 use super::*;
926
927 pub(super) fn dump_zkapp_verification(
928 verification_key: &VerificationKey,
929 zkapp_statement: &ZkappStatement,
930 sideloaded_proof: &PicklesProofProofsVerified2ReprStableV2,
931 ) {
932 use mina_p2p_messages::{
933 binprot,
934 binprot::macros::{BinProtRead, BinProtWrite},
935 };
936
937 #[derive(Clone, Debug, PartialEq, BinProtRead, BinProtWrite)]
938 struct VerifyZkapp {
939 vk: v2::MinaBaseVerificationKeyWireStableV1,
940 zkapp_statement: v2::MinaBaseZkappStatementStableV2,
941 proof: v2::PicklesProofProofsVerified2ReprStableV2,
942 }
943
944 let data = VerifyZkapp {
945 vk: verification_key.into(),
946 zkapp_statement: zkapp_statement.into(),
947 proof: sideloaded_proof.clone(),
948 };
949
950 dump_to_file(&data, "verify_zkapp")
951 }
952
953 pub(super) fn dump_block_verification(header: &MinaBlockHeaderStableV2) {
954 dump_to_file(header, "verify_block")
955 }
956
957 pub(super) fn dump_tx_verification(
958 txs: &[(
959 &Statement<SokDigest>,
960 &PicklesProofProofsVerified2ReprStableV2,
961 &VK,
962 )],
963 ) {
964 let data = txs
965 .iter()
966 .map(|(statement, proof, _vk)| {
967 let statement: v2::MinaStateSnarkedLedgerStateWithSokStableV2 = (*statement).into();
968 (statement, (*proof).clone())
969 })
970 .collect::<Vec<_>>();
971
972 dump_to_file(&data, "verify_txs")
973 }
974
975 #[allow(unreachable_code)]
976 fn dump_to_file<D: BinProtWrite>(data: &D, filename: &str) {
977 #[cfg(any(test, feature = "fuzzing"))]
978 {
979 let (_, _) = (data, filename); return;
981 }
982
983 if let Err(e) = dump_to_file_impl(data, filename) {
984 openmina_core::error!(
985 message = "Failed to dump proof verification data",
986 error = format!("{e:?}")
987 );
988 }
989 }
990
991 fn dump_to_file_impl<D: BinProtWrite>(data: &D, filename: &str) -> std::io::Result<()> {
992 let bin = {
993 let mut vec = Vec::with_capacity(128 * 1024);
994 data.binprot_write(&mut vec)?;
995 vec
996 };
997
998 let debug_dir = openmina_core::get_debug_dir();
999 let filename = debug_dir
1000 .join(generate_new_filename(filename, "binprot", &bin)?)
1001 .to_string_lossy()
1002 .to_string();
1003 std::fs::create_dir_all(&debug_dir)?;
1004
1005 let mut file = std::fs::File::create(&filename)?;
1006 file.write_all(&bin)?;
1007 file.sync_all()?;
1008
1009 openmina_core::error!(
1010 message = format!("proof verication failed, dumped data to {:?}", &filename)
1011 );
1012
1013 Ok(())
1014 }
1015
1016 fn generate_new_filename(name: &str, extension: &str, data: &[u8]) -> std::io::Result<String> {
1017 use crate::proofs::util::sha256_sum;
1018
1019 let sum = sha256_sum(data);
1020 for index in 0..100_000 {
1021 let name = format!("{}_{}_{}.{}", name, sum, index, extension);
1022 let path = std::path::Path::new(&name);
1023 if !path.try_exists().unwrap_or(true) {
1024 return Ok(name);
1025 }
1026 }
1027 Err(std::io::Error::other("no filename available"))
1028 }
1029}
1030
1031#[cfg(test)]
1032mod tests {
1033 use std::path::Path;
1034
1035 use mina_curves::pasta::Fp;
1036 use mina_p2p_messages::{binprot::BinProtRead, v2};
1037
1038 use crate::proofs::{provers::devnet_circuit_directory, transaction::tests::panic_in_ci};
1039
1040 use super::*;
1041
1042 #[cfg(target_family = "wasm")]
1043 use wasm_bindgen_test::wasm_bindgen_test as test;
1044
1045 #[test]
1046 fn test_verify_zkapp() {
1047 use mina_p2p_messages::{
1048 binprot,
1049 binprot::macros::{BinProtRead, BinProtWrite},
1050 };
1051
1052 #[derive(Clone, Debug, PartialEq, BinProtRead, BinProtWrite)]
1053 struct VerifyZkapp {
1054 vk: v2::MinaBaseVerificationKeyWireStableV1,
1055 zkapp_statement: v2::MinaBaseZkappStatementStableV2,
1056 proof: v2::PicklesProofProofsVerified2ReprStableV2,
1057 }
1058
1059 let base_dir = Path::new(env!("CARGO_MANIFEST_DIR"))
1060 .join(devnet_circuit_directory())
1061 .join("tests");
1062
1063 let cases = [
1064 "verify_zapp_4af39d1e141859c964fe32b4e80537d3bd8c32d75e2754c0b869738006d25251_0.binprot",
1065 "verify_zapp_dc518dc7e0859ea6ffa0cd42637cdcc9c79ab369dfb7ff44c8a89b1219f98728_0.binprot",
1066 "verify_zapp_9db7255327f342f75d27b5c0f646988ee68c6338f6e26c4dc549675f811b4152_0.binprot",
1067 "verify_zapp_f2bbc8088654c09314a58c96428f6828d3ee8096b6f34e3a027ad9b028ae22e0_0.binprot",
1068 ];
1069
1070 for filename in cases {
1071 let Ok(file) = std::fs::read(base_dir.join(filename)) else {
1072 panic_in_ci();
1073 return;
1074 };
1075
1076 let VerifyZkapp {
1077 vk,
1078 zkapp_statement,
1079 proof,
1080 } = VerifyZkapp::binprot_read(&mut file.as_slice()).unwrap();
1081
1082 let vk = (&vk).try_into().unwrap();
1083 let zkapp_statement = (&zkapp_statement).try_into().unwrap();
1084 let srs = crate::verifier::get_srs::<Fp>();
1085
1086 let ok = verify_zkapp(&vk, &zkapp_statement, &proof, &srs);
1087 assert!(ok);
1088 }
1089 }
1090
1091 }