From e4e4309a227efc4f642ab35b0ca4f770f3b07b65 Mon Sep 17 00:00:00 2001 From: Arthur Paulino Date: Mon, 17 Jul 2023 16:50:40 -0300 Subject: [PATCH] solve conflicts (#521) --- Cargo.lock | 162 +++++++++++++++ Cargo.toml | 1 + benches/end2end.rs | 10 +- benches/fibonacci.rs | 2 +- deny.toml | 1 + examples/sha256.rs | 2 +- fcomm/src/lib.rs | 2 +- src/cli/lurk_proof.rs | 108 ++++++++++ src/cli/mod.rs | 291 +++++++++++++++++++------- src/cli/paths.rs | 28 ++- src/cli/repl.rs | 331 ++++++++++++++++++++++-------- src/eval/lang.rs | 2 +- src/field.rs | 17 ++ src/proof/nova.rs | 16 +- src/public_parameters/registry.rs | 5 +- 15 files changed, 801 insertions(+), 177 deletions(-) create mode 100644 src/cli/lurk_proof.rs diff --git a/Cargo.lock b/Cargo.lock index 2b1d4545ef..db8e07426c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -160,6 +160,17 @@ dependencies = [ "wait-timeout", ] +[[package]] +name = "async-trait" +version = "0.1.71" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a564d521dd56509c4c47480d00b80ee55f7e385ae48db5744c67ad50c92d2ebf" +dependencies = [ + "proc-macro2 1.0.63", + "quote 1.0.29", + "syn 2.0.23", +] + [[package]] name = "atty" version = "0.2.14" @@ -621,6 +632,25 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +[[package]] +name = "config" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d379af7f68bfc21714c6c7dea883544201741d2ce8274bb12fa54f89507f52a7" +dependencies = [ + "async-trait", + "json5", + "lazy_static", + "nom", + "pathdiff", + "ron", + "rust-ini", + "serde", + "serde_json", + "toml", + "yaml-rust", +] + [[package]] name = "constant_time_eq" version = "0.1.5" @@ -814,6 +844,12 @@ dependencies = [ "crypto-common", ] +[[package]] +name = "dlv-list" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0688c2a7f92e427f44895cd63841bff7b29f8d7a1648b9e7e07a4a365b2e1257" + [[package]] name = "doc-comment" version = "0.3.3" @@ -1171,6 +1207,9 @@ name = "hashbrown" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +dependencies = [ + "ahash 0.7.6", +] [[package]] name = "heck" @@ -1331,6 +1370,17 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "json5" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96b0db21af676c1ce64250b5f40f3ce2cf27e4e47cb91ed91eb6fe9350b430c1" +dependencies = [ + "pest", + "pest_derive", + "serde", +] + [[package]] name = "keccak" version = "0.1.4" @@ -1361,6 +1411,12 @@ version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7012b1bbb0719e1097c47611d3898568c546d597c2e74d66f6087edd5233ff4" +[[package]] +name = "linked-hash-map" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" + [[package]] name = "linux-raw-sys" version = "0.3.8" @@ -1405,6 +1461,7 @@ dependencies = [ "blstrs", "cfg-if", "clap 4.3.11", + "config", "criterion", "dashmap", "ff", @@ -1706,6 +1763,16 @@ dependencies = [ "libc", ] +[[package]] +name = "ordered-multimap" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccd746e37177e1711c20dd619a1620f34f5c8b569c53590a72dedd5344d8924a" +dependencies = [ + "dlv-list", + "hashbrown", +] + [[package]] name = "os_str_bytes" version = "6.5.1" @@ -1781,12 +1848,62 @@ version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4b27ab7be369122c218afc2079489cdcb4b517c0a3fc386ff11e1fedfcc2b35" +[[package]] +name = "pathdiff" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" + [[package]] name = "peekmore" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9163e1259760e83d528d1b3171e5100c1767f10c52e1c4d6afad26e63d47d758" +[[package]] +name = "pest" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f73935e4d55e2abf7f130186537b19e7a4abc886a0252380b59248af473a3fc9" +dependencies = [ + "thiserror", + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aef623c9bbfa0eedf5a0efba11a5ee83209c326653ca31ff019bec3a95bfff2b" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3e8cba4ec22bada7fc55ffe51e2deb6a0e0db2d0b7ab0b103acc80d2510c190" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2 1.0.63", + "quote 1.0.29", + "syn 2.0.23", +] + +[[package]] +name = "pest_meta" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a01f71cb40bd8bb94232df14b946909e14660e33fc05db3e50ae2a82d7ea0ca0" +dependencies = [ + "once_cell", + "pest", + "sha2", +] + [[package]] name = "plotters" version = "0.3.5" @@ -2121,6 +2238,17 @@ dependencies = [ "bytemuck", ] +[[package]] +name = "ron" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88073939a61e5b7680558e6be56b419e208420c2adb92be54921fa6b72283f1a" +dependencies = [ + "base64", + "bitflags 1.3.2", + "serde", +] + [[package]] name = "rust-gpu-tools" version = "0.7.2" @@ -2138,6 +2266,16 @@ dependencies = [ "thiserror", ] +[[package]] +name = "rust-ini" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6d5f2436026b4f6e79dc829837d467cc7e9a55ee40e750d716713540715a2df" +dependencies = [ + "cfg-if", + "ordered-multimap", +] + [[package]] name = "rustacuda_core" version = "0.1.2" @@ -2589,6 +2727,15 @@ dependencies = [ "serde_json", ] +[[package]] +name = "toml" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +dependencies = [ + "serde", +] + [[package]] name = "trait-set" version = "0.3.0" @@ -2606,6 +2753,12 @@ version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" +[[package]] +name = "ucd-trie" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" + [[package]] name = "unarray" version = "0.1.4" @@ -2860,6 +3013,15 @@ dependencies = [ "tap", ] +[[package]] +name = "yaml-rust" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" +dependencies = [ + "linked-hash-map", +] + [[package]] name = "yastl" version = "0.1.2" diff --git a/Cargo.toml b/Cargo.toml index 43112c7652..39a0b231a6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,6 +20,7 @@ bellperson = { workspace = true } bincode = { workspace = true } blstrs = { workspace = true } clap = { version = "4.3.10", features = ["derive"] } +config = "0.13.3" dashmap = "5.4.0" ff = { workspace = true } generic-array = "0.14.6" diff --git a/benches/end2end.rs b/benches/end2end.rs index b4c9e1a48c..c408900735 100644 --- a/benches/end2end.rs +++ b/benches/end2end.rs @@ -272,7 +272,7 @@ fn prove_benchmark(c: &mut Criterion) { b.iter(|| { let result = prover - .prove(&pp, frames.clone(), &mut store, lang_pallas_rc.clone()) + .prove(&pp, &frames, &mut store, lang_pallas_rc.clone()) .unwrap(); black_box(result); }) @@ -307,13 +307,13 @@ fn verify_benchmark(c: &mut Criterion) { .get_evaluation_frames(ptr, empty_sym_env(&store), &mut store, limit, &lang_pallas) .unwrap(); let (proof, z0, zi, num_steps) = prover - .prove(&pp, frames, &mut store, lang_pallas_rc.clone()) + .prove(&pp, &frames, &mut store, lang_pallas_rc.clone()) .unwrap(); b.iter_batched( || z0.clone(), |z0| { - let result = proof.verify(&pp, num_steps, z0, &zi[..]).unwrap(); + let result = proof.verify(&pp, num_steps, &z0, &zi[..]).unwrap(); black_box(result); }, BatchSize::LargeInput, @@ -352,7 +352,7 @@ fn verify_compressed_benchmark(c: &mut Criterion) { .get_evaluation_frames(ptr, empty_sym_env(&store), &mut store, limit, &lang_pallas) .unwrap(); let (proof, z0, zi, num_steps) = prover - .prove(&pp, frames, &mut store, lang_pallas_rc.clone()) + .prove(&pp, &frames, &mut store, lang_pallas_rc.clone()) .unwrap(); let compressed_proof = proof.compress(&pp).unwrap(); @@ -361,7 +361,7 @@ fn verify_compressed_benchmark(c: &mut Criterion) { || z0.clone(), |z0| { let result = compressed_proof - .verify(&pp, num_steps, z0, &zi[..]) + .verify(&pp, num_steps, &z0, &zi[..]) .unwrap(); black_box(result); }, diff --git a/benches/fibonacci.rs b/benches/fibonacci.rs index a4245f6215..f05c7732d7 100644 --- a/benches/fibonacci.rs +++ b/benches/fibonacci.rs @@ -117,7 +117,7 @@ fn fibo_prove(name: &str, iterations: u64, c: &mut b.iter_batched( || (frames.clone(), lang_rc.clone()), // avoid cloning the frames in the benchmark |(frames, lang_rc)| { - let result = prover.prove(&pp, frames, &mut store, lang_rc).unwrap(); + let result = prover.prove(&pp, &frames, &mut store, lang_rc).unwrap(); black_box(result); }, BatchSize::LargeInput, diff --git a/deny.toml b/deny.toml index a75f1c925c..c9eadaf201 100644 --- a/deny.toml +++ b/deny.toml @@ -109,6 +109,7 @@ allow = [ "CC0-1.0", "Apache-2.0", "Unicode-DFS-2016", + "ISC", ] # List of explicitly disallowed licenses # See https://spdx.org/licenses/ for list of possible licenses diff --git a/examples/sha256.rs b/examples/sha256.rs index 921ac25545..41eff2fd91 100644 --- a/examples/sha256.rs +++ b/examples/sha256.rs @@ -190,7 +190,7 @@ fn main() { println!("Verifying proof..."); let verify_start = Instant::now(); - let res = proof.verify(&pp, num_steps, z0, &zi).unwrap(); + let res = proof.verify(&pp, num_steps, &z0, &zi).unwrap(); let verify_end = verify_start.elapsed(); println!("Verify took {:?}", verify_end); diff --git a/fcomm/src/lib.rs b/fcomm/src/lib.rs index 7f7c2025e0..09511d7133 100644 --- a/fcomm/src/lib.rs +++ b/fcomm/src/lib.rs @@ -931,7 +931,7 @@ impl<'a> Proof<'a, S1> { let verified = claim_iterations_and_num_steps_are_consistent && self .proof - .verify(pp, self.num_steps, public_inputs, &public_outputs) + .verify(pp, self.num_steps, &public_inputs, &public_outputs) .expect("error verifying"); let result = VerificationResult::new(verified); diff --git a/src/cli/lurk_proof.rs b/src/cli/lurk_proof.rs new file mode 100644 index 0000000000..b713bf5f7d --- /dev/null +++ b/src/cli/lurk_proof.rs @@ -0,0 +1,108 @@ +use serde::{Deserialize, Serialize}; + +use anyhow::Result; + +use lurk::{ + eval::{ + lang::{Coproc, Lang}, + Status, + }, + field::{LanguageField, LurkField}, + proof::nova, + public_parameters::public_params, + z_ptr::ZExprPtr, + z_store::ZStore, +}; + +/// A wrapper for data whose deserialization depends on a certain LurkField +#[derive(Serialize, Deserialize)] +pub struct FieldData { + field: LanguageField, + data: Vec, +} + +#[allow(dead_code)] +impl FieldData { + #[inline] + pub fn wrap(t: &T) -> Result { + Ok(Self { + field: F::FIELD, + data: bincode::serialize(t)?, + }) + } + + #[inline] + pub fn open<'a, T: Deserialize<'a>>(&'a self) -> Result { + Ok(bincode::deserialize(&self.data)?) + } +} + +/// Carries extra information to help with visualization, experiments etc +#[derive(Serialize, Deserialize)] +pub struct LurkProofMeta { + pub iterations: usize, + pub evaluation_cost: u128, + pub generation_cost: u128, + pub compression_cost: u128, + pub status: Status, + pub expression: ZExprPtr, + pub environment: ZExprPtr, + pub result: ZExprPtr, + pub zstore: ZStore, +} + +type F = pasta_curves::pallas::Scalar; // TODO: generalize this + +/// Minimal data structure containing just enough for proof verification +#[derive(Serialize, Deserialize)] +pub enum LurkProof<'a> { + Nova { + proof: nova::Proof<'a, Coproc>, + public_inputs: Vec, + public_outputs: Vec, + num_steps: usize, + rc: usize, + lang: Lang>, + }, +} + +impl<'a> LurkProof<'a> { + #[allow(dead_code)] + fn verify(self) -> Result { + match self { + Self::Nova { + proof, + public_inputs, + public_outputs, + num_steps, + rc, + lang, + } => { + log::info!("Loading public parameters"); + let pp = public_params(rc, std::sync::Arc::new(lang))?; + Ok(proof.verify(&pp, num_steps, &public_inputs, &public_outputs)?) + } + } + } + + #[allow(dead_code)] + fn print_verification(proof_id: &str, success: bool) { + if success { + println!("✓ Proof \"{proof_id}\" verified"); + } else { + println!("✗ Proof \"{proof_id}\" failed on verification"); + } + } + + #[cfg(not(target_arch = "wasm32"))] + pub fn verify_proof(proof_id: &str) -> Result<()> { + use super::paths::proof_path; + use std::{fs::File, io::BufReader}; + + let file = File::open(proof_path(proof_id))?; + let fd: FieldData = bincode::deserialize_from(BufReader::new(file))?; + let lurk_proof: LurkProof = fd.open()?; + Self::print_verification(proof_id, lurk_proof.verify()?); + Ok(()) + } +} diff --git a/src/cli/mod.rs b/src/cli/mod.rs index 8890d2c8f3..fbc22aff00 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -1,26 +1,27 @@ +mod lurk_proof; mod paths; mod repl; -use std::fs; -use std::path::PathBuf; - use anyhow::{bail, Context, Result}; +use clap::{Args, Parser, Subcommand}; +use config::{Config, Environment, File}; +use pasta_curves::pallas; +use std::{collections::HashMap, fs, path::PathBuf}; -use lurk::eval::lang::Coproc; -use lurk::field::{LanguageField, LurkField}; -use lurk::store::Store; -use lurk::z_data::{from_z_data, ZData}; -use lurk::z_store::ZStore; -use pasta_curves::{pallas, vesta}; +use lurk::{ + field::{LanguageField, LurkField}, + store::Store, + z_data::{from_z_data, ZData}, + z_store::ZStore, +}; -use clap::{Args, Parser, Subcommand}; +use crate::cli::repl::validate_non_zero; -// use self::prove_and_verify::verify_proof; -use self::repl::Repl; +use self::repl::{Backend, Repl}; -const DEFAULT_FIELD: LanguageField = LanguageField::Pallas; const DEFAULT_LIMIT: usize = 100_000_000; const DEFAULT_RC: usize = 10; +const DEFAULT_BACKEND: Backend = Backend::Nova; #[derive(Parser, Debug)] #[clap(version)] @@ -35,8 +36,8 @@ enum Command { Load(LoadArgs), /// Enters Lurk's REPL environment ("repl" can be elided) Repl(ReplArgs), - // /// Verifies a Lurk proof - // Verify(VerifyArgs), + /// Verifies a Lurk proof + Verify(VerifyArgs), } #[derive(Args, Debug)] @@ -49,16 +50,29 @@ struct LoadArgs { #[clap(long, value_parser)] zstore: Option, + /// Flag to prove the last evaluation + #[arg(long)] + prove: bool, + + /// Config file, containing the lowest precedence parameters + #[clap(long, value_parser)] + config: Option, + /// Maximum number of iterations allowed (defaults to 100_000_000) #[clap(long, value_parser)] limit: Option, - // /// Reduction count used for proofs (defaults to 10) - // #[clap(long, value_parser)] - // rc: Option, - // /// Flag to prove the last evaluation - // #[arg(long)] - // prove: bool, + /// Reduction count used for proofs (defaults to 10) + #[clap(long, value_parser)] + rc: Option, + + /// Prover backend (defaults to "Nova") + #[clap(long, value_parser)] + backend: Option, + + /// Arithmetic field (defaults to the backend's standard field) + #[clap(long, value_parser)] + field: Option, } #[derive(Parser, Debug)] @@ -69,13 +83,23 @@ struct LoadCli { #[clap(long, value_parser)] zstore: Option, + #[arg(long)] + prove: bool, + + #[clap(long, value_parser)] + config: Option, + #[clap(long, value_parser)] limit: Option, - // #[arg(long)] - // prove: bool, - // #[clap(long, value_parser)] - // rc: Option, + #[clap(long, value_parser)] + rc: Option, + + #[clap(long, value_parser)] + backend: Option, + + #[clap(long, value_parser)] + field: Option, } impl LoadArgs { @@ -83,9 +107,12 @@ impl LoadArgs { LoadCli { lurk_file: self.lurk_file, zstore: self.zstore, + prove: self.prove, + config: self.config, limit: self.limit, - // prove: self.prove, - // rc: self.rc, + rc: self.rc, + backend: self.backend, + field: self.field, } } } @@ -100,12 +127,25 @@ struct ReplArgs { #[clap(long, value_parser)] load: Option, + /// Config file, containing the lowest precedence parameters + #[clap(long, value_parser)] + config: Option, + /// Maximum number of iterations allowed (defaults to 100_000_000) #[clap(long, value_parser)] limit: Option, - // /// Reduction count used for proofs (defaults to 10) - // #[clap(long, value_parser)] - // rc: Option, + + /// Reduction count used for proofs (defaults to 10) + #[clap(long, value_parser)] + rc: Option, + + /// Prover backend (defaults to "Nova") + #[clap(long, value_parser)] + backend: Option, + + /// Arithmetic field (defaults to the backend's standard field) + #[clap(long, value_parser)] + field: Option, } #[derive(Parser, Debug)] @@ -116,10 +156,20 @@ struct ReplCli { #[clap(long, value_parser)] zstore: Option, + #[clap(long, value_parser)] + config: Option, + #[clap(long, value_parser)] limit: Option, - // #[clap(long, value_parser)] - // rc: Option, + + #[clap(long, value_parser)] + rc: Option, + + #[clap(long, value_parser)] + backend: Option, + + #[clap(long, value_parser)] + field: Option, } impl ReplArgs { @@ -127,25 +177,74 @@ impl ReplArgs { ReplCli { load: self.load, zstore: self.zstore, + config: self.config, limit: self.limit, - // rc: self.rc, + rc: self.rc, + backend: self.backend, + field: self.field, } } } -fn get_field() -> Result { - if let Ok(lurk_field) = std::env::var("LURK_FIELD") { - match lurk_field.to_lowercase().as_str() { - "bls12-381" => Ok(LanguageField::BLS12_381), - "pallas" => Ok(LanguageField::Pallas), - "vesta" => Ok(LanguageField::Vesta), - _ => bail!("Field not supported: {lurk_field}"), - } - } else { - Ok(DEFAULT_FIELD) +fn parse_backend(backend_str: &String) -> Result { + match backend_str.to_lowercase().as_str() { + "nova" => Ok(Backend::Nova), + "snarkpack+" => Ok(Backend::SnarkPackPlus), + _ => bail!("Backend not supported: {backend_str}"), } } +fn parse_field(field_str: &String) -> Result { + match field_str.to_lowercase().as_str() { + "pallas" => Ok(LanguageField::Pallas), + "vesta" => Ok(LanguageField::Vesta), + "bls12-381" => Ok(LanguageField::BLS12_381), + _ => bail!("Field not supported: {field_str}"), + } +} + +fn get_parsed_usize( + param_name: &str, + arg: &Option, + config: &HashMap, + default: usize, +) -> Result { + match arg { + Some(arg) => Ok(*arg), + None => match config.get(param_name) { + None => Ok(default), + Some(arg_str) => Ok(arg_str.parse::()?), + }, + } +} + +fn get_parsed( + param_name: &str, + arg: &Option, + config: &HashMap, + parse_fn: fn(&String) -> Result, + default: T, +) -> Result { + match arg { + Some(arg) => parse_fn(arg), + None => match config.get(param_name) { + None => Ok(default), + Some(arg) => parse_fn(arg), + }, + } +} + +fn get_config(config_path: &Option) -> Result> { + // First load from the config file + let builder = match config_path { + Some(config_path) => Config::builder().add_source(File::from(config_path.to_owned())), + None => Config::builder(), + }; + // Then potentially overwrite with environment variables + let builder = builder.add_source(Environment::with_prefix("LURK")); + Ok(builder.build()?.try_deserialize()?) +} + fn get_store serde::de::Deserialize<'a>>( zstore_path: &Option, ) -> Result> { @@ -161,31 +260,50 @@ fn get_store serde::de::Deserialize<'a>>( } macro_rules! new_repl { - ( $cli: expr, $field: path ) => {{ - let limit = $cli.limit.unwrap_or(DEFAULT_LIMIT); - // let rc = $cli.rc.unwrap_or(DEFAULT_RC); + ( $cli: expr, $limit: expr, $rc: expr, $field: path, $backend: expr ) => {{ let mut store = get_store(&$cli.zstore).with_context(|| "reading store from file")?; let env = store.nil(); - // Repl::<$field, Coproc<$field>>::new(store, env, limit, rc)? - Repl::<$field, Coproc<$field>>::new(store, env, limit, DEFAULT_RC)? + Repl::<$field>::new(store, env, $limit, $rc, $backend) }}; } impl ReplCli { pub fn run(&self) -> Result<()> { macro_rules! repl { - ( $field: path ) => {{ - let mut repl = new_repl!(self, $field); + ( $limit: expr, $rc: expr, $field: path, $backend: expr ) => {{ + let mut repl = new_repl!(self, $limit, $rc, $field, $backend); if let Some(lurk_file) = &self.load { repl.load_file(lurk_file)?; } repl.start() }}; } - match get_field()? { - LanguageField::Pallas => repl!(pallas::Scalar), - LanguageField::Vesta => repl!(vesta::Scalar), - LanguageField::BLS12_381 => repl!(blstrs::Scalar), + let config = get_config(&self.config)?; + let limit = get_parsed_usize("limit", &self.limit, &config, DEFAULT_LIMIT)?; + let rc = get_parsed_usize("rc", &self.rc, &config, DEFAULT_RC)?; + let backend = get_parsed( + "backend", + &self.backend, + &config, + parse_backend, + DEFAULT_BACKEND, + )?; + let field = get_parsed( + "field", + &self.field, + &config, + parse_field, + backend.default_field(), + )?; + validate_non_zero("limit", limit)?; + validate_non_zero("rc", rc)?; + backend.validate_field(&field)?; + match field { + LanguageField::Pallas => repl!(limit, rc, pallas::Scalar, backend), + // LanguageField::Vesta => repl!(limit, rc, vesta::Scalar, backend), + // LanguageField::BLS12_381 => repl!(limit, rc, blstrs::Scalar, backend), + LanguageField::Vesta => todo!(), + LanguageField::BLS12_381 => todo!(), } } } @@ -193,33 +311,58 @@ impl ReplCli { impl LoadCli { pub fn run(&self) -> Result<()> { macro_rules! load { - ( $field: path ) => {{ - let mut repl = new_repl!(self, $field); + ( $limit: expr, $rc: expr, $field: path, $backend: expr ) => {{ + let mut repl = new_repl!(self, $limit, $rc, $field, $backend); repl.load_file(&self.lurk_file)?; - // if self.prove { - // repl.prove_last_claim()?; - // } + if self.prove { + #[cfg(not(target_arch = "wasm32"))] + repl.prove_last_frames()?; + } Ok(()) }}; } - match get_field()? { - LanguageField::Pallas => load!(pallas::Scalar), - LanguageField::Vesta => load!(vesta::Scalar), - LanguageField::BLS12_381 => load!(blstrs::Scalar), + let config = get_config(&self.config)?; + let limit = get_parsed_usize("limit", &self.limit, &config, DEFAULT_LIMIT)?; + let rc = get_parsed_usize("rc", &self.rc, &config, DEFAULT_RC)?; + let backend = get_parsed( + "backend", + &self.backend, + &config, + parse_backend, + DEFAULT_BACKEND, + )?; + let field = get_parsed( + "field", + &self.field, + &config, + parse_field, + backend.default_field(), + )?; + validate_non_zero("limit", limit)?; + validate_non_zero("rc", rc)?; + backend.validate_field(&field)?; + match field { + LanguageField::Pallas => load!(limit, rc, pallas::Scalar, backend), + // LanguageField::Vesta => load!(limit, rc, vesta::Scalar, backend), + // LanguageField::BLS12_381 => load!(limit, rc, blstrs::Scalar, backend), + LanguageField::Vesta => todo!(), + LanguageField::BLS12_381 => todo!(), } } } -// #[derive(Args, Debug)] -// struct VerifyArgs { -// #[clap(value_parser)] -// proof_file: PathBuf, -// } +#[derive(Args, Debug)] +struct VerifyArgs { + /// ID of the proof to be verified + #[clap(value_parser)] + proof_id: String, +} /// Parses CLI arguments and continues the program flow accordingly pub fn parse_and_run() -> Result<()> { #[cfg(not(target_arch = "wasm32"))] - paths::create_lurk_dir()?; + paths::create_lurk_dirs()?; + if let Ok(repl_cli) = ReplCli::try_parse() { repl_cli.run() } else if let Ok(load_cli) = LoadCli::try_parse() { @@ -228,7 +371,15 @@ pub fn parse_and_run() -> Result<()> { match Cli::parse().command { Command::Repl(repl_args) => repl_args.into_cli().run(), Command::Load(load_args) => load_args.into_cli().run(), - // Command::Verify(verify_args) => verify_proof(&verify_args.proof_file), + #[allow(unused_variables)] + Command::Verify(verify_args) => { + #[cfg(not(target_arch = "wasm32"))] + { + use crate::cli::lurk_proof::LurkProof; + LurkProof::verify_proof(&verify_args.proof_id)?; + } + Ok(()) + } } } } diff --git a/src/cli/paths.rs b/src/cli/paths.rs index 531edf03d3..31708f6059 100644 --- a/src/cli/paths.rs +++ b/src/cli/paths.rs @@ -18,9 +18,31 @@ pub fn lurk_dir() -> PathBuf { } #[cfg(not(target_arch = "wasm32"))] -pub fn create_lurk_dir() -> Result<()> { - let dir_path = lurk_dir(); - Ok(fs::create_dir_all(dir_path)?) +pub fn proofs_dir() -> PathBuf { + lurk_dir().join(Path::new("proofs")) +} + +#[cfg(not(target_arch = "wasm32"))] +pub fn lurk_leaf_dirs() -> [PathBuf; 1] { + [proofs_dir()] +} + +#[cfg(not(target_arch = "wasm32"))] +pub fn create_lurk_dirs() -> Result<()> { + for dir in lurk_leaf_dirs() { + fs::create_dir_all(dir)?; + } + Ok(()) +} + +#[cfg(not(target_arch = "wasm32"))] +pub fn proof_path(name: &str) -> PathBuf { + proofs_dir().join(Path::new(name)).with_extension("proof") +} + +#[cfg(not(target_arch = "wasm32"))] +pub fn proof_meta_path(name: &str) -> PathBuf { + proofs_dir().join(Path::new(name)).with_extension("meta") } #[cfg(not(target_arch = "wasm32"))] diff --git a/src/cli/repl.rs b/src/cli/repl.rs index 2a81ba4b46..b6305aedd5 100644 --- a/src/cli/repl.rs +++ b/src/cli/repl.rs @@ -1,10 +1,12 @@ -use std::io; use std::path::Path; use std::sync::Arc; -use std::{fs::read_to_string, process}; + +use std::{fs::read_to_string, process, time::Instant}; use anyhow::{bail, Context, Result}; +use log::info; + use rustyline::{ error::ReadlineError, history::DefaultHistory, @@ -14,19 +16,34 @@ use rustyline::{ use rustyline_derive::{Completer, Helper, Highlighter, Hinter}; use lurk::{ - eval::{lang::Lang, Evaluator}, - field::LurkField, + eval::{ + lang::{Coproc, Lang}, + Evaluator, Frame, Witness, IO, + }, + field::{LanguageField, LurkField}, parser, ptr::Ptr, store::Store, tag::{ContTag, ExprTag}, writer::Write, Num, UInt, - {coprocessor::Coprocessor, eval::IO}, }; #[cfg(not(target_arch = "wasm32"))] -use crate::cli::paths::repl_history; +use lurk::{ + proof::{nova::NovaProver, Prover}, + public_parameters::public_params, + z_store::ZStore, +}; + +#[cfg(not(target_arch = "wasm32"))] +use std::{fs::File, io::BufWriter}; + +#[cfg(not(target_arch = "wasm32"))] +use super::{ + lurk_proof::{LurkProof, LurkProofMeta}, + paths::{proof_meta_path, proof_path}, +}; #[derive(Completer, Helper, Highlighter, Hinter)] struct InputValidator { @@ -39,64 +56,198 @@ impl Validator for InputValidator { } } -pub struct Repl> { +pub enum Backend { + Nova, + SnarkPackPlus, +} + +impl std::fmt::Display for Backend { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Nova => write!(f, "Nova"), + Self::SnarkPackPlus => write!(f, "SnarkPack+"), + } + } +} + +impl Backend { + pub fn default_field(&self) -> LanguageField { + match self { + Self::Nova => LanguageField::Pallas, + Self::SnarkPackPlus => LanguageField::BLS12_381, + } + } + + fn compatible_fields(&self) -> Vec { + use LanguageField::*; + match self { + Self::Nova => vec![Pallas, Vesta], + Self::SnarkPackPlus => vec![BLS12_381], + } + } + + pub fn validate_field(&self, field: &LanguageField) -> Result<()> { + let compatible_fields = self.compatible_fields(); + if !compatible_fields.contains(field) { + bail!( + "Backend {self} is incompatible with field {field}. Compatible fields are:\n {}", + compatible_fields + .iter() + .map(|f| f.to_string()) + .collect::>() + .join(", ") + ) + } + Ok(()) + } +} + +#[allow(dead_code)] +struct Evaluation { + frames: Vec, Witness, Coproc>>, + iterations: usize, + cost: u128, +} + +#[allow(dead_code)] +pub struct Repl { store: Store, env: Ptr, limit: usize, - lang: Arc>, - // last_claim: Option>, + lang: Arc>>, rc: usize, + backend: Backend, + evaluation: Option>, } -fn check_non_zero(name: &str, x: usize) -> Result<()> { +pub fn validate_non_zero(name: &str, x: usize) -> Result<()> { if x == 0 { bail!("`{name}` can't be zero") } Ok(()) } -/// Pads the number of iterations to the first multiple of the reduction count -/// that's equal or greater than the number of iterations +/// `pad(a, m)` returns the first multiple of `m` that's equal or greater than `a` /// -/// Panics if reduction count is zero +/// Panics if `m` is zero +#[inline] #[allow(dead_code)] -fn pad_iterations(iterations: usize, rc: usize) -> usize { - let lower = rc * (iterations / rc); - if lower < iterations { - lower + rc - } else { - lower - } +fn pad(a: usize, m: usize) -> usize { + (a + m - 1) / m * m +} + +#[allow(dead_code)] +fn timestamp() -> u128 { + use std::time::{SystemTime, UNIX_EPOCH}; + SystemTime::now() + .duration_since(UNIX_EPOCH) + .expect("We're after UNIX_EPOCH") + .as_nanos() } -impl serde::Deserialize<'de>, C: Coprocessor> - Repl -{ - pub fn new(store: Store, env: Ptr, limit: usize, rc: usize) -> Result> { - check_non_zero("limit", limit)?; - check_non_zero("rc", rc)?; - Ok(Repl { +type F = pasta_curves::pallas::Scalar; // TODO: generalize this + +impl Repl { + pub fn new(store: Store, env: Ptr, limit: usize, rc: usize, backend: Backend) -> Repl { + info!( + "Launching REPL with backend {backend} and field {}", + F::FIELD + ); + Repl { store, env, limit, - lang: Arc::new(Lang::::new()), - // last_claim: None, + lang: Arc::new(Lang::new()), rc, - }) + backend, + evaluation: None, + } } - pub fn prove_last_claim(&mut self) -> Result<()> { - Ok(()) - // match &self.last_claim { - // Some(claim) => { - // // TODO - // let _proof = prove_claim(claim); - // Ok(()) - // } - // None => { - // bail!("No claim to prove"); - // } - // } + #[cfg(not(target_arch = "wasm32"))] + pub fn prove_last_frames(&mut self) -> Result<()> { + use crate::cli::lurk_proof::FieldData; + + match self.evaluation.as_mut() { + None => bail!("No evaluation to prove"), + Some(Evaluation { + frames, + iterations, + cost, + }) => match self.backend { + Backend::Nova => { + // padding the frames, if needed + let mut n_frames = frames.len(); + let n_pad = pad(n_frames, self.rc) - n_frames; + if n_pad != 0 { + frames.extend(vec![frames[n_frames - 1].clone(); n_pad]); + n_frames = frames.len(); + } + + let prover = NovaProver::new(self.rc, (*self.lang).clone()); + + info!("Loading public parameters"); + let pp = public_params(self.rc, self.lang.clone())?; + + info!("Hydrating the store"); + self.store.hydrate_scalar_cache(); + + // saving to avoid clones + let input = &frames[0].input; + let output = &frames[*iterations].output; + let status = output.cont.into(); + let mut zstore = Some(ZStore::::default()); + let expression = self.store.get_z_expr(&input.expr, &mut zstore)?.0; + let environment = self.store.get_z_expr(&input.env, &mut zstore)?.0; + let result = self.store.get_z_expr(&output.expr, &mut zstore)?.0; + + info!("Proving and compressing"); + let start = Instant::now(); + let (proof, public_inputs, public_outputs, num_steps) = + prover.prove(&pp, frames, &mut self.store, self.lang.clone())?; + let generation = Instant::now(); + let proof = proof.compress(&pp)?; + let compression = Instant::now(); + assert_eq!(self.rc * num_steps, n_frames); + assert!(proof.verify(&pp, num_steps, &public_inputs, &public_outputs)?); + + let lurk_proof_wrap = FieldData::wrap::>(&LurkProof::Nova { + proof, + public_inputs, + public_outputs, + num_steps, + rc: self.rc, + lang: (*self.lang).clone(), + })?; + + let lurk_proof_meta_wrap = + FieldData::wrap::>(&LurkProofMeta { + iterations: *iterations, + evaluation_cost: *cost, + generation_cost: generation.duration_since(start).as_nanos(), + compression_cost: compression.duration_since(generation).as_nanos(), + status, + expression, + environment, + result, + zstore: zstore.unwrap(), + })?; + + let id = &format!("{}", timestamp()); + bincode::serialize_into( + BufWriter::new(&File::create(proof_path(id))?), + &lurk_proof_wrap, + )?; + bincode::serialize_into( + BufWriter::new(&File::create(proof_meta_path(id))?), + &lurk_proof_meta_wrap, + )?; + println!("Proof ID: \"{id}\""); + Ok(()) + } + Backend::SnarkPackPlus => todo!(), + }, + } } #[inline] @@ -201,7 +352,7 @@ impl serde::Deserialize<'de>, C: Copr } _ => bail!("Argument of `load` must be a string."), } - io::Write::flush(&mut io::stdout()).unwrap(); + std::io::Write::flush(&mut std::io::stdout()).unwrap(); } "assert" => { let first = self.peek1(cmd, args)?; @@ -274,22 +425,35 @@ impl serde::Deserialize<'de>, C: Copr } "set-limit" => { let limit = self.peek_usize(cmd, args)?; - check_non_zero("limit", limit)?; + validate_non_zero("limit", limit)?; self.limit = limit; } "set-rc" => { let rc = self.peek_usize(cmd, args)?; - check_non_zero("rc", rc)?; + validate_non_zero("rc", rc)?; self.rc = rc; } "prove" => { if !args.is_nil() { - self.eval_expr_and_set_last_claim(self.peek1(cmd, args)?)?; + self.eval_expr_and_memoize(self.peek1(cmd, args)?)?; } - self.prove_last_claim()?; + #[cfg(not(target_arch = "wasm32"))] + self.prove_last_frames()?; } "verify" => { - todo!() + #[cfg(not(target_arch = "wasm32"))] + { + let first = self.peek1(cmd, args)?; + match self.store.fetch_string(&first) { + None => bail!( + "Proof ID {} not parsed as a string", + first.fmt_to_string(&self.store) + ), + Some(proof_id) => { + LurkProof::verify_proof(&proof_id)?; + } + } + } } _ => bail!("Unsupported meta command: {cmd}"), } @@ -310,35 +474,34 @@ impl serde::Deserialize<'de>, C: Copr Ok(()) } - fn eval_expr_and_set_last_claim(&mut self, expr_ptr: Ptr) -> Result<(IO, usize)> { - self.eval_expr(expr_ptr).map(|(output, iterations, _)| { - if matches!( - output.cont.tag, - ContTag::Outermost | ContTag::Terminal | ContTag::Error - ) { - // let cont = self.store.get_cont_outermost(); - - // let claim = Claim::PtrEvaluation::(PtrEvaluation { - // expr: LurkPtr::from_ptr(&mut self.store, &expr_ptr), - // env: LurkPtr::from_ptr(&mut self.store, &self.env), - // cont: LurkCont::from_cont_ptr(&mut self.store, &cont), - // expr_out: LurkPtr::from_ptr(&mut self.store, &output.expr), - // env_out: LurkPtr::from_ptr(&mut self.store, &output.env), - // cont_out: LurkCont::from_cont_ptr(&mut self.store, &output.cont), - // status: as Evaluable, Coproc>>::status( - // &output, - // ), - // iterations: Some(pad_iterations(iterations, self.rc)), - // }); - - // self.last_claim = Some(claim); - } - (output, iterations) - }) + fn eval_expr_and_memoize(&mut self, expr_ptr: Ptr) -> Result<(IO, usize)> { + let start = Instant::now(); + let frames = Evaluator::new(expr_ptr, self.env, &mut self.store, self.limit, &self.lang) + .get_frames()?; + let cost = Instant::now().duration_since(start).as_nanos(); + + let last_idx = frames.len() - 1; + let last_frame = &frames[last_idx]; + let last_output = last_frame.output; + + let mut iterations = last_idx; + + // FIXME: proving is not working for incomplete computations + if last_frame.is_complete() { + self.evaluation = Some(Evaluation { + frames, + iterations, + cost, + }) + } else { + iterations += 1; + } + + Ok((last_output, iterations)) } fn handle_non_meta(&mut self, expr_ptr: Ptr) -> Result<()> { - self.eval_expr_and_set_last_claim(expr_ptr) + self.eval_expr_and_memoize(expr_ptr) .map(|(output, iterations)| { let prefix = if iterations != 1 { format!("[{iterations} iterations] => ") @@ -347,11 +510,9 @@ impl serde::Deserialize<'de>, C: Copr }; let suffix = match output.cont.tag { - ContTag::Outermost | ContTag::Terminal => { - output.expr.fmt_to_string(&self.store) - } + ContTag::Terminal => output.expr.fmt_to_string(&self.store), ContTag::Error => "ERROR!".into(), - _ => format!("Computation incomplete after limit: {}", self.limit), + _ => "Computation incomplete (limit reached)".into(), }; println!("{}{}", prefix, suffix); @@ -410,7 +571,7 @@ impl serde::Deserialize<'de>, C: Copr })); #[cfg(not(target_arch = "wasm32"))] - let history_path = &repl_history(); + let history_path = &crate::cli::paths::repl_history(); #[cfg(not(target_arch = "wasm32"))] if history_path.exists() { @@ -455,11 +616,11 @@ impl serde::Deserialize<'de>, C: Copr mod test { #[test] fn test_padding() { - use crate::cli::repl::pad_iterations; - assert_eq!(pad_iterations(61, 10), 70); - assert_eq!(pad_iterations(1, 10), 10); - assert_eq!(pad_iterations(61, 1), 61); - assert_eq!(pad_iterations(610, 10), 610); - assert_eq!(pad_iterations(619, 20), 620); + use crate::cli::repl::pad; + assert_eq!(pad(61, 10), 70); + assert_eq!(pad(1, 10), 10); + assert_eq!(pad(61, 1), 61); + assert_eq!(pad(610, 10), 610); + assert_eq!(pad(619, 20), 620); } } diff --git a/src/eval/lang.rs b/src/eval/lang.rs index 27e5368f1c..0b71bc55e0 100644 --- a/src/eval/lang.rs +++ b/src/eval/lang.rs @@ -76,7 +76,7 @@ pub enum Coproc { /// exact set of coprocessors to be allowed in the `Lang` struct. /// // TODO: Define a trait for the Hash and parameterize on that also. -#[derive(Debug, Default, Clone)] +#[derive(Debug, Default, Clone, Deserialize, Serialize)] pub struct Lang> { // A HashMap that stores coprocessors with their associated `Sym` keys. coprocessors: HashMap)>, diff --git a/src/field.rs b/src/field.rs index 85e9f87df7..9e98e47819 100644 --- a/src/field.rs +++ b/src/field.rs @@ -9,9 +9,13 @@ use serde::{Deserialize, Serialize}; use std::convert::TryFrom; use std::hash::Hash; +#[cfg(not(target_arch = "wasm32"))] +use lurk_macros::serde_test; #[cfg(not(target_arch = "wasm32"))] use proptest::prelude::*; #[cfg(not(target_arch = "wasm32"))] +use proptest_derive::Arbitrary; +#[cfg(not(target_arch = "wasm32"))] use rand::{rngs::StdRng, SeedableRng}; use crate::tag::{ContTag, ExprTag, Op1, Op2}; @@ -26,6 +30,9 @@ use crate::tag::{ContTag, ExprTag, Op1, Op2}; /// Because confusion on this point, perhaps combined with cargo-cult copying of incorrect previous usage has led to /// inconsistencies and inaccuracies in the code base, please prefer the named Scalar forms when correspondence to a /// named `LanguageField` is important. +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)] +#[cfg_attr(not(target_arch = "wasm32"), derive(Arbitrary))] +#[cfg_attr(not(target_arch = "wasm32"), serde_test)] pub enum LanguageField { /// The Pallas field, Pallas, @@ -35,6 +42,16 @@ pub enum LanguageField { BLS12_381, } +impl std::fmt::Display for LanguageField { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Pallas => write!(f, "Pallas"), + Self::Vesta => write!(f, "Vesta"), + Self::BLS12_381 => write!(f, "BLS12-381"), + } + } +} + /// Trait implemented by finite fields used in the language pub trait LurkField: PrimeField + PrimeFieldBits { /// The type of the field element's representation diff --git a/src/proof/nova.rs b/src/proof/nova.rs index e39e8d8a22..cad1ac682e 100644 --- a/src/proof/nova.rs +++ b/src/proof/nova.rs @@ -149,13 +149,13 @@ impl> NovaProver { pub fn prove<'a>( &'a self, pp: &'a PublicParams<'_, C>, - frames: Vec, Witness, C>>, + frames: &[Frame, Witness, C>], store: &'a mut Store, lang: Arc>, ) -> Result<(Proof<'_, C>, Vec, Vec, usize), ProofError> { let z0 = frames[0].input.to_vector(store)?; let zi = frames.last().unwrap().output.to_vector(store)?; - let circuits = MultiFrame::from_frames(self.reduction_count(), &frames, store, &lang); + let circuits = MultiFrame::from_frames(self.reduction_count(), frames, store, &lang); let num_steps = circuits.len(); let proof = Proof::prove_recursively(pp, store, &circuits, self.reduction_count, z0.clone(), lang)?; @@ -174,7 +174,7 @@ impl> NovaProver { lang: Arc>, ) -> Result<(Proof<'_, C>, Vec, Vec, usize), ProofError> { let frames = self.get_evaluation_frames(expr, env, store, limit, &lang)?; - self.prove(pp, frames, store, lang) + self.prove(pp, &frames, store, lang) } } @@ -340,7 +340,7 @@ impl<'a: 'b, 'b, C: Coprocessor> Proof<'a, C> { &self, pp: &PublicParams<'_, C>, num_steps: usize, - z0: Vec, + z0: &[S1], zi: &[S1], ) -> Result { let (z0_primary, zi_primary) = (z0, zi); @@ -348,8 +348,8 @@ impl<'a: 'b, 'b, C: Coprocessor> Proof<'a, C> { let zi_secondary = z0_secondary.clone(); let (zi_primary_verified, zi_secondary_verified) = match self { - Self::Recursive(p) => p.verify(&pp.pp, num_steps, &z0_primary, &z0_secondary), - Self::Compressed(p) => p.verify(&pp.vk, num_steps, z0_primary, z0_secondary), + Self::Recursive(p) => p.verify(&pp.pp, num_steps, z0_primary, &z0_secondary), + Self::Compressed(p) => p.verify(&pp.vk, num_steps, z0_primary.to_vec(), z0_secondary), }?; Ok(zi_primary == zi_primary_verified && zi_secondary == zi_secondary_verified) @@ -471,14 +471,14 @@ pub mod tests { .evaluate_and_prove(&pp, expr, empty_sym_env(s), s, limit, lang.clone()) .unwrap(); - let res = proof.verify(&pp, num_steps, z0.clone(), &zi); + let res = proof.verify(&pp, num_steps, &z0, &zi); if res.is_err() { dbg!(&res); } assert!(res.unwrap()); let compressed = proof.compress(&pp).unwrap(); - let res2 = compressed.verify(&pp, num_steps, z0, &zi); + let res2 = compressed.verify(&pp, num_steps, &z0, &zi); assert!(res2.unwrap()); } diff --git a/src/public_parameters/registry.rs b/src/public_parameters/registry.rs index 5b236fd029..7c85a04344 100644 --- a/src/public_parameters/registry.rs +++ b/src/public_parameters/registry.rs @@ -3,6 +3,7 @@ use std::{ sync::{Arc, Mutex}, }; +use log::info; use once_cell::sync::Lazy; use pasta_curves::pallas; use serde::{de::DeserializeOwned, Serialize}; @@ -50,13 +51,13 @@ impl Registry { let key = format!("public-params-rc-{rc}-coproc-{lang_key}"); // read the file if it exists, otherwise initialize if let Some(pp) = disk_cache.get::>(&key) { - eprintln!("Using disk-cached public params for lang {}", lang_key); + info!("Using disk-cached public params for lang {lang_key}"); Ok(Arc::new(pp)) } else { let pp = default(lang); disk_cache .set(&key, &*pp) - .tap_ok(|_| eprintln!("Writing public params to disk-cache: {}", lang_key)) + .tap_ok(|_| info!("Writing public params to disk-cache for lang {lang_key}")) .map_err(|e| Error::CacheError(format!("Disk write error: {e}")))?; Ok(pp) }