mina_tree/proofs/
verification.rs

1use std::rc::Rc;
2
3use ark_poly::{EvaluationDomain, Radix2EvaluationDomain};
4use ark_serialize::Write;
5use itertools::Itertools;
6use mina_p2p_messages::bigint::InvalidBigInt;
7use poly_commitment::ipa::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::ipa::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::ipa::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        mina_core::warn!(message = format!("verify_block: Protocol state contains invalid field"));
741        return false; // invalid bigint
742    };
743    let protocol_state_hash = MinaHash::hash(&protocol_state);
744
745    let accum_check =
746        accumulator_check::accumulator_check(srs, &[protocol_state_proof]).unwrap_or(false);
747    let verified = verify_impl(&protocol_state_hash, protocol_state_proof, &vk).unwrap_or(false);
748    let ok = accum_check && verified;
749
750    mina_core::info!(message = format!("verify_block OK={ok:?}"));
751
752    if !ok {
753        on_fail::dump_block_verification(header);
754    }
755
756    ok
757}
758
759pub fn verify_transaction<'a>(
760    proofs: impl IntoIterator<Item = (&'a Statement<SokDigest>, &'a TransactionSnarkProofStableV2)>,
761    verifier_index: &VerifierIndex<Fq>,
762    srs: &SRS<Vesta>,
763) -> bool {
764    let vk = VK {
765        commitments: PlonkVerificationKeyEvals::from(verifier_index),
766        index: verifier_index,
767        data: (),
768    };
769
770    let mut inputs: Vec<(
771        &Statement<SokDigest>,
772        &PicklesProofProofsVerified2ReprStableV2,
773        &VK,
774    )> = Vec::with_capacity(128);
775
776    let mut accum_check_proofs: Vec<&PicklesProofProofsVerified2ReprStableV2> =
777        Vec::with_capacity(128);
778
779    proofs
780        .into_iter()
781        .for_each(|(statement, transaction_proof)| {
782            accum_check_proofs.push(transaction_proof);
783            inputs.push((statement, transaction_proof, &vk));
784        });
785
786    let accum_check =
787        accumulator_check::accumulator_check(srs, &accum_check_proofs).unwrap_or(false);
788    let verified = batch_verify_impl(inputs.as_slice()).unwrap_or(false);
789    let ok = accum_check && verified;
790
791    mina_core::info!(message = format!("verify_transactions OK={ok:?}"));
792
793    if !ok {
794        on_fail::dump_tx_verification(&inputs);
795    }
796
797    ok
798}
799
800/// <https://github.com/MinaProtocol/mina/blob/bfd1009abdbee78979ff0343cc73a3480e862f58/src/lib/crypto/kimchi_bindings/stubs/src/pasta_fq_plonk_proof.rs#L116>
801pub fn verify_zkapp(
802    verification_key: &VerificationKey,
803    zkapp_statement: &ZkappStatement,
804    sideloaded_proof: &PicklesProofProofsVerified2ReprStableV2,
805    srs: &SRS<Vesta>,
806) -> bool {
807    let verifier_index = make_zkapp_verifier_index(verification_key);
808    // <https://github.com/MinaProtocol/mina/blob/4e0b324912017c3ff576704ee397ade3d9bda412/src/lib/pickles/pickles.ml#LL260C1-L274C18>
809    let vk = VK {
810        commitments: *verification_key.wrap_index.clone(),
811        index: &verifier_index,
812        data: (),
813    };
814
815    let accum_check =
816        accumulator_check::accumulator_check(srs, &[sideloaded_proof]).unwrap_or(false);
817    let verified = verify_impl(&zkapp_statement, sideloaded_proof, &vk).unwrap_or(false);
818
819    let ok = accum_check && verified;
820
821    mina_core::info!(message = format!("verify_zkapp OK={ok:?}"));
822
823    if !ok {
824        on_fail::dump_zkapp_verification(verification_key, zkapp_statement, sideloaded_proof);
825    }
826
827    ok
828}
829
830fn verify_impl<AppState>(
831    app_state: &AppState,
832    proof: &PicklesProofProofsVerified2ReprStableV2,
833    vk: &VK,
834) -> anyhow::Result<bool>
835where
836    AppState: ToFieldElements<Fp>,
837{
838    let deferred_values = compute_deferred_values(proof)?;
839    let checks = run_checks(proof, vk.index);
840
841    let message_for_next_step_proof = get_message_for_next_step_proof(
842        &proof.statement.messages_for_next_step_proof,
843        &vk.commitments,
844        app_state,
845    )?;
846
847    let message_for_next_wrap_proof =
848        get_message_for_next_wrap_proof(&proof.statement.proof_state.messages_for_next_wrap_proof)?;
849
850    let prepared_statement = get_prepared_statement(
851        &message_for_next_step_proof,
852        &message_for_next_wrap_proof,
853        deferred_values,
854        &proof.statement.proof_state.sponge_digest_before_evaluations,
855    );
856
857    let npublic_input = vk.index.public;
858    let public_inputs = prepared_statement.to_public_input(npublic_input)?;
859    let proof = make_padded_proof_from_p2p(proof)?;
860
861    let result = verify_with(vk.index, &proof, &public_inputs);
862
863    if let Err(e) = result {
864        eprintln!("verify error={:?}", e);
865    };
866
867    Ok(result.is_ok() && checks)
868}
869
870fn batch_verify_impl<AppState>(
871    proofs: &[(&AppState, &PicklesProofProofsVerified2ReprStableV2, &VK)],
872) -> anyhow::Result<bool>
873where
874    AppState: ToFieldElements<Fp>,
875{
876    let mut verification_contexts = Vec::with_capacity(proofs.len());
877    let mut checks = true;
878
879    for (app_state, proof, vk) in proofs {
880        let deferred_values = compute_deferred_values(proof)?;
881        checks = checks && run_checks(proof, vk.index);
882
883        let message_for_next_step_proof = get_message_for_next_step_proof(
884            &proof.statement.messages_for_next_step_proof,
885            &vk.commitments,
886            app_state,
887        )?;
888
889        let message_for_next_wrap_proof = get_message_for_next_wrap_proof(
890            &proof.statement.proof_state.messages_for_next_wrap_proof,
891        )?;
892
893        let prepared_statement = get_prepared_statement(
894            &message_for_next_step_proof,
895            &message_for_next_wrap_proof,
896            deferred_values,
897            &proof.statement.proof_state.sponge_digest_before_evaluations,
898        );
899
900        let npublic_input = vk.index.public;
901        let public_inputs = prepared_statement.to_public_input(npublic_input)?;
902        let proof_padded = make_padded_proof_from_p2p(proof)?;
903
904        verification_contexts.push((vk.index, proof_padded, public_inputs));
905    }
906
907    let proofs: Vec<VerificationContext> = verification_contexts
908        .iter()
909        .map(|(vk, proof, public_input)| VerificationContext {
910            verifier_index: vk,
911            proof,
912            public_input,
913        })
914        .collect();
915
916    let result = batch_verify(&proofs);
917
918    Ok(result.is_ok() && checks)
919}
920
921/// Dump data when it fails, to reproduce and compare in OCaml
922mod on_fail {
923    use super::*;
924
925    pub(super) fn dump_zkapp_verification(
926        verification_key: &VerificationKey,
927        zkapp_statement: &ZkappStatement,
928        sideloaded_proof: &PicklesProofProofsVerified2ReprStableV2,
929    ) {
930        use mina_p2p_messages::{
931            binprot,
932            binprot::macros::{BinProtRead, BinProtWrite},
933        };
934
935        #[derive(Clone, Debug, PartialEq, BinProtRead, BinProtWrite)]
936        struct VerifyZkapp {
937            vk: v2::MinaBaseVerificationKeyWireStableV1,
938            zkapp_statement: v2::MinaBaseZkappStatementStableV2,
939            proof: v2::PicklesProofProofsVerified2ReprStableV2,
940        }
941
942        let data = VerifyZkapp {
943            vk: verification_key.into(),
944            zkapp_statement: zkapp_statement.into(),
945            proof: sideloaded_proof.clone(),
946        };
947
948        dump_to_file(&data, "verify_zkapp")
949    }
950
951    pub(super) fn dump_block_verification(header: &MinaBlockHeaderStableV2) {
952        dump_to_file(header, "verify_block")
953    }
954
955    pub(super) fn dump_tx_verification(
956        txs: &[(
957            &Statement<SokDigest>,
958            &PicklesProofProofsVerified2ReprStableV2,
959            &VK,
960        )],
961    ) {
962        let data = txs
963            .iter()
964            .map(|(statement, proof, _vk)| {
965                let statement: v2::MinaStateSnarkedLedgerStateWithSokStableV2 = (*statement).into();
966                (statement, (*proof).clone())
967            })
968            .collect::<Vec<_>>();
969
970        dump_to_file(&data, "verify_txs")
971    }
972
973    #[allow(unreachable_code)]
974    fn dump_to_file<D: BinProtWrite>(data: &D, filename: &str) {
975        #[cfg(any(test, feature = "fuzzing"))]
976        {
977            let (_, _) = (data, filename); // avoid unused vars
978            return;
979        }
980
981        if let Err(e) = dump_to_file_impl(data, filename) {
982            mina_core::error!(
983                message = "Failed to dump proof verification data",
984                error = format!("{e:?}")
985            );
986        }
987    }
988
989    fn dump_to_file_impl<D: BinProtWrite>(data: &D, filename: &str) -> std::io::Result<()> {
990        let bin = {
991            let mut vec = Vec::with_capacity(128 * 1024);
992            data.binprot_write(&mut vec)?;
993            vec
994        };
995
996        let debug_dir = mina_core::get_debug_dir();
997        let filename = debug_dir
998            .join(generate_new_filename(filename, "binprot", &bin)?)
999            .to_string_lossy()
1000            .to_string();
1001        std::fs::create_dir_all(&debug_dir)?;
1002
1003        let mut file = std::fs::File::create(&filename)?;
1004        file.write_all(&bin)?;
1005        file.sync_all()?;
1006
1007        mina_core::error!(
1008            message = format!("proof verication failed, dumped data to {:?}", &filename)
1009        );
1010
1011        Ok(())
1012    }
1013
1014    fn generate_new_filename(name: &str, extension: &str, data: &[u8]) -> std::io::Result<String> {
1015        use crate::proofs::util::sha256_sum;
1016
1017        let sum = sha256_sum(data);
1018        for index in 0..100_000 {
1019            let name = format!("{}_{}_{}.{}", name, sum, index, extension);
1020            let path = std::path::Path::new(&name);
1021            if !path.try_exists().unwrap_or(true) {
1022                return Ok(name);
1023            }
1024        }
1025        Err(std::io::Error::other("no filename available"))
1026    }
1027}
1028
1029#[cfg(test)]
1030mod tests {
1031    use std::path::Path;
1032
1033    use mina_curves::pasta::Fp;
1034    use mina_p2p_messages::{binprot::BinProtRead, v2};
1035
1036    use crate::proofs::{provers::devnet_circuit_directory, transaction::tests::panic_in_ci};
1037
1038    use super::*;
1039
1040    #[cfg(target_family = "wasm")]
1041    use wasm_bindgen_test::wasm_bindgen_test as test;
1042
1043    #[test]
1044    fn test_verify_zkapp() {
1045        use mina_p2p_messages::{
1046            binprot,
1047            binprot::macros::{BinProtRead, BinProtWrite},
1048        };
1049
1050        #[derive(Clone, Debug, PartialEq, BinProtRead, BinProtWrite)]
1051        struct VerifyZkapp {
1052            vk: v2::MinaBaseVerificationKeyWireStableV1,
1053            zkapp_statement: v2::MinaBaseZkappStatementStableV2,
1054            proof: v2::PicklesProofProofsVerified2ReprStableV2,
1055        }
1056
1057        let base_dir = Path::new(env!("CARGO_MANIFEST_DIR"))
1058            .join(devnet_circuit_directory())
1059            .join("tests");
1060
1061        let cases = [
1062            "verify_zapp_4af39d1e141859c964fe32b4e80537d3bd8c32d75e2754c0b869738006d25251_0.binprot",
1063            "verify_zapp_dc518dc7e0859ea6ffa0cd42637cdcc9c79ab369dfb7ff44c8a89b1219f98728_0.binprot",
1064            "verify_zapp_9db7255327f342f75d27b5c0f646988ee68c6338f6e26c4dc549675f811b4152_0.binprot",
1065            "verify_zapp_f2bbc8088654c09314a58c96428f6828d3ee8096b6f34e3a027ad9b028ae22e0_0.binprot",
1066        ];
1067
1068        for filename in cases {
1069            let Ok(file) = std::fs::read(base_dir.join(filename)) else {
1070                panic_in_ci();
1071                return;
1072            };
1073
1074            let VerifyZkapp {
1075                vk,
1076                zkapp_statement,
1077                proof,
1078            } = VerifyZkapp::binprot_read(&mut file.as_slice()).unwrap();
1079
1080            let vk = (&vk).try_into().unwrap();
1081            let zkapp_statement = (&zkapp_statement).try_into().unwrap();
1082            let srs = crate::verifier::get_srs::<Fp>();
1083
1084            let ok = verify_zkapp(&vk, &zkapp_statement, &proof, &srs);
1085            assert!(ok);
1086        }
1087    }
1088
1089    // #[test]
1090    // fn test_verification() {
1091    //     let now = redux::Instant::now();
1092    //     let verifier_index = get_verifier_index(VerifierKind::Blockchain);
1093    //     println!("get_verifier_index={:?}", now.elapsed());
1094
1095    //     let now = redux::Instant::now();
1096    //     let srs = get_srs::<Fp>();
1097    //     let srs = srs.lock().unwrap();
1098    //     println!("get_srs={:?}\n", now.elapsed());
1099
1100    //     // let now = redux::Instant::now();
1101    //     // let bytes = verifier_index_to_bytes(&verifier_index);
1102    //     // println!("verifier_elapsed={:?}", now.elapsed());
1103    //     // println!("verifier_length={:?}", bytes.len());
1104    //     // assert_eq!(bytes.len(), 5622520);
1105
1106    //     // let now = redux::Instant::now();
1107    //     // let verifier_index = verifier_index_from_bytes(&bytes);
1108    //     // println!("verifier_deserialize_elapsed={:?}\n", now.elapsed());
1109
1110    //     // let now = redux::Instant::now();
1111    //     // let bytes = srs_to_bytes(&srs);
1112    //     // println!("srs_elapsed={:?}", now.elapsed());
1113    //     // println!("srs_length={:?}", bytes.len());
1114    //     // assert_eq!(bytes.len(), 5308513);
1115
1116    //     // let now = redux::Instant::now();
1117    //     // let srs: SRS<Vesta> = srs_from_bytes(&bytes);
1118    //     // println!("deserialize_elapsed={:?}\n", now.elapsed());
1119
1120    //     // Few blocks headers from berkeleynet
1121    //     let files = [
1122    //         include_bytes!("/tmp/block-rampup4.binprot"),
1123    //         // include_bytes!("../data/5573.binprot"),
1124    //         // include_bytes!("../data/5574.binprot"),
1125    //         // include_bytes!("../data/5575.binprot"),
1126    //         // include_bytes!("../data/5576.binprot"),
1127    //         // include_bytes!("../data/5577.binprot"),
1128    //         // include_bytes!("../data/5578.binprot"),
1129    //         // include_bytes!("../data/5579.binprot"),
1130    //         // include_bytes!("../data/5580.binprot"),
1131    //     ];
1132
1133    //     use mina_p2p_messages::binprot::BinProtRead;
1134    //     use crate::proofs::accumulator_check::accumulator_check;
1135
1136    //     for file in files {
1137    //         let header = MinaBlockHeaderStableV2::binprot_read(&mut file.as_slice()).unwrap();
1138
1139    //         let now = redux::Instant::now();
1140    //         let accum_check = accumulator_check(&*srs, &header.protocol_state_proof.0);
1141    //         println!("accumulator_check={:?}", now.elapsed());
1142
1143    //         let now = redux::Instant::now();
1144    //         let verified = super::verify_block(&header, &verifier_index, &*srs);
1145
1146    //         // let verified = crate::verify(&header, &verifier_index);
1147    //         println!("snark::verify={:?}", now.elapsed());
1148
1149    //         assert!(accum_check);
1150    //         assert!(verified);
1151    //     }
1152    // }
1153
1154    // #[test]
1155    // fn test_verifier_index_deterministic() {
1156    //     let mut nruns = 0;
1157    //     let nruns = &mut nruns;
1158
1159    //     let mut hash_verifier_index = || {
1160    //         *nruns += 1;
1161    //         let verifier_index = get_verifier_index();
1162    //         let bytes = verifier_index_to_bytes(&verifier_index);
1163
1164    //         let mut hasher = DefaultHasher::new();
1165    //         bytes.hash(&mut hasher);
1166    //         hasher.finish()
1167    //     };
1168
1169    //     assert_eq!(hash_verifier_index(), hash_verifier_index());
1170    //     assert_eq!(*nruns, 2);
1171    // }
1172}