mina_tree/proofs/
verifiers.rs

1use std::{
2    io::Read,
3    path::{Path, PathBuf},
4    sync::Arc,
5};
6
7use anyhow::Context;
8use mina_core::{info, log::system_time, warn};
9use once_cell::sync::OnceCell;
10use serde::{Deserialize, Serialize};
11use sha2::{Digest, Sha256};
12
13use ark_poly::{EvaluationDomain, Radix2EvaluationDomain};
14use kimchi::{
15    circuits::{
16        constraints::FeatureFlags,
17        expr::Linearization,
18        lookup::lookups::{LookupFeatures, LookupPatterns},
19        polynomials::permutation::{permutation_vanishing_polynomial, zk_w},
20    },
21    linearization::expr_linearization,
22    mina_curves::pasta::Pallas,
23};
24use mina_curves::pasta::{Fp, Fq};
25use poly_commitment::{ipa::SRS, SRS as _};
26
27use crate::{proofs::BACKEND_TOCK_ROUNDS_N, VerificationKey};
28
29use super::{
30    transaction::{endos, InnerCurve},
31    wrap::{Domain, Domains},
32    VerifierIndex,
33};
34
35#[derive(Clone, Copy)]
36enum Kind {
37    BlockVerifier,
38    TransactionVerifier,
39}
40
41impl Kind {
42    pub fn to_str(self) -> &'static str {
43        match self {
44            Self::BlockVerifier => "block_verifier_index",
45            Self::TransactionVerifier => "transaction_verifier_index",
46        }
47    }
48
49    pub fn filename(self) -> String {
50        format!("{}.postcard", self.to_str())
51    }
52}
53
54impl std::fmt::Display for Kind {
55    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
56        write!(f, "{}", self.to_str())
57    }
58}
59
60fn cache_filename(kind: Kind) -> PathBuf {
61    let circuits_config = mina_core::NetworkConfig::global().circuits_config;
62    Path::new(circuits_config.directory_name).join(kind.filename())
63}
64
65#[cfg(not(target_family = "wasm"))]
66fn cache_path(kind: Kind) -> Option<PathBuf> {
67    super::circuit_blobs::home_base_dir().map(|p| p.join(cache_filename(kind)))
68}
69
70macro_rules! read_cache {
71    ($kind: expr, $digest: expr) => {{
72        #[cfg(not(target_family = "wasm"))]
73        let data = super::circuit_blobs::fetch_blocking(&cache_filename($kind))
74            .context("fetching verifier index failed")?;
75        #[cfg(target_family = "wasm")]
76        let data = super::circuit_blobs::fetch(&cache_filename($kind))
77            .await
78            .context("fetching verifier index failed")?;
79        let mut slice = data.as_slice();
80        let mut d = [0; 32];
81        // source digest
82        slice.read_exact(&mut d).context("reading source digest")?;
83        if d != $digest {
84            anyhow::bail!("source digest verification failed");
85        }
86
87        // index digest
88        slice.read_exact(&mut d).context("reading index digest")?;
89
90        let mut hasher = Sha256::new();
91        hasher.update(slice);
92        let digest = hasher.finalize();
93        if d != digest.as_slice() {
94            anyhow::bail!("verifier index digest verification failed");
95        }
96        Ok(super::caching::verifier_index_from_bytes(slice)?)
97    }};
98}
99
100#[cfg(not(target_family = "wasm"))]
101fn read_cache(kind: Kind, digest: &[u8]) -> anyhow::Result<VerifierIndex<Fq>> {
102    read_cache!(kind, digest)
103}
104
105#[cfg(target_family = "wasm")]
106async fn read_cache(kind: Kind, digest: &[u8]) -> anyhow::Result<VerifierIndex<Fq>> {
107    read_cache!(kind, digest)
108}
109
110#[cfg(not(target_family = "wasm"))]
111fn write_cache(kind: Kind, index: &VerifierIndex<Fq>, digest: &[u8]) -> anyhow::Result<()> {
112    use std::{fs::File, io::Write};
113
114    let path = cache_path(kind)
115        .ok_or_else(|| anyhow::anyhow!("$HOME env not set, so can't cache verifier index"))?;
116    let bytes = super::caching::verifier_index_to_bytes(index)?;
117    let mut hasher = Sha256::new();
118    hasher.update(&bytes);
119    let Some(parent) = path.parent() else {
120        anyhow::bail!("cannot get parent for {path:?}");
121    };
122    std::fs::create_dir_all(parent).context("creating cache file parent directory")?;
123    let mut file = File::create(path).context("creating cache file")?;
124    file.write_all(digest).context("storing source digest")?;
125    file.write_all(&hasher.finalize())
126        .context("storing verifier index digest")?;
127    file.write_all(&bytes)
128        .context("storing verifier index into cache file")?;
129    Ok(())
130}
131
132macro_rules! make_with_ext_cache {
133    ($kind: expr, $data: expr) => {{
134        let verifier_index: VerifierIndex<Fq> = serde_json::from_str($data).unwrap();
135        let mut hasher = Sha256::new();
136        hasher.update($data);
137        let src_index_digest = hasher.finalize();
138
139        #[cfg(not(target_family = "wasm"))]
140        let cache = read_cache($kind, &src_index_digest);
141        #[cfg(target_family = "wasm")]
142        let cache = read_cache($kind, &src_index_digest).await;
143
144        match cache {
145            Ok(verifier_index) => {
146                info!(system_time(); "Verifier index is loaded");
147                verifier_index
148            }
149            Err(err) => {
150                warn!(system_time(); "Cannot load verifier index: {err}");
151                let index = make_verifier_index(verifier_index);
152                #[cfg(not(target_family = "wasm"))]
153                if let Err(err) = write_cache($kind, &index, &src_index_digest) {
154                    warn!(system_time(); "Cannot store verifier index to cache file: {err}");
155                } else {
156                    info!(system_time(); "Stored verifier index to cache file");
157                }
158                index
159            }
160        }
161    }}
162}
163
164#[cfg(not(target_family = "wasm"))]
165fn make_with_ext_cache(kind: Kind, data: &str) -> VerifierIndex<Fq> {
166    make_with_ext_cache!(kind, data)
167}
168
169#[cfg(target_family = "wasm")]
170async fn make_with_ext_cache(kind: Kind, data: &str) -> VerifierIndex<Fq> {
171    make_with_ext_cache!(kind, data)
172}
173
174/// Verifier index for block proofs (consensus layer / block selection).
175/// Lazily initialized and cached globally.
176#[derive(Serialize, Deserialize, Debug, Clone)]
177pub struct BlockVerifier(Arc<VerifierIndex<Fq>>);
178
179/// Verifier index for transaction proofs (execution layer / transaction confirmation).
180/// Lazily initialized and cached globally.
181#[derive(Serialize, Deserialize, Debug, Clone)]
182pub struct TransactionVerifier(Arc<VerifierIndex<Fq>>);
183
184static BLOCK_VERIFIER: OnceCell<BlockVerifier> = OnceCell::new();
185static TX_VERIFIER: OnceCell<TransactionVerifier> = OnceCell::new();
186
187impl BlockVerifier {
188    fn kind() -> Kind {
189        Kind::BlockVerifier
190    }
191
192    fn src_json() -> &'static str {
193        let network_name = mina_core::NetworkConfig::global().name;
194        match network_name {
195            "mainnet" => include_str!("data/mainnet_blockchain_verifier_index.json"),
196            "devnet" => include_str!("data/devnet_blockchain_verifier_index.json"),
197            other => panic!("get_verifier_index: unknown network '{other}'"),
198        }
199    }
200}
201
202impl TransactionVerifier {
203    fn kind() -> Kind {
204        Kind::TransactionVerifier
205    }
206
207    fn src_json() -> &'static str {
208        let network_name = mina_core::NetworkConfig::global().name;
209        match network_name {
210            "mainnet" => include_str!("data/mainnet_transaction_verifier_index.json"),
211            "devnet" => include_str!("data/devnet_transaction_verifier_index.json"),
212            other => panic!("get_verifier_index: unknown network '{other}'"),
213        }
214    }
215
216    pub fn get() -> Option<Self> {
217        TX_VERIFIER.get().cloned()
218    }
219}
220
221#[cfg(not(target_family = "wasm"))]
222impl BlockVerifier {
223    /// Creates or returns cached block verifier index from embedded JSON data.
224    /// Network-specific (mainnet/devnet). Uses external cache for faster loading.
225    pub fn make() -> Self {
226        BLOCK_VERIFIER
227            .get_or_init(|| {
228                Self(Arc::new(make_with_ext_cache(
229                    Self::kind(),
230                    Self::src_json(),
231                )))
232            })
233            .clone()
234    }
235}
236
237#[cfg(target_family = "wasm")]
238impl BlockVerifier {
239    pub async fn make() -> Self {
240        if let Some(v) = BLOCK_VERIFIER.get() {
241            v.clone()
242        } else {
243            let verifier = Self(Arc::new(
244                make_with_ext_cache(Self::kind(), Self::src_json()).await,
245            ));
246            BLOCK_VERIFIER.get_or_init(move || verifier).clone()
247        }
248    }
249}
250
251#[cfg(not(target_family = "wasm"))]
252impl TransactionVerifier {
253    /// Creates or returns cached transaction verifier index from embedded JSON.
254    /// Network-specific (mainnet/devnet). Uses external cache for faster loading.
255    pub fn make() -> Self {
256        TX_VERIFIER
257            .get_or_init(|| {
258                Self(Arc::new(make_with_ext_cache(
259                    Self::kind(),
260                    Self::src_json(),
261                )))
262            })
263            .clone()
264    }
265}
266
267#[cfg(target_family = "wasm")]
268impl TransactionVerifier {
269    pub async fn make() -> Self {
270        if let Some(v) = TX_VERIFIER.get() {
271            v.clone()
272        } else {
273            let verifier = Self(Arc::new(
274                make_with_ext_cache(Self::kind(), Self::src_json()).await,
275            ));
276            TX_VERIFIER.get_or_init(move || verifier).clone()
277        }
278    }
279}
280
281impl std::ops::Deref for BlockVerifier {
282    type Target = VerifierIndex<Fq>;
283
284    fn deref(&self) -> &Self::Target {
285        &self.0
286    }
287}
288
289impl std::ops::Deref for TransactionVerifier {
290    type Target = VerifierIndex<Fq>;
291
292    fn deref(&self) -> &Self::Target {
293        &self.0
294    }
295}
296
297impl From<BlockVerifier> for Arc<VerifierIndex<Fq>> {
298    fn from(value: BlockVerifier) -> Self {
299        value.0
300    }
301}
302
303impl From<TransactionVerifier> for Arc<VerifierIndex<Fq>> {
304    fn from(value: TransactionVerifier) -> Self {
305        value.0
306    }
307}
308
309fn make_verifier_index(index: VerifierIndex<Fq>) -> VerifierIndex<Fq> {
310    let domain = index.domain;
311    let max_poly_size: usize = index.max_poly_size;
312    let (endo, _) = endos::<Fq>();
313
314    let feature_flags = FeatureFlags {
315        range_check0: false,
316        range_check1: false,
317        foreign_field_add: false,
318        foreign_field_mul: false,
319        xor: false,
320        rot: false,
321        lookup_features: LookupFeatures {
322            patterns: LookupPatterns {
323                xor: false,
324                lookup: false,
325                range_check: false,
326                foreign_field_mul: false,
327            },
328            joint_lookup_used: false,
329            uses_runtime_tables: false,
330        },
331    };
332
333    let (mut linearization, powers_of_alpha) = expr_linearization(Some(&feature_flags), true);
334
335    let linearization = Linearization {
336        constant_term: linearization.constant_term,
337        index_terms: {
338            // Make the verifier index deterministic
339            linearization
340                .index_terms
341                .sort_by_key(|&(columns, _)| columns);
342            linearization.index_terms
343        },
344    };
345
346    // <https://github.com/o1-labs/proof-systems/blob/2702b09063c7a48131173d78b6cf9408674fd67e/kimchi/src/verifier_index.rs#L310-L314>
347    let srs = {
348        let srs = SRS::create(max_poly_size);
349        srs.get_lagrange_basis(domain);
350        Arc::new(srs)
351    };
352
353    // <https://github.com/o1-labs/proof-systems/blob/2702b09063c7a48131173d78b6cf9408674fd67e/kimchi/src/verifier_index.rs#L319>
354    let permutation_vanishing_polynomial_m =
355        permutation_vanishing_polynomial(domain, index.zk_rows);
356
357    // <https://github.com/o1-labs/proof-systems/blob/2702b09063c7a48131173d78b6cf9408674fd67e/kimchi/src/verifier_index.rs#L324>
358    let w = zk_w(domain, index.zk_rows);
359
360    VerifierIndex::<Fq> {
361        srs,
362        permutation_vanishing_polynomial_m: OnceCell::from(permutation_vanishing_polynomial_m),
363        w: OnceCell::from(w),
364        endo,
365        linearization,
366        powers_of_alpha,
367        ..index
368    }
369}
370
371/// <https://github.com/MinaProtocol/mina/blob/bfd1009abdbee78979ff0343cc73a3480e862f58/src/lib/crypto/kimchi_bindings/stubs/src/pasta_fq_plonk_verifier_index.rs#L213>
372/// <https://github.com/MinaProtocol/mina/blob/bfd1009abdbee78979ff0343cc73a3480e862f58/src/lib/pickles/common.ml#L16C1-L25C58>
373pub fn make_shifts(
374    domain: &Radix2EvaluationDomain<Fq>,
375) -> kimchi::circuits::polynomials::permutation::Shifts<Fq> {
376    // let value = 1 << log2_size;
377    // let domain = Domain::<Fq>::new(value).unwrap();
378    kimchi::circuits::polynomials::permutation::Shifts::new(domain)
379}
380
381// <https://github.com/MinaProtocol/mina/blob/bfd1009abdbee78979ff0343cc73a3480e862f58/src/lib/pickles/common.ml#L27>
382pub fn wrap_domains(proofs_verified: usize) -> Domains {
383    let h = match proofs_verified {
384        0 => 13,
385        1 => 14,
386        2 => 15,
387        _ => unreachable!(),
388    };
389
390    Domains {
391        h: Domain::Pow2RootsOfUnity(h),
392    }
393}
394
395/// <https://github.com/MinaProtocol/mina/blob/bfd1009abdbee78979ff0343cc73a3480e862f58/src/lib/pickles/side_loaded_verification_key.ml#L206>
396pub fn make_zkapp_verifier_index(vk: &VerificationKey) -> VerifierIndex<Fq> {
397    let d = wrap_domains(vk.actual_wrap_domain_size.to_int());
398    let log2_size = d.h.log2_size();
399
400    let public = 40; // Is that constant ?
401
402    let domain: Radix2EvaluationDomain<Fq> =
403        Radix2EvaluationDomain::new(1 << log2_size as u64).unwrap();
404
405    let srs = {
406        let degree = 1 << BACKEND_TOCK_ROUNDS_N;
407        let srs = SRS::<Pallas>::create(degree);
408        srs.get_lagrange_basis(domain);
409        srs
410    };
411
412    let make_poly = |poly: &InnerCurve<Fp>| poly_commitment::PolyComm {
413        chunks: vec![poly.to_affine()],
414    };
415
416    let feature_flags = FeatureFlags {
417        range_check0: false,
418        range_check1: false,
419        foreign_field_add: false,
420        foreign_field_mul: false,
421        rot: false,
422        xor: false,
423        lookup_features: LookupFeatures {
424            patterns: LookupPatterns {
425                xor: false,
426                lookup: false,
427                range_check: false,
428                foreign_field_mul: false,
429            },
430            joint_lookup_used: false,
431            uses_runtime_tables: false,
432        },
433    };
434
435    let (endo_q, _endo_r) = endos::<Fq>();
436    let (linearization, powers_of_alpha) = expr_linearization(Some(&feature_flags), true);
437
438    let shift = make_shifts(&domain);
439
440    // <https://github.com/MinaProtocol/mina/blob/047375688f93546d4bdd58c75674394e3faae1f4/src/lib/pickles/side_loaded_verification_key.ml#L232>
441    let zk_rows = 3;
442
443    // Note: Verifier index is converted from OCaml here:
444    // <https://github.com/MinaProtocol/mina/blob/bfd1009abdbee78979ff0343cc73a3480e862f58/src/lib/crypto/kimchi_bindings/stubs/src/pasta_fq_plonk_verifier_index.rs#L58>
445
446    VerifierIndex::<Fq> {
447        domain,
448        max_poly_size: 1 << BACKEND_TOCK_ROUNDS_N,
449        srs: Arc::new(srs),
450        public,
451        prev_challenges: 2,
452        sigma_comm: vk.wrap_index.sigma.each_ref().map(make_poly),
453        coefficients_comm: vk.wrap_index.coefficients.each_ref().map(make_poly),
454        generic_comm: make_poly(&vk.wrap_index.generic),
455        psm_comm: make_poly(&vk.wrap_index.psm),
456        complete_add_comm: make_poly(&vk.wrap_index.complete_add),
457        mul_comm: make_poly(&vk.wrap_index.mul),
458        emul_comm: make_poly(&vk.wrap_index.emul),
459        endomul_scalar_comm: make_poly(&vk.wrap_index.endomul_scalar),
460        range_check0_comm: None,
461        range_check1_comm: None,
462        foreign_field_add_comm: None,
463        foreign_field_mul_comm: None,
464        xor_comm: None,
465        rot_comm: None,
466        shift: *shift.shifts(),
467        permutation_vanishing_polynomial_m: OnceCell::with_value(permutation_vanishing_polynomial(
468            domain, zk_rows,
469        )),
470        w: { OnceCell::with_value(zk_w(domain, zk_rows)) },
471        endo: endo_q,
472        lookup_index: None,
473        linearization,
474        powers_of_alpha,
475        zk_rows,
476    }
477}