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 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)]
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 linearization
332 .index_terms
333 .sort_by_key(|&(columns, _)| columns);
334 linearization.index_terms
335 },
336 };
337
338 let srs = {
340 let mut srs = SRS::create(max_poly_size);
341 srs.add_lagrange_basis(domain);
342 Arc::new(srs)
343 };
344
345 let permutation_vanishing_polynomial_m =
347 permutation_vanishing_polynomial(domain, index.zk_rows);
348
349 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
363pub fn make_shifts(
366 domain: &Radix2EvaluationDomain<Fq>,
367) -> kimchi::circuits::polynomials::permutation::Shifts<Fq> {
368 kimchi::circuits::polynomials::permutation::Shifts::new(domain)
371}
372
373pub 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
387pub 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; 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 let zk_rows = 3;
434
435 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}