mina_tree/proofs/
verifiers.rs

1use std::{
2    io::Read,
3    path::{Path, PathBuf},
4    sync::Arc,
5};
6
7use anyhow::Context;
8use once_cell::sync::OnceCell;
9use openmina_core::{info, log::system_time, warn};
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::srs::SRS;
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 = openmina_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#[derive(Serialize, Deserialize, Debug, Clone)]
175pub struct BlockVerifier(Arc<VerifierIndex<Fq>>);
176
177#[derive(Serialize, Deserialize, Debug, Clone)]
178pub struct TransactionVerifier(Arc<VerifierIndex<Fq>>);
179
180static BLOCK_VERIFIER: OnceCell<BlockVerifier> = OnceCell::new();
181static TX_VERIFIER: OnceCell<TransactionVerifier> = OnceCell::new();
182
183impl BlockVerifier {
184    fn kind() -> Kind {
185        Kind::BlockVerifier
186    }
187
188    fn src_json() -> &'static str {
189        let network_name = openmina_core::NetworkConfig::global().name;
190        match network_name {
191            "mainnet" => include_str!("data/mainnet_blockchain_verifier_index.json"),
192            "devnet" => include_str!("data/devnet_blockchain_verifier_index.json"),
193            other => panic!("get_verifier_index: unknown network '{other}'"),
194        }
195    }
196}
197
198impl TransactionVerifier {
199    fn kind() -> Kind {
200        Kind::TransactionVerifier
201    }
202
203    fn src_json() -> &'static str {
204        let network_name = openmina_core::NetworkConfig::global().name;
205        match network_name {
206            "mainnet" => include_str!("data/mainnet_transaction_verifier_index.json"),
207            "devnet" => include_str!("data/devnet_transaction_verifier_index.json"),
208            other => panic!("get_verifier_index: unknown network '{other}'"),
209        }
210    }
211
212    pub fn get() -> Option<Self> {
213        TX_VERIFIER.get().cloned()
214    }
215}
216
217#[cfg(not(target_family = "wasm"))]
218impl BlockVerifier {
219    pub fn make() -> Self {
220        BLOCK_VERIFIER
221            .get_or_init(|| {
222                Self(Arc::new(make_with_ext_cache(
223                    Self::kind(),
224                    Self::src_json(),
225                )))
226            })
227            .clone()
228    }
229}
230
231#[cfg(target_family = "wasm")]
232impl BlockVerifier {
233    pub async fn make() -> Self {
234        if let Some(v) = BLOCK_VERIFIER.get() {
235            v.clone()
236        } else {
237            let verifier = Self(Arc::new(
238                make_with_ext_cache(Self::kind(), Self::src_json()).await,
239            ));
240            BLOCK_VERIFIER.get_or_init(move || verifier).clone()
241        }
242    }
243}
244
245#[cfg(not(target_family = "wasm"))]
246impl TransactionVerifier {
247    pub fn make() -> Self {
248        TX_VERIFIER
249            .get_or_init(|| {
250                Self(Arc::new(make_with_ext_cache(
251                    Self::kind(),
252                    Self::src_json(),
253                )))
254            })
255            .clone()
256    }
257}
258
259#[cfg(target_family = "wasm")]
260impl TransactionVerifier {
261    pub async fn make() -> Self {
262        if let Some(v) = TX_VERIFIER.get() {
263            v.clone()
264        } else {
265            let verifier = Self(Arc::new(
266                make_with_ext_cache(Self::kind(), Self::src_json()).await,
267            ));
268            TX_VERIFIER.get_or_init(move || verifier).clone()
269        }
270    }
271}
272
273impl std::ops::Deref for BlockVerifier {
274    type Target = VerifierIndex<Fq>;
275
276    fn deref(&self) -> &Self::Target {
277        &self.0
278    }
279}
280
281impl std::ops::Deref for TransactionVerifier {
282    type Target = VerifierIndex<Fq>;
283
284    fn deref(&self) -> &Self::Target {
285        &self.0
286    }
287}
288
289impl From<BlockVerifier> for Arc<VerifierIndex<Fq>> {
290    fn from(value: BlockVerifier) -> Self {
291        value.0
292    }
293}
294
295impl From<TransactionVerifier> for Arc<VerifierIndex<Fq>> {
296    fn from(value: TransactionVerifier) -> Self {
297        value.0
298    }
299}
300
301fn make_verifier_index(index: VerifierIndex<Fq>) -> VerifierIndex<Fq> {
302    let domain = index.domain;
303    let max_poly_size: usize = index.max_poly_size;
304    let (endo, _) = endos::<Fq>();
305
306    let feature_flags = FeatureFlags {
307        range_check0: false,
308        range_check1: false,
309        foreign_field_add: false,
310        foreign_field_mul: false,
311        xor: false,
312        rot: false,
313        lookup_features: LookupFeatures {
314            patterns: LookupPatterns {
315                xor: false,
316                lookup: false,
317                range_check: false,
318                foreign_field_mul: false,
319            },
320            joint_lookup_used: false,
321            uses_runtime_tables: false,
322        },
323    };
324
325    let (mut linearization, powers_of_alpha) = expr_linearization(Some(&feature_flags), true);
326
327    let linearization = Linearization {
328        constant_term: linearization.constant_term,
329        index_terms: {
330            // Make the verifier index deterministic
331            linearization
332                .index_terms
333                .sort_by_key(|&(columns, _)| columns);
334            linearization.index_terms
335        },
336    };
337
338    // <https://github.com/o1-labs/proof-systems/blob/2702b09063c7a48131173d78b6cf9408674fd67e/kimchi/src/verifier_index.rs#L310-L314>
339    let srs = {
340        let mut srs = SRS::create(max_poly_size);
341        srs.add_lagrange_basis(domain);
342        Arc::new(srs)
343    };
344
345    // <https://github.com/o1-labs/proof-systems/blob/2702b09063c7a48131173d78b6cf9408674fd67e/kimchi/src/verifier_index.rs#L319>
346    let permutation_vanishing_polynomial_m =
347        permutation_vanishing_polynomial(domain, index.zk_rows);
348
349    // <https://github.com/o1-labs/proof-systems/blob/2702b09063c7a48131173d78b6cf9408674fd67e/kimchi/src/verifier_index.rs#L324>
350    let w = zk_w(domain, index.zk_rows);
351
352    VerifierIndex::<Fq> {
353        srs,
354        permutation_vanishing_polynomial_m: OnceCell::from(permutation_vanishing_polynomial_m),
355        w: OnceCell::from(w),
356        endo,
357        linearization,
358        powers_of_alpha,
359        ..index
360    }
361}
362
363/// <https://github.com/MinaProtocol/mina/blob/bfd1009abdbee78979ff0343cc73a3480e862f58/src/lib/crypto/kimchi_bindings/stubs/src/pasta_fq_plonk_verifier_index.rs#L213>
364/// <https://github.com/MinaProtocol/mina/blob/bfd1009abdbee78979ff0343cc73a3480e862f58/src/lib/pickles/common.ml#L16C1-L25C58>
365pub fn make_shifts(
366    domain: &Radix2EvaluationDomain<Fq>,
367) -> kimchi::circuits::polynomials::permutation::Shifts<Fq> {
368    // let value = 1 << log2_size;
369    // let domain = Domain::<Fq>::new(value).unwrap();
370    kimchi::circuits::polynomials::permutation::Shifts::new(domain)
371}
372
373// <https://github.com/MinaProtocol/mina/blob/bfd1009abdbee78979ff0343cc73a3480e862f58/src/lib/pickles/common.ml#L27>
374pub fn wrap_domains(proofs_verified: usize) -> Domains {
375    let h = match proofs_verified {
376        0 => 13,
377        1 => 14,
378        2 => 15,
379        _ => unreachable!(),
380    };
381
382    Domains {
383        h: Domain::Pow2RootsOfUnity(h),
384    }
385}
386
387/// <https://github.com/MinaProtocol/mina/blob/bfd1009abdbee78979ff0343cc73a3480e862f58/src/lib/pickles/side_loaded_verification_key.ml#L206>
388pub fn make_zkapp_verifier_index(vk: &VerificationKey) -> VerifierIndex<Fq> {
389    let d = wrap_domains(vk.actual_wrap_domain_size.to_int());
390    let log2_size = d.h.log2_size();
391
392    let public = 40; // Is that constant ?
393
394    let domain: Radix2EvaluationDomain<Fq> =
395        Radix2EvaluationDomain::new(1 << log2_size as u64).unwrap();
396
397    let srs = {
398        let degree = 1 << BACKEND_TOCK_ROUNDS_N;
399        let mut srs = SRS::<Pallas>::create(degree);
400        srs.add_lagrange_basis(domain);
401        srs
402    };
403
404    let make_poly = |poly: &InnerCurve<Fp>| poly_commitment::PolyComm {
405        elems: vec![poly.to_affine()],
406    };
407
408    let feature_flags = FeatureFlags {
409        range_check0: false,
410        range_check1: false,
411        foreign_field_add: false,
412        foreign_field_mul: false,
413        rot: false,
414        xor: false,
415        lookup_features: LookupFeatures {
416            patterns: LookupPatterns {
417                xor: false,
418                lookup: false,
419                range_check: false,
420                foreign_field_mul: false,
421            },
422            joint_lookup_used: false,
423            uses_runtime_tables: false,
424        },
425    };
426
427    let (endo_q, _endo_r) = endos::<Fq>();
428    let (linearization, powers_of_alpha) = expr_linearization(Some(&feature_flags), true);
429
430    let shift = make_shifts(&domain);
431
432    // <https://github.com/MinaProtocol/mina/blob/047375688f93546d4bdd58c75674394e3faae1f4/src/lib/pickles/side_loaded_verification_key.ml#L232>
433    let zk_rows = 3;
434
435    // Note: Verifier index is converted from OCaml here:
436    // <https://github.com/MinaProtocol/mina/blob/bfd1009abdbee78979ff0343cc73a3480e862f58/src/lib/crypto/kimchi_bindings/stubs/src/pasta_fq_plonk_verifier_index.rs#L58>
437
438    VerifierIndex::<Fq> {
439        domain,
440        max_poly_size: 1 << BACKEND_TOCK_ROUNDS_N,
441        srs: Arc::new(srs),
442        public,
443        prev_challenges: 2,
444        sigma_comm: vk.wrap_index.sigma.each_ref().map(make_poly),
445        coefficients_comm: vk.wrap_index.coefficients.each_ref().map(make_poly),
446        generic_comm: make_poly(&vk.wrap_index.generic),
447        psm_comm: make_poly(&vk.wrap_index.psm),
448        complete_add_comm: make_poly(&vk.wrap_index.complete_add),
449        mul_comm: make_poly(&vk.wrap_index.mul),
450        emul_comm: make_poly(&vk.wrap_index.emul),
451        endomul_scalar_comm: make_poly(&vk.wrap_index.endomul_scalar),
452        range_check0_comm: None,
453        range_check1_comm: None,
454        foreign_field_add_comm: None,
455        foreign_field_mul_comm: None,
456        xor_comm: None,
457        rot_comm: None,
458        shift: *shift.shifts(),
459        permutation_vanishing_polynomial_m: OnceCell::with_value(permutation_vanishing_polynomial(
460            domain, zk_rows,
461        )),
462        w: { OnceCell::with_value(zk_w(domain, zk_rows)) },
463        endo: endo_q,
464        lookup_index: None,
465        linearization,
466        powers_of_alpha,
467        zk_rows,
468    }
469}