mina_tree/proofs/
verification.rs

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!() // Unused during proof verification
309    }
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!() // Unused during proof verification
318    }
319}
320
321// TODO: `domain_log2` and `srs_length_log2` might be the same here ? Remove one or the other
322pub 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            // init
377            zk_polynomial * (minimal.zeta - omega_to_zk_minus_1()),
378            // f
379            |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: _, // unused
439        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        // TODO: Don't use hardcoded values
649        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
715/// <https://github.com/MinaProtocol/mina/blob/4e0b324912017c3ff576704ee397ade3d9bda412/src/lib/pickles/verification_key.mli#L30>
716pub struct VK<'a> {
717    pub commitments: PlonkVerificationKeyEvals<Fp>,
718    pub index: &'a VerifierIndex<Fq>,
719    pub data: (), // Unused in proof verification
720}
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; // invalid bigint
744    };
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
802/// <https://github.com/MinaProtocol/mina/blob/bfd1009abdbee78979ff0343cc73a3480e862f58/src/lib/crypto/kimchi_bindings/stubs/src/pasta_fq_plonk_proof.rs#L116>
803pub 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    // <https://github.com/MinaProtocol/mina/blob/4e0b324912017c3ff576704ee397ade3d9bda412/src/lib/pickles/pickles.ml#LL260C1-L274C18>
811    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
923/// Dump data when it fails, to reproduce and compare in OCaml
924mod 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); // avoid unused vars
980            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    // #[test]
1092    // fn test_verification() {
1093    //     let now = redux::Instant::now();
1094    //     let verifier_index = get_verifier_index(VerifierKind::Blockchain);
1095    //     println!("get_verifier_index={:?}", now.elapsed());
1096
1097    //     let now = redux::Instant::now();
1098    //     let srs = get_srs::<Fp>();
1099    //     let srs = srs.lock().unwrap();
1100    //     println!("get_srs={:?}\n", now.elapsed());
1101
1102    //     // let now = redux::Instant::now();
1103    //     // let bytes = verifier_index_to_bytes(&verifier_index);
1104    //     // println!("verifier_elapsed={:?}", now.elapsed());
1105    //     // println!("verifier_length={:?}", bytes.len());
1106    //     // assert_eq!(bytes.len(), 5622520);
1107
1108    //     // let now = redux::Instant::now();
1109    //     // let verifier_index = verifier_index_from_bytes(&bytes);
1110    //     // println!("verifier_deserialize_elapsed={:?}\n", now.elapsed());
1111
1112    //     // let now = redux::Instant::now();
1113    //     // let bytes = srs_to_bytes(&srs);
1114    //     // println!("srs_elapsed={:?}", now.elapsed());
1115    //     // println!("srs_length={:?}", bytes.len());
1116    //     // assert_eq!(bytes.len(), 5308513);
1117
1118    //     // let now = redux::Instant::now();
1119    //     // let srs: SRS<Vesta> = srs_from_bytes(&bytes);
1120    //     // println!("deserialize_elapsed={:?}\n", now.elapsed());
1121
1122    //     // Few blocks headers from berkeleynet
1123    //     let files = [
1124    //         include_bytes!("/tmp/block-rampup4.binprot"),
1125    //         // include_bytes!("../data/5573.binprot"),
1126    //         // include_bytes!("../data/5574.binprot"),
1127    //         // include_bytes!("../data/5575.binprot"),
1128    //         // include_bytes!("../data/5576.binprot"),
1129    //         // include_bytes!("../data/5577.binprot"),
1130    //         // include_bytes!("../data/5578.binprot"),
1131    //         // include_bytes!("../data/5579.binprot"),
1132    //         // include_bytes!("../data/5580.binprot"),
1133    //     ];
1134
1135    //     use mina_p2p_messages::binprot::BinProtRead;
1136    //     use crate::proofs::accumulator_check::accumulator_check;
1137
1138    //     for file in files {
1139    //         let header = MinaBlockHeaderStableV2::binprot_read(&mut file.as_slice()).unwrap();
1140
1141    //         let now = redux::Instant::now();
1142    //         let accum_check = accumulator_check(&*srs, &header.protocol_state_proof.0);
1143    //         println!("accumulator_check={:?}", now.elapsed());
1144
1145    //         let now = redux::Instant::now();
1146    //         let verified = super::verify_block(&header, &verifier_index, &*srs);
1147
1148    //         // let verified = crate::verify(&header, &verifier_index);
1149    //         println!("snark::verify={:?}", now.elapsed());
1150
1151    //         assert!(accum_check);
1152    //         assert!(verified);
1153    //     }
1154    // }
1155
1156    // #[test]
1157    // fn test_verifier_index_deterministic() {
1158    //     let mut nruns = 0;
1159    //     let nruns = &mut nruns;
1160
1161    //     let mut hash_verifier_index = || {
1162    //         *nruns += 1;
1163    //         let verifier_index = get_verifier_index();
1164    //         let bytes = verifier_index_to_bytes(&verifier_index);
1165
1166    //         let mut hasher = DefaultHasher::new();
1167    //         bytes.hash(&mut hasher);
1168    //         hasher.finish()
1169    //     };
1170
1171    //     assert_eq!(hash_verifier_index(), hash_verifier_index());
1172    //     assert_eq!(*nruns, 2);
1173    // }
1174}