diff --git a/Cargo.lock b/Cargo.lock index db8e07426c..c51481d449 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1497,6 +1497,7 @@ dependencies = [ "rustyline", "rustyline-derive", "serde", + "serde_bytes", "serde_json", "serde_repr", "sha2", @@ -2426,6 +2427,15 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_bytes" +version = "0.11.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab33ec92f677585af6d88c65593ae2375adde54efdbf16d597f2cbc7a6d368ff" +dependencies = [ + "serde", +] + [[package]] name = "serde_derive" version = "1.0.167" diff --git a/Cargo.toml b/Cargo.toml index 39a0b231a6..ff404b5ebf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,6 +47,7 @@ rand_xorshift = "0.3.0" rayon = "1.7.0" rustyline-derive = "0.8.0" serde = { workspace = true, features = ["derive"] } +serde_bytes = "0.11.12" serde_json = { workspace = true } serde_repr = "0.1.10" tap = "1.0.1" diff --git a/fcomm/src/lib.rs b/fcomm/src/lib.rs index 09511d7133..3c355c700a 100644 --- a/fcomm/src/lib.rs +++ b/fcomm/src/lib.rs @@ -555,7 +555,7 @@ impl LurkPtr { LurkPtr::ZStorePtr(z_store_ptr) => { let z_store = &z_store_ptr.z_store; let z_ptr = z_store_ptr.z_ptr; - s.intern_z_expr_ptr(z_ptr, z_store) + s.intern_z_expr_ptr(&z_ptr, z_store) .expect("failed to intern z_ptr") } } diff --git a/src/cli/commitment.rs b/src/cli/commitment.rs new file mode 100644 index 0000000000..eb6a7d52a2 --- /dev/null +++ b/src/cli/commitment.rs @@ -0,0 +1,50 @@ +use lurk::{field::LurkField, z_ptr::ZExprPtr, z_store::ZStore}; +use serde::{Deserialize, Serialize}; + +use super::field_data::HasFieldModulus; + +/// Holds data for commitments. +/// +/// **Warning**: holds private data. The `ZStore` contains the secret used to +/// hide the original payload. +#[derive(Serialize, Deserialize)] +pub struct Commitment { + pub(crate) hidden: ZExprPtr, + pub(crate) zstore: ZStore, +} + +impl HasFieldModulus for Commitment { + fn field_modulus() -> String { + F::MODULUS.to_owned() + } +} + +#[cfg(not(target_arch = "wasm32"))] +mod non_wasm { + use anyhow::Result; + use lurk::{field::LurkField, ptr::Ptr, store::Store, z_store::ZStore}; + use serde::Serialize; + + use crate::cli::{field_data::non_wasm::dump, paths::non_wasm::commitment_path}; + + use super::Commitment; + + impl Commitment { + pub fn new(secret: F, payload: Ptr, store: &mut Store) -> Result { + let hidden = store.hide(secret, payload); + let mut zstore = Some(ZStore::::default()); + let hidden = store.get_z_expr(&hidden, &mut zstore)?.0; + Ok(Self { + hidden, + zstore: zstore.unwrap(), + }) + } + } + + impl Commitment { + #[inline] + pub fn persist(self, hash: &str) -> Result<()> { + dump(self, commitment_path(hash)) + } + } +} diff --git a/src/cli/field_data.rs b/src/cli/field_data.rs new file mode 100644 index 0000000000..e068bea7c8 --- /dev/null +++ b/src/cli/field_data.rs @@ -0,0 +1,169 @@ +use anyhow::Result; + +use serde::{de::DeserializeOwned, Deserialize, Serialize}; + +// This module implements a 2-step serde protocol for data that is parametrized +// on an arithmetic field in order to be properly deserialized. +// +// First, we serialize it to a vector of bytes. Then, we wrap the vector with a +// struct that contains the field modulus, which, in turn, is serialized to the +// final vector of bytes. +// +// When deserializing, we unwrap the vector of bytes and double check the field +// modulus for consistency. If everything goes well, we further unwrap the second +// vector of bytes. + +pub(crate) trait HasFieldModulus { + fn field_modulus() -> String; +} + +#[allow(dead_code)] +pub(crate) fn ser(t: T) -> Result> { + Ok(bincode::serialize(&FieldData(t))?) +} + +#[allow(dead_code)] +pub(crate) fn de(bytes: &[u8]) -> Result { + let FieldData(data) = bincode::deserialize(bytes)?; + Ok(data) +} + +#[cfg(not(target_arch = "wasm32"))] +pub mod non_wasm { + use super::{de, ser, HasFieldModulus}; + use anyhow::Result; + use serde::{de::DeserializeOwned, Serialize}; + use std::path::PathBuf; + + pub(crate) fn dump(t: T, path: PathBuf) -> Result<()> { + Ok(std::fs::write(path, ser(t)?)?) + } + + pub(crate) fn load(path: PathBuf) -> Result { + de(&std::fs::read(path)?) + } +} + +#[derive(Debug, PartialEq, Eq)] +struct FieldData(T); + +#[derive(Deserialize, Serialize)] +struct FieldDataWrap { + field_modulus: String, + #[serde(with = "serde_bytes")] + bytes: Vec, +} + +impl<'de, T: DeserializeOwned + HasFieldModulus> Deserialize<'de> for FieldData { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + let fdw = FieldDataWrap::deserialize(deserializer)?; + if fdw.field_modulus != T::field_modulus() { + return Err(serde::de::Error::custom("Field mismatch")); + }; + let t: T = bincode::deserialize(&fdw.bytes).map_err(serde::de::Error::custom)?; + Ok(FieldData(t)) + } +} + +impl Serialize for FieldData { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let fdw = FieldDataWrap { + field_modulus: T::field_modulus(), + bytes: bincode::serialize(&self.0).map_err(serde::ser::Error::custom)?, + }; + fdw.serialize(serializer) + } +} + +#[cfg(test)] +mod tests { + use ff::Field; + use lurk::field::LurkField; + use pasta_curves::Fq; + use serde::{Deserialize, Serialize}; + + use super::{de, ser, HasFieldModulus}; + + #[derive(PartialEq, Debug, Clone, Serialize, Deserialize)] + struct Struct { + str: String, + int: i32, + ff: F, + } + + #[derive(PartialEq, Debug, Clone, Serialize, Deserialize)] + enum Enum1 { + CaseStr(String), + CaseInt(i32), + CaseFF(F), + } + + impl HasFieldModulus for Struct { + fn field_modulus() -> String { + F::MODULUS.to_string() + } + } + + impl HasFieldModulus for Enum1 { + fn field_modulus() -> String { + F::MODULUS.to_string() + } + } + + #[test] + fn struct_roundtrips() { + let s = Struct { + str: "hi".into(), + int: 42, + ff: Fq::double(&Fq::ONE), + }; + assert_eq!(s, de(&ser(s.clone()).unwrap()).unwrap()) + } + + #[test] + fn enum1_roundtrips() { + let e11 = Enum1::CaseStr("bye".into()); + let e12 = Enum1::CaseInt(11); + let e13 = Enum1::CaseFF(Fq::double(&Fq::double(&Fq::ONE))); + for e in [e11, e12, e13] { + assert_eq!(e, de(&ser(e.clone()).unwrap()).unwrap()); + } + } + + /// An enum can be deserialized to another, if the enum constructor has the + /// same index and uses the same inner data + #[test] + fn stable_enum() { + #[derive(PartialEq, Debug, Clone, Serialize, Deserialize)] + enum Enum2 { + CaseStr2(String), + CaseInt2(i32), + CaseFF2(F), + Foo, + } + + impl HasFieldModulus for Enum2 { + fn field_modulus() -> String { + F::MODULUS.to_string() + } + } + let e11 = Enum1::CaseStr("bye".into()); + let e12 = Enum1::CaseInt(11); + let e13 = Enum1::CaseFF(Fq::double(&Fq::double(&Fq::ONE))); + + let e21 = Enum2::CaseStr2("bye".into()); + let e22 = Enum2::CaseInt2(11); + let e23 = Enum2::CaseFF2(Fq::double(&Fq::double(&Fq::ONE))); + + for (e1, e2) in [(e11, e21), (e12, e22), (e13, e23)] { + assert_eq!(e2.clone(), de(&ser(e1.clone()).unwrap()).unwrap()); + assert_eq!(e1, de(&ser(e2).unwrap()).unwrap()); + } + } +} diff --git a/src/cli/lurk_proof.rs b/src/cli/lurk_proof.rs index b713bf5f7d..2225fbc962 100644 --- a/src/cli/lurk_proof.rs +++ b/src/cli/lurk_proof.rs @@ -1,61 +1,48 @@ use serde::{Deserialize, Serialize}; -use anyhow::Result; - use lurk::{ - eval::{ - lang::{Coproc, Lang}, - Status, - }, - field::{LanguageField, LurkField}, + coprocessor::Coprocessor, + eval::lang::{Coproc, Lang}, + field::LurkField, proof::nova, - public_parameters::public_params, - z_ptr::ZExprPtr, + z_ptr::{ZContPtr, ZExprPtr}, z_store::ZStore, }; -/// A wrapper for data whose deserialization depends on a certain LurkField +use super::field_data::HasFieldModulus; + +/// Carries extra information to help with visualization, experiments etc. +/// +/// Note: the `ZStore` in this struct only has enough data to recover the meaning +/// of the claim being proven: `expr`, when evaluated in the context of `env` and +/// continuation `cont`, is reduced to `expr_out`, resulting on environment +/// `env_out` and continuation `cont_out`. It doesn't contain private data. #[derive(Serialize, Deserialize)] -pub struct FieldData { - field: LanguageField, - data: Vec, +pub struct LurkProofMeta { + pub(crate) iterations: usize, + pub(crate) expr: ZExprPtr, + pub(crate) env: ZExprPtr, + pub(crate) cont: ZContPtr, + pub(crate) expr_out: ZExprPtr, + pub(crate) env_out: ZExprPtr, + pub(crate) cont_out: ZContPtr, + pub(crate) zstore: ZStore, } -#[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)?) +impl HasFieldModulus for LurkProofMeta { + fn field_modulus() -> String { + F::MODULUS.to_owned() } } -/// 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 +type Pallas = pasta_curves::pallas::Scalar; // TODO: generalize this /// Minimal data structure containing just enough for proof verification #[derive(Serialize, Deserialize)] -pub enum LurkProof<'a> { +pub enum LurkProof<'a, F: LurkField> +where + Coproc: Coprocessor, +{ Nova { proof: nova::Proof<'a, Coproc>, public_inputs: Vec, @@ -66,43 +53,73 @@ pub enum LurkProof<'a> { }, } -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)?) - } +impl<'a, F: LurkField> HasFieldModulus for LurkProof<'a, F> +where + Coproc: Coprocessor, +{ + fn field_modulus() -> String { + F::MODULUS.to_owned() + } +} + +#[cfg(not(target_arch = "wasm32"))] +mod non_wasm { + use crate::cli::{ + field_data::non_wasm::{dump, load}, + paths::non_wasm::{proof_meta_path, proof_path}, + }; + use anyhow::Result; + use lurk::{ + coprocessor::Coprocessor, eval::lang::Coproc, field::LurkField, + public_parameters::public_params, + }; + use serde::Serialize; + + use super::{LurkProof, LurkProofMeta, Pallas}; + + impl LurkProofMeta { + #[inline] + pub fn persist(self, id: &str) -> Result<()> { + dump(self, proof_meta_path(id)) } } - #[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"); + impl<'a, F: LurkField + Serialize> LurkProof<'a, F> + where + Coproc: Coprocessor, + { + #[inline] + pub fn persist(self, id: &str) -> Result<()> { + dump(self, proof_path(id)) } } - #[cfg(not(target_arch = "wasm32"))] - pub fn verify_proof(proof_id: &str) -> Result<()> { - use super::paths::proof_path; - use std::{fs::File, io::BufReader}; + impl<'a> LurkProof<'a, Pallas> { + 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)?) + } + } + } - 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(()) + pub fn verify_proof(proof_id: &str) -> Result<()> { + let lurk_proof: LurkProof<'_, Pallas> = load(proof_path(proof_id))?; + if lurk_proof.verify()? { + println!("✓ Proof \"{proof_id}\" verified"); + } else { + println!("✗ Proof \"{proof_id}\" failed on verification"); + } + Ok(()) + } } } diff --git a/src/cli/mod.rs b/src/cli/mod.rs index fbc22aff00..0fbda97f30 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -1,3 +1,5 @@ +mod commitment; +mod field_data; mod lurk_proof; mod paths; mod repl; @@ -361,7 +363,7 @@ struct VerifyArgs { /// Parses CLI arguments and continues the program flow accordingly pub fn parse_and_run() -> Result<()> { #[cfg(not(target_arch = "wasm32"))] - paths::create_lurk_dirs()?; + paths::non_wasm::create_lurk_dirs()?; if let Ok(repl_cli) = ReplCli::try_parse() { repl_cli.run() diff --git a/src/cli/paths.rs b/src/cli/paths.rs index 31708f6059..e416eada26 100644 --- a/src/cli/paths.rs +++ b/src/cli/paths.rs @@ -1,51 +1,52 @@ #[cfg(not(target_arch = "wasm32"))] -use anyhow::Result; +pub mod non_wasm { + use anyhow::Result; -#[cfg(not(target_arch = "wasm32"))] -use std::{ - fs, - path::{Path, PathBuf}, -}; + use std::{ + fs, + path::{Path, PathBuf}, + }; -#[cfg(not(target_arch = "wasm32"))] -fn home_dir() -> PathBuf { - home::home_dir().expect("missing home directory") -} + fn home_dir() -> PathBuf { + home::home_dir().expect("missing home directory") + } -#[cfg(not(target_arch = "wasm32"))] -pub fn lurk_dir() -> PathBuf { - home_dir().join(Path::new(".lurk")) -} + pub fn lurk_dir() -> PathBuf { + home_dir().join(Path::new(".lurk")) + } -#[cfg(not(target_arch = "wasm32"))] -pub fn proofs_dir() -> PathBuf { - lurk_dir().join(Path::new("proofs")) -} + 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()] -} + pub fn commits_dir() -> PathBuf { + lurk_dir().join(Path::new("commits")) + } -#[cfg(not(target_arch = "wasm32"))] -pub fn create_lurk_dirs() -> Result<()> { - for dir in lurk_leaf_dirs() { - fs::create_dir_all(dir)?; + pub fn lurk_leaf_dirs() -> [PathBuf; 2] { + [proofs_dir(), commits_dir()] } - Ok(()) -} -#[cfg(not(target_arch = "wasm32"))] -pub fn proof_path(name: &str) -> PathBuf { - proofs_dir().join(Path::new(name)).with_extension("proof") -} + 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_meta_path(name: &str) -> PathBuf { - proofs_dir().join(Path::new(name)).with_extension("meta") -} + pub fn repl_history() -> PathBuf { + lurk_dir().join(Path::new("repl-history")) + } -#[cfg(not(target_arch = "wasm32"))] -pub fn repl_history() -> PathBuf { - lurk_dir().join(Path::new("repl-history")) + pub fn commitment_path(name: &str) -> PathBuf { + commits_dir().join(Path::new(name)) + } + + pub fn proof_path(name: &str) -> PathBuf { + proofs_dir().join(Path::new(name)).with_extension("proof") + } + + pub fn proof_meta_path(name: &str) -> PathBuf { + proofs_dir().join(Path::new(name)).with_extension("meta") + } } diff --git a/src/cli/repl.rs b/src/cli/repl.rs index b6305aedd5..34e6bddd4a 100644 --- a/src/cli/repl.rs +++ b/src/cli/repl.rs @@ -1,7 +1,7 @@ use std::path::Path; use std::sync::Arc; -use std::{fs::read_to_string, process, time::Instant}; +use std::{fs::read_to_string, process}; use anyhow::{bail, Context, Result}; @@ -37,13 +37,7 @@ use lurk::{ }; #[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}, -}; +use super::lurk_proof::{LurkProof, LurkProofMeta}; #[derive(Completer, Helper, Highlighter, Hinter)] struct InputValidator { @@ -106,7 +100,6 @@ impl Backend { struct Evaluation { frames: Vec, Witness, Coproc>>, iterations: usize, - cost: u128, } #[allow(dead_code)] @@ -136,15 +129,6 @@ 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() -} - type F = pasta_curves::pallas::Scalar; // TODO: generalize this impl Repl { @@ -164,85 +148,126 @@ impl Repl { } } + #[allow(dead_code)] + fn proof_claim( + store: &mut Store, + exprs: (Ptr, Ptr), + envs: (Ptr, Ptr), + conts: ((F, F), (F, F)), + ) -> Ptr { + let expr_key = store.key("expr"); + let env_key = store.key("env"); + let cont_key = store.key("cont"); + let expr_out_key = store.key("expr-out"); + let env_out_key = store.key("env-out"); + let cont_out_key = store.key("cont-out"); + let cont_tag = store.num(Num::Scalar(conts.0 .0)); + let cont_val = store.num(Num::Scalar(conts.0 .1)); + let cont = store.cons(cont_tag, cont_val); + let cont_out_tag = store.num(Num::Scalar(conts.1 .0)); + let cont_out_val = store.num(Num::Scalar(conts.1 .1)); + let cont_out = store.cons(cont_out_tag, cont_out_val); + store.list(&[ + expr_key, + exprs.0, + env_key, + envs.0, + cont_key, + cont, + expr_out_key, + exprs.1, + env_out_key, + envs.1, + cont_out_key, + cont_out, + ]) + } + #[cfg(not(target_arch = "wasm32"))] pub fn prove_last_frames(&mut self) -> Result<()> { - use crate::cli::lurk_proof::FieldData; + use ff::Field; + + use crate::cli::{commitment::Commitment, paths::non_wasm::proof_path}; match self.evaluation.as_mut() { None => bail!("No evaluation to prove"), - Some(Evaluation { - frames, - iterations, - cost, - }) => match self.backend { + Some(Evaluation { frames, iterations }) => 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 { + let expr = self.store.get_z_expr(&input.expr, &mut zstore)?.0; + let env = self.store.get_z_expr(&input.env, &mut zstore)?.0; + let cont = self.store.get_z_cont(&input.cont, &mut zstore)?.0; + let expr_out = self.store.get_z_expr(&output.expr, &mut zstore)?.0; + let env_out = self.store.get_z_expr(&output.env, &mut zstore)?.0; + let cont_out = self.store.get_z_cont(&output.cont, &mut zstore)?.0; + + let proof_claim = Self::proof_claim( + &mut self.store, + (input.expr, output.expr), + (input.env, output.env), + (cont.parts(), cont_out.parts()), + ); + let commitment = Commitment::new(F::ZERO, proof_claim, &mut self.store)?; + let proof_id = &commitment.hidden.value().hex_digits(); + + let proof_path = proof_path(proof_id); + + if proof_path.exists() { + info!("Proof already cached"); + // TODO: make sure that the proof file is not corrupted + } else { + info!("Proof not cached"); + // 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(); + } + + info!("Loading public parameters"); + let pp = public_params(self.rc, self.lang.clone())?; + + let prover = NovaProver::new(self.rc, (*self.lang).clone()); + + info!("Proving and compressing"); + let (proof, public_inputs, public_outputs, num_steps) = + prover.prove(&pp, frames, &mut self.store, self.lang.clone())?; + let proof = proof.compress(&pp)?; + assert_eq!(self.rc * num_steps, n_frames); + assert!(proof.verify(&pp, num_steps, &public_inputs, &public_outputs)?); + + let lurk_proof = LurkProof::Nova { + proof, + public_inputs, + public_outputs, + num_steps, + rc: self.rc, + lang: (*self.lang).clone(), + }; + + let lurk_proof_meta = 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, + expr, + env, + cont, + expr_out, + env_out, + cont_out, 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}\""); + }; + + lurk_proof.persist(proof_id)?; + lurk_proof_meta.persist(proof_id)?; + commitment.persist(proof_id)?; + } + println!("Proof ID: \"{proof_id}\""); Ok(()) } Backend::SnarkPackPlus => todo!(), @@ -250,6 +275,44 @@ impl Repl { } } + #[cfg(not(target_arch = "wasm32"))] + fn hide(&mut self, secret: F, payload: Ptr) -> Result<()> { + use super::commitment::Commitment; + + let commitment = Commitment::new(secret, payload, &mut self.store)?; + let hash = &commitment.hidden.value().hex_digits(); + commitment.persist(hash)?; + println!( + "Data: {}\nHash: 0x{hash}", + payload.fmt_to_string(&self.store) + ); + Ok(()) + } + + #[cfg(not(target_arch = "wasm32"))] + fn fetch(&mut self, hash: &str, print_data: bool) -> Result<()> { + use super::{ + commitment::Commitment, field_data::non_wasm::load, paths::non_wasm::commitment_path, + }; + + let commitment: Commitment = load(commitment_path(hash))?; + if commitment.hidden.value().hex_digits() != hash { + bail!("Hash mismatch. Corrupted commitment file.") + } else { + let comm = self + .store + .intern_z_expr_ptr(&commitment.hidden, &commitment.zstore) + .unwrap(); + if print_data { + let data = self.store.fetch_comm(&comm).unwrap().1; + println!("{}", data.fmt_to_string(&self.store)); + } else { + println!("Data is now available"); + } + } + Ok(()) + } + #[inline] fn eval_expr(&mut self, expr_ptr: Ptr) -> Result<(IO, usize, Vec>)> { Ok(Evaluator::new(expr_ptr, self.env, &mut self.store, self.limit, &self.lang).eval()?) @@ -292,6 +355,21 @@ impl Repl { } } + #[allow(dead_code)] + fn get_comm_hash(&mut self, cmd: &str, args: &Ptr) -> Result { + let first = self.peek1(cmd, args)?; + let n = self.store.lurk_sym("num"); + let expr = self.store.list(&[n, first]); + let (expr_io, ..) = self + .eval_expr(expr) + .with_context(|| "evaluating first arg")?; + let hash = self + .store + .fetch_num(&expr_io.expr) + .expect("must be a number"); + Ok(hash.into_scalar().hex_digits()) + } + fn handle_meta_cases(&mut self, cmd: &str, args: &Ptr, pwd_path: &Path) -> Result<()> { match cmd { "def" => { @@ -416,6 +494,44 @@ impl Repl { process::exit(1); } } + "lurk.commit" => { + #[cfg(not(target_arch = "wasm32"))] + { + let first = self.peek1(cmd, args)?; + let (first_io, ..) = self.eval_expr(first)?; + self.hide(ff::Field::ZERO, first_io.expr)?; + } + } + "lurk.hide" => { + #[cfg(not(target_arch = "wasm32"))] + { + let (first, second) = self.peek2(cmd, args)?; + let (first_io, ..) = self + .eval_expr(first) + .with_context(|| "evaluating first arg")?; + let (second_io, ..) = self + .eval_expr(second) + .with_context(|| "evaluating second arg")?; + let Some(secret) = self.store.fetch_num(&first_io.expr) else { + bail!("Secret must be a number. Got {}", first_io.expr.fmt_to_string(&self.store)) + }; + self.hide(secret.into_scalar(), second_io.expr)?; + } + } + "fetch" => { + #[cfg(not(target_arch = "wasm32"))] + { + let hash = self.get_comm_hash(cmd, args)?; + self.fetch(&hash, false)?; + } + } + "lurk.open" => { + #[cfg(not(target_arch = "wasm32"))] + { + let hash = self.get_comm_hash(cmd, args)?; + self.fetch(&hash, true)?; + } + } "clear" => self.env = self.store.nil(), "set-env" => { // The state's env is set to the result of evaluating the first argument. @@ -475,10 +591,8 @@ impl Repl { } 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]; @@ -488,11 +602,7 @@ impl Repl { // FIXME: proving is not working for incomplete computations if last_frame.is_complete() { - self.evaluation = Some(Evaluation { - frames, - iterations, - cost, - }) + self.evaluation = Some(Evaluation { frames, iterations }) } else { iterations += 1; } @@ -571,7 +681,7 @@ impl Repl { })); #[cfg(not(target_arch = "wasm32"))] - let history_path = &crate::cli::paths::repl_history(); + let history_path = &crate::cli::paths::non_wasm::repl_history(); #[cfg(not(target_arch = "wasm32"))] if history_path.exists() { diff --git a/src/store.rs b/src/store.rs index 95c8c97954..5de0450b0f 100644 --- a/src/store.rs +++ b/src/store.rs @@ -1441,26 +1441,30 @@ impl Store { } } - pub fn intern_z_expr_ptr(&mut self, z_ptr: ZExprPtr, z_store: &ZStore) -> Option> { - if let Some(ptr) = self.fetch_z_expr_ptr(&z_ptr) { + pub fn intern_z_expr_ptr( + &mut self, + z_ptr: &ZExprPtr, + z_store: &ZStore, + ) -> Option> { + if let Some(ptr) = self.fetch_z_expr_ptr(z_ptr) { Some(ptr) } else { use ZExpr::*; - match (z_ptr.tag(), z_store.get_expr(&z_ptr)) { + match (z_ptr.tag(), z_store.get_expr(z_ptr)) { (ExprTag::Nil, Some(Nil)) => { let ptr = self.lurk_sym("nil"); self.create_z_expr_ptr(ptr, *z_ptr.value()); Some(ptr) } (ExprTag::Cons, Some(Cons(car, cdr))) => { - let car = self.intern_z_expr_ptr(car, z_store)?; - let cdr = self.intern_z_expr_ptr(cdr, z_store)?; + let car = self.intern_z_expr_ptr(&car, z_store)?; + let cdr = self.intern_z_expr_ptr(&cdr, z_store)?; let ptr = self.intern_cons(car, cdr); self.create_z_expr_ptr(ptr, *z_ptr.value()); Some(ptr) } (ExprTag::Comm, Some(Comm(secret, payload))) => { - let payload = self.intern_z_expr_ptr(payload, z_store)?; + let payload = self.intern_z_expr_ptr(&payload, z_store)?; let ptr = self.intern_comm(secret, payload); self.create_z_expr_ptr(ptr, *z_ptr.value()); Some(ptr) @@ -1471,8 +1475,8 @@ impl Store { Some(ptr) } (ExprTag::Str, Some(Str(strcar, strcdr))) => { - let strcar = self.intern_z_expr_ptr(strcar, z_store)?; - let strcdr = self.intern_z_expr_ptr(strcdr, z_store)?; + let strcar = self.intern_z_expr_ptr(&strcar, z_store)?; + let strcdr = self.intern_z_expr_ptr(&strcdr, z_store)?; let ptr = self.intern_strcons(strcar, strcdr); self.create_z_expr_ptr(ptr, *z_ptr.value()); Some(ptr) @@ -1483,15 +1487,15 @@ impl Store { Some(ptr) } (ExprTag::Sym, Some(Sym(symcar, symcdr))) => { - let symcar = self.intern_z_expr_ptr(symcar, z_store)?; - let symcdr = self.intern_z_expr_ptr(symcdr, z_store)?; + let symcar = self.intern_z_expr_ptr(&symcar, z_store)?; + let symcdr = self.intern_z_expr_ptr(&symcdr, z_store)?; let ptr = self.intern_symcons(symcar, symcdr); self.create_z_expr_ptr(ptr, *z_ptr.value()); Some(ptr) } (ExprTag::Key, Some(Key(keycar, keycdr))) => { - let keycar = self.intern_z_expr_ptr(keycar, z_store)?; - let keycdr = self.intern_z_expr_ptr(keycdr, z_store)?; + let keycar = self.intern_z_expr_ptr(&keycar, z_store)?; + let keycdr = self.intern_z_expr_ptr(&keycdr, z_store)?; let ptr = self.intern_keycons(keycar, keycdr); self.create_z_expr_ptr(ptr, *z_ptr.value()); Some(ptr) @@ -1504,8 +1508,8 @@ impl Store { (ExprTag::Char, Some(Char(x))) => Some(x.into()), (ExprTag::U64, Some(UInt(x))) => Some(self.intern_uint(x)), (ExprTag::Thunk, Some(Thunk(value, continuation))) => { - let value = self.intern_z_expr_ptr(value, z_store)?; - let continuation = self.intern_z_cont_ptr(continuation, z_store)?; + let value = self.intern_z_expr_ptr(&value, z_store)?; + let continuation = self.intern_z_cont_ptr(&continuation, z_store)?; let ptr = self.intern_thunk(expr::Thunk { value, continuation, @@ -1521,9 +1525,9 @@ impl Store { closed_env, }), ) => { - let arg = self.intern_z_expr_ptr(arg, z_store)?; - let body = self.intern_z_expr_ptr(body, z_store)?; - let env = self.intern_z_expr_ptr(closed_env, z_store)?; + let arg = self.intern_z_expr_ptr(&arg, z_store)?; + let body = self.intern_z_expr_ptr(&body, z_store)?; + let env = self.intern_z_expr_ptr(&closed_env, z_store)?; let ptr = self.intern_fun(arg, body, env); self.create_z_expr_ptr(ptr, *z_ptr.value()); Some(ptr) @@ -1549,13 +1553,13 @@ impl Store { pub fn intern_z_cont_ptr( &mut self, - z_ptr: ZContPtr, + z_ptr: &ZContPtr, z_store: &ZStore, ) -> Option> { use ZCont::*; let tag: ContTag = z_ptr.tag(); - if let Some(cont) = z_store.get_cont(&z_ptr) { + if let Some(cont) = z_store.get_cont(z_ptr) { let continuation = match cont { Outermost => Continuation::Outermost, Dummy => Continuation::Dummy, @@ -1564,48 +1568,48 @@ impl Store { saved_env, continuation, } => Continuation::Call0 { - saved_env: self.intern_z_expr_ptr(saved_env, z_store)?, - continuation: self.intern_z_cont_ptr(continuation, z_store)?, + saved_env: self.intern_z_expr_ptr(&saved_env, z_store)?, + continuation: self.intern_z_cont_ptr(&continuation, z_store)?, }, Call { saved_env, unevaled_arg, continuation, } => Continuation::Call { - saved_env: self.intern_z_expr_ptr(saved_env, z_store)?, - unevaled_arg: self.intern_z_expr_ptr(unevaled_arg, z_store)?, - continuation: self.intern_z_cont_ptr(continuation, z_store)?, + saved_env: self.intern_z_expr_ptr(&saved_env, z_store)?, + unevaled_arg: self.intern_z_expr_ptr(&unevaled_arg, z_store)?, + continuation: self.intern_z_cont_ptr(&continuation, z_store)?, }, Call2 { saved_env, function, continuation, } => Continuation::Call2 { - saved_env: self.intern_z_expr_ptr(saved_env, z_store)?, - function: self.intern_z_expr_ptr(function, z_store)?, - continuation: self.intern_z_cont_ptr(continuation, z_store)?, + saved_env: self.intern_z_expr_ptr(&saved_env, z_store)?, + function: self.intern_z_expr_ptr(&function, z_store)?, + continuation: self.intern_z_cont_ptr(&continuation, z_store)?, }, Tail { saved_env, continuation, } => Continuation::Tail { - saved_env: self.intern_z_expr_ptr(saved_env, z_store)?, - continuation: self.intern_z_cont_ptr(continuation, z_store)?, + saved_env: self.intern_z_expr_ptr(&saved_env, z_store)?, + continuation: self.intern_z_cont_ptr(&continuation, z_store)?, }, Error => Continuation::Error, Lookup { saved_env, continuation, } => Continuation::Lookup { - saved_env: self.intern_z_expr_ptr(saved_env, z_store)?, - continuation: self.intern_z_cont_ptr(continuation, z_store)?, + saved_env: self.intern_z_expr_ptr(&saved_env, z_store)?, + continuation: self.intern_z_cont_ptr(&continuation, z_store)?, }, Unop { operator, continuation, } => Continuation::Unop { operator, - continuation: self.intern_z_cont_ptr(continuation, z_store)?, + continuation: self.intern_z_cont_ptr(&continuation, z_store)?, }, Binop { operator, @@ -1614,9 +1618,9 @@ impl Store { continuation, } => Continuation::Binop { operator, - saved_env: self.intern_z_expr_ptr(saved_env, z_store)?, - unevaled_args: self.intern_z_expr_ptr(unevaled_args, z_store)?, - continuation: self.intern_z_cont_ptr(continuation, z_store)?, + saved_env: self.intern_z_expr_ptr(&saved_env, z_store)?, + unevaled_args: self.intern_z_expr_ptr(&unevaled_args, z_store)?, + continuation: self.intern_z_cont_ptr(&continuation, z_store)?, }, Binop2 { operator, @@ -1624,15 +1628,15 @@ impl Store { continuation, } => Continuation::Binop2 { operator, - evaled_arg: self.intern_z_expr_ptr(evaled_arg, z_store)?, - continuation: self.intern_z_cont_ptr(continuation, z_store)?, + evaled_arg: self.intern_z_expr_ptr(&evaled_arg, z_store)?, + continuation: self.intern_z_cont_ptr(&continuation, z_store)?, }, If { unevaled_args, continuation, } => Continuation::If { - unevaled_args: self.intern_z_expr_ptr(unevaled_args, z_store)?, - continuation: self.intern_z_cont_ptr(continuation, z_store)?, + unevaled_args: self.intern_z_expr_ptr(&unevaled_args, z_store)?, + continuation: self.intern_z_cont_ptr(&continuation, z_store)?, }, Let { var, @@ -1640,10 +1644,10 @@ impl Store { saved_env, continuation, } => Continuation::Let { - var: self.intern_z_expr_ptr(var, z_store)?, - body: self.intern_z_expr_ptr(body, z_store)?, - saved_env: self.intern_z_expr_ptr(saved_env, z_store)?, - continuation: self.intern_z_cont_ptr(continuation, z_store)?, + var: self.intern_z_expr_ptr(&var, z_store)?, + body: self.intern_z_expr_ptr(&body, z_store)?, + saved_env: self.intern_z_expr_ptr(&saved_env, z_store)?, + continuation: self.intern_z_cont_ptr(&continuation, z_store)?, }, LetRec { var, @@ -1651,13 +1655,13 @@ impl Store { saved_env, continuation, } => Continuation::LetRec { - var: self.intern_z_expr_ptr(var, z_store)?, - body: self.intern_z_expr_ptr(body, z_store)?, - saved_env: self.intern_z_expr_ptr(saved_env, z_store)?, - continuation: self.intern_z_cont_ptr(continuation, z_store)?, + var: self.intern_z_expr_ptr(&var, z_store)?, + body: self.intern_z_expr_ptr(&body, z_store)?, + saved_env: self.intern_z_expr_ptr(&saved_env, z_store)?, + continuation: self.intern_z_cont_ptr(&continuation, z_store)?, }, Emit { continuation } => Continuation::Emit { - continuation: self.intern_z_cont_ptr(continuation, z_store)?, + continuation: self.intern_z_cont_ptr(&continuation, z_store)?, }, }; @@ -1892,10 +1896,10 @@ impl ZStore { let mut store = Store::new(); for ptr in self.expr_map.keys() { - store.intern_z_expr_ptr(*ptr, self); + store.intern_z_expr_ptr(ptr, self); } for ptr in self.cont_map.keys() { - store.intern_z_cont_ptr(*ptr, self); + store.intern_z_cont_ptr(ptr, self); } store } @@ -1904,12 +1908,12 @@ impl ZStore { let mut store = Store::new(); for ptr in self.expr_map.keys() { - store.intern_z_expr_ptr(*ptr, self); + store.intern_z_expr_ptr(ptr, self); } for ptr in self.cont_map.keys() { - store.intern_z_cont_ptr(*ptr, self); + store.intern_z_cont_ptr(ptr, self); } - match store.intern_z_expr_ptr(*z_ptr, self) { + match store.intern_z_expr_ptr(z_ptr, self) { Some(ptr_ret) => Ok((store, ptr_ret)), None => Err(Error("Couldn't find given ZExprPtr".into())), } diff --git a/src/z_data/z_ptr.rs b/src/z_data/z_ptr.rs index 6279b639de..eb611d3705 100644 --- a/src/z_data/z_ptr.rs +++ b/src/z_data/z_ptr.rs @@ -101,6 +101,10 @@ impl ZPtr { &self.1 } + pub fn parts(&self) -> (F, F) { + (self.tag_field(), self.1) + } + // TODO: Create a permanent format for ZPtr strings/ZIDs /// Converts the ZPtr to a base32-encoded string pub fn to_base32(&self) -> String {