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 slice.read_exact(&mut d).context("reading source digest")?;
83 if d != $digest {
84 anyhow::bail!("source digest verification failed");
85 }
86
87 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)]
177pub struct BlockVerifier(Arc<VerifierIndex<Fq>>);
178
179#[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 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 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 linearization
340 .index_terms
341 .sort_by_key(|&(columns, _)| columns);
342 linearization.index_terms
343 },
344 };
345
346 let srs = {
348 let srs = SRS::create(max_poly_size);
349 srs.get_lagrange_basis(domain);
350 Arc::new(srs)
351 };
352
353 let permutation_vanishing_polynomial_m =
355 permutation_vanishing_polynomial(domain, index.zk_rows);
356
357 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
371pub fn make_shifts(
374 domain: &Radix2EvaluationDomain<Fq>,
375) -> kimchi::circuits::polynomials::permutation::Shifts<Fq> {
376 kimchi::circuits::polynomials::permutation::Shifts::new(domain)
379}
380
381pub 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
395pub 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; 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 let zk_rows = 3;
442
443 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}