From 6045c596f2e136aca58e248c993d85a370f983f9 Mon Sep 17 00:00:00 2001 From: Dan Dore Date: Thu, 14 Mar 2024 04:47:12 -0700 Subject: [PATCH] Add PCS types and basic setup flow using SRS (#28) * add basic setup flow using SRS * merge with main * some small fixes to bugs in the compression module from recent PRs * spartan integration fixes * tests for srs generation * sets default srs size to 19 * use `deserialize_compressed_unchecked` * remove unnecessary blinding factor from SRS generation * fix * adds SRS to git lfs * add git lfs to CI workflow * sample test srs in CI * install nexus-tools in CI * ignore tests related to large SRS * remove SRS generation from CI * merge fixes * fmt * fixes from review * small fix * remove file-manipulating tests * Adds a cli option to the prover crate for compression (#55) * testing proof deserialization * proofs save and verify * removed cargo.lock * todo: merge dorebell onto this * fixed merge mistakes * reads proof and compresses. key not yet saved to file * added back cargo.lock * moved the compression cli into prover crate * formatting * remove whitespace * added com option to prove * updated local prove * todo:save key and proof to file * derive CanonicalSerialize+CanonicalDeserialize for Spartan types * add options to save and load spartan key from file * save compressed proof to file, implement arkworks serialization * clippy * remove SRS generation from CI * ignore spartan_encode_test * integrate compression cli with recent version of nexus-tools * add cli function to sample test SRS * forgot to add new files * small compression UI fixes * add spartan setup command to main 'cargo nexus' * bump number of SRS vars to 27 * minor fix * review fixes * another round of review fixes * read pp and srs from default cache locations if unspecified during compression * add helper function to get minimum srs size for a given k --------- Co-authored-by: Dan Dore * fix broken edit links in docs (#99) * CCS implementation (#52) * Initial CCS implementation. * Remove direct construction interfaces so that everything goes through R1CS. * Trim more, Fold multipliers together, and inline satisfaction checking. * Fix formatting. * Precompute products. * Remove direct CCS construction. * Add mle helpers. * Start to integrate polynomial commitments. * Shading closer to polynomial commitments. * Initial stab at relating various polynomial types and traits. * Finish utility functions. * Fix endianness and ranges and get tests passing. * Fix formatting. * Realized there's a better way to invoke the partially fixed polynomial. * Update interfaces and some additional reworking. * Resolve clippy. * Unify shapes. * Product renaming Co-authored-by: Dan Dore * Revert "Unify shapes." This reverts commit 3463e430c863f5711d698373c991c297ee3a3a17. * Move to polynomial/poly commitment implementations from Spartan repo. * Move to unified matrix-based model. * Fix tests. * Fix fmt. * Remove files accidently restored during rebase. * Move to using polynomial commitment exlcusively. * Fix formatting. --------- Co-authored-by: Dan Dore * All Contributors Setup (#120) * Update README.md * Update README.md * Create .all-contributorsrc * docs: add nexus-xyz as a contributor for code (#121) * docs: update README.md * docs: update .all-contributorsrc --------- Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com> * Switch Contributors (#122) * Delete .all-contributorsrc * Update README.md * fix merge conflicts --------- Co-authored-by: Guru Vamsi Policharla Co-authored-by: Daniel Marin <60114322+danielmarinq@users.noreply.github.com> Co-authored-by: Samuel Judson Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com> --- .gitignore | 3 + config/src/vm.rs | 5 + network/src/api.rs | 17 +- network/src/bin/pcdnode/db.rs | 2 +- network/src/bin/pcdnode/main.rs | 2 +- network/src/bin/pcdnode/post.rs | 21 +- network/src/client.rs | 9 +- network/src/lib.rs | 2 +- network/src/pcd.rs | 2 +- .../nova/pcd/compression/conversion.rs | 17 ++ .../circuits/nova/pcd/compression/error.rs | 43 ++++ nova/src/circuits/nova/pcd/compression/mod.rs | 36 +++- nova/src/folding/nova/cyclefold/nimfs/mod.rs | 53 +++++ .../folding/nova/cyclefold/secondary/mod.rs | 2 + nova/src/folding/nova/nifs.rs | 22 ++ nova/src/lib.rs | 2 +- prover/Cargo.toml | 4 +- prover/examples/prove.rs | 2 +- prover/src/error.rs | 28 ++- prover/src/key.rs | 146 +++++++++++++ prover/src/lib.rs | 204 +++++++++++++----- prover/src/main.rs | 160 +++++++++++++- prover/src/pp.rs | 135 +++++++++--- prover/src/srs.rs | 70 ++++++ prover/src/types.rs | 20 +- spartan/src/crr1cs.rs | 1 + spartan/src/errors.rs | 29 +++ .../zeromorph/data_structures.rs | 3 +- spartan/src/polycommitments/zeromorph/mod.rs | 6 +- spartan/src/product_tree.rs | 4 +- spartan/src/sparse_mlpoly.rs | 6 +- tools/Cargo.toml | 1 + tools/src/command/compress.rs | 91 ++++++++ tools/src/command/mod.rs | 4 + tools/src/command/prove.rs | 60 +++--- tools/src/command/public_params.rs | 78 ++++++- tools/src/command/spartan_key.rs | 98 +++++++++ tools/src/command/verify.rs | 111 +++++++++- .../tools-dev/src/command/common/compress.rs | 25 +++ tools/tools-dev/src/command/common/mod.rs | 9 +- tools/tools-dev/src/command/common/prove.rs | 4 + .../src/command/common/public_params.rs | 29 +++ .../src/command/common/spartan_key.rs | 42 ++++ tools/tools-dev/src/command/common/verify.rs | 8 + .../src/command/dev/common_impl/prove.rs | 6 +- .../command/dev/common_impl/public_params.rs | 17 +- .../src/command/dev/common_impl/run.rs | 4 +- .../src/command/dev/common_impl/verify.rs | 21 +- tools/tools-dev/src/command/dev/config.rs | 2 +- 49 files changed, 1475 insertions(+), 191 deletions(-) create mode 100644 prover/src/key.rs create mode 100644 prover/src/srs.rs create mode 100644 tools/src/command/compress.rs create mode 100644 tools/src/command/spartan_key.rs create mode 100644 tools/tools-dev/src/command/common/compress.rs create mode 100644 tools/tools-dev/src/command/common/spartan_key.rs diff --git a/.gitignore b/.gitignore index 488d5cd9d..8338e28d8 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,6 @@ Cargo.lock .*.swp .config.env + +# public parameter files +*.zst diff --git a/config/src/vm.rs b/config/src/vm.rs index c9f12bc03..81696babf 100644 --- a/config/src/vm.rs +++ b/config/src/vm.rs @@ -18,6 +18,10 @@ pub enum NovaImpl { #[serde(rename = "par")] #[cfg_attr(feature = "clap_derive", clap(name = "par"))] Parallel, + + #[serde(rename = "par-com")] + #[cfg_attr(feature = "clap_derive", clap(name = "par-com"))] + ParallelCompressible, } impl fmt::Display for NovaImpl { @@ -25,6 +29,7 @@ impl fmt::Display for NovaImpl { match self { NovaImpl::Sequential => write!(f, "seq"), NovaImpl::Parallel => write!(f, "par"), + NovaImpl::ParallelCompressible => write!(f, "par-com"), } } } diff --git a/network/src/api.rs b/network/src/api.rs index c89be250a..33ea56ac5 100644 --- a/network/src/api.rs +++ b/network/src/api.rs @@ -1,14 +1,5 @@ use serde::{Deserialize, Serialize}; -#[derive(Serialize, Deserialize)] -pub enum NexusAPI { - Program { account: String, elf: Vec }, - Query { hash: String }, - Proof(Proof), - Error(String), -} -pub use NexusAPI::*; - #[derive(Default, Clone, Serialize, Deserialize)] pub struct Proof { pub hash: String, @@ -36,3 +27,11 @@ impl std::fmt::Display for Proof { Ok(()) } } + +#[derive(Serialize, Deserialize)] +pub enum NexusAPI { + Program { account: String, elf: Vec }, + Query { hash: String }, + NexusProof(Proof), + Error(String), +} diff --git a/network/src/bin/pcdnode/db.rs b/network/src/bin/pcdnode/db.rs index c892a5b94..560a5b058 100644 --- a/network/src/bin/pcdnode/db.rs +++ b/network/src/bin/pcdnode/db.rs @@ -1,7 +1,7 @@ use std::collections::HashMap; use std::sync::{Arc, Mutex}; -use nexus_network::api::Proof; +use crate::api::Proof; #[derive(Clone, Default)] pub struct DB(Arc>); diff --git a/network/src/bin/pcdnode/main.rs b/network/src/bin/pcdnode/main.rs index d106067e7..f3bffe7b3 100644 --- a/network/src/bin/pcdnode/main.rs +++ b/network/src/bin/pcdnode/main.rs @@ -86,7 +86,7 @@ async fn main() -> Result<()> { let opts = Opts::parse(); - let pp = gen_or_load(false, 0, &opts.pp_file)?; + let pp = gen_or_load(false, 0, &opts.pp_file, None)?; let state = WorkerState::new(pp); start_local_workers(state.clone())?; diff --git a/network/src/bin/pcdnode/post.rs b/network/src/bin/pcdnode/post.rs index b43b7e92b..117038853 100644 --- a/network/src/bin/pcdnode/post.rs +++ b/network/src/bin/pcdnode/post.rs @@ -1,18 +1,25 @@ use std::collections::VecDeque; use std::sync::Arc; +use nexus_network::{ + api::{NexusAPI, Proof}, + pcd::{ + encode, + NexusMsg::{self, LeafReq, NodeReq, PCDRes}, + }, + Result, +}; use sha2::{Digest, Sha256}; use hyper::{header, Body, Request, Response, StatusCode}; use tokio::task::JoinHandle; -use nexus_network::api::*; -use nexus_network::pcd::*; -use nexus_network::*; +use crate::{ + api::NexusAPI::{Error, NexusProof, Program, Query}, + request_work, WorkerState, LOG_TARGET, +}; use nexus_vm::{eval::NexusVM, riscv::translate_elf_bytes, trace::trace}; -use crate::workers::*; - pub fn manage_proof(mut state: WorkerState, hash: String, mut vm: NexusVM) -> Result<()> { let trace = Arc::new(trace(&mut vm, 1, true)?); @@ -96,7 +103,7 @@ fn api(mut state: WorkerState, msg: NexusAPI) -> Result { let vm = translate_elf_bytes(&elf)?; let hash = hex::encode(Sha256::digest(&elf)); manage_proof(state, hash.clone(), vm)?; - Ok(Proof(Proof { hash, ..Proof::default() })) + Ok(NexusProof(Proof { hash, ..Proof::default() })) } Query { hash } => { tracing::info!( @@ -106,7 +113,7 @@ fn api(mut state: WorkerState, msg: NexusAPI) -> Result { let proof = state.db.query_proof(&hash); match proof { None => Err("proof not found".into()), - Some(p) => Ok(Proof(p)), + Some(p) => Ok(NexusProof(p)), } } _ => Err("Invalid Message".into()), diff --git a/network/src/client.rs b/network/src/client.rs index ee90e0f6b..a5df97e40 100644 --- a/network/src/client.rs +++ b/network/src/client.rs @@ -5,8 +5,11 @@ use hyper::body::{Buf, HttpBody}; use hyper::client::HttpConnector; use tokio::runtime; -use crate::api::*; -use crate::Result; +use crate::client::NexusAPI::{Error, NexusProof, Program, Query}; +use crate::{ + api::{NexusAPI, Proof}, + Result, +}; pub const LOG_TARGET: &str = "nexus-network::client"; @@ -56,7 +59,7 @@ impl Client { .map_err(|_err| "request failed".to_owned())??; match response { - Proof(p) => Ok(p), + NexusProof(p) => Ok(p), Error(m) => Err(m.into()), _ => Err("unexpected response".into()), } diff --git a/network/src/lib.rs b/network/src/lib.rs index 9f5d75b98..72aeb93e2 100644 --- a/network/src/lib.rs +++ b/network/src/lib.rs @@ -5,6 +5,6 @@ pub mod pcd; pub mod ws; pub type DynError = Box; -pub type Result = std::result::Result; +pub type Result = std::result::Result; pub const LOG_TARGET: &str = "nexus-network"; diff --git a/network/src/pcd.rs b/network/src/pcd.rs index 9ee60c826..dfdba16aa 100644 --- a/network/src/pcd.rs +++ b/network/src/pcd.rs @@ -215,7 +215,7 @@ mod test { #[ignore] fn round_trip_node() { let circuit = nop_circuit(3).unwrap(); - let pp: ParPP = gen_pp(&circuit).unwrap(); + let pp: ParPP = gen_pp(&circuit, &()).unwrap(); let n0 = PCDNode::prove_leaf(&pp, &circuit, 0, &circuit.input(0).unwrap()).unwrap(); let n2 = PCDNode::prove_leaf(&pp, &circuit, 2, &circuit.input(2).unwrap()).unwrap(); let n = PCDNode::prove_parent(&pp, &circuit, &n0, &n2).unwrap(); diff --git a/nova/src/circuits/nova/pcd/compression/conversion.rs b/nova/src/circuits/nova/pcd/compression/conversion.rs index 1daad8abc..64ed415b2 100644 --- a/nova/src/circuits/nova/pcd/compression/conversion.rs +++ b/nova/src/circuits/nova/pcd/compression/conversion.rs @@ -5,6 +5,7 @@ use ark_spartan::{ polycommitments::PolyCommitmentScheme, Assignment, Instance, }; +use ark_std::{error::Error, fmt::Display}; use super::PolyVectorCommitment; use crate::{ @@ -23,6 +24,22 @@ impl From for ConversionError { } } +impl Error for ConversionError { + fn source(&self) -> Option<&(dyn Error + 'static)> { + match self { + ConversionError::ConversionError(e) => Some(e), + } + } +} + +impl Display for ConversionError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::ConversionError(e) => write!(f, "Conversion error: {e}"), + } + } +} + impl TryFrom> for CRR1CSShape where G: SWCurveConfig, diff --git a/nova/src/circuits/nova/pcd/compression/error.rs b/nova/src/circuits/nova/pcd/compression/error.rs index 247fd189e..f504f380b 100644 --- a/nova/src/circuits/nova/pcd/compression/error.rs +++ b/nova/src/circuits/nova/pcd/compression/error.rs @@ -1,5 +1,6 @@ use ark_relations::r1cs::SynthesisError; use ark_spartan::errors::ProofVerifyError; +use ark_std::{error::Error, fmt::Display}; use super::conversion::ConversionError; pub use crate::folding::nova::cyclefold::Error as NovaError; @@ -53,3 +54,45 @@ impl From for SpartanError { Self::InvalidProof(ProofError::InvalidSpartanProof(error)) } } + +impl Error for SpartanError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + SpartanError::ConversionError(e) => Some(e), + SpartanError::FoldingError(e) => Some(e), + SpartanError::InvalidProof(e) => Some(e), + } + } +} + +impl Display for SpartanError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + SpartanError::ConversionError(e) => write!(f, "{}", e), + SpartanError::FoldingError(e) => write!(f, "{}", e), + SpartanError::InvalidProof(e) => write!(f, "{}", e), + } + } +} + +impl Error for ProofError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + Self::InvalidProof => None, + Self::InvalidPublicInput => None, + Self::InvalidSpartanProof(e) => Some(e), + Self::SecondaryCircuitNotSatisfied => None, + } + } +} + +impl Display for ProofError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::InvalidProof => write!(f, "Invalid proof"), + Self::InvalidPublicInput => write!(f, "Invalid public input"), + Self::InvalidSpartanProof(e) => write!(f, "{}", e), + Self::SecondaryCircuitNotSatisfied => write!(f, "Secondary circuit not satisfied"), + } + } +} diff --git a/nova/src/circuits/nova/pcd/compression/mod.rs b/nova/src/circuits/nova/pcd/compression/mod.rs index aa63898e9..62f5da66d 100644 --- a/nova/src/circuits/nova/pcd/compression/mod.rs +++ b/nova/src/circuits/nova/pcd/compression/mod.rs @@ -9,7 +9,7 @@ use ark_ec::{ use ark_ff::PrimeField; use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; use ark_spartan::{ - committed_relaxed_snark as ark_spartan_snark, committed_relaxed_snark::CRSNARKKey as SNARKGens, + committed_relaxed_snark as spartan_snark, committed_relaxed_snark::CRSNARKKey as SNARKGens, crr1csproof::CRR1CSShape, polycommitments::PolyCommitmentScheme, ComputationCommitment, ComputationDecommitment, }; @@ -24,6 +24,7 @@ use crate::{ NIMFSProof, R1CSInstance, RelaxedR1CSInstance, RelaxedR1CSWitness, }, nova::pcd::{augmented::SQUEEZE_NATIVE_ELEMENTS_NUM, PCDNode}, + r1cs::R1CSShape, StepCircuit, LOG_TARGET, }; @@ -38,6 +39,7 @@ pub use error::{ProofError, SpartanError}; pub type PVC = PolyVectorCommitment, PC>; +#[derive(CanonicalSerialize, CanonicalDeserialize)] pub struct CompressedPCDProof where G1: SWCurveConfig, @@ -60,7 +62,7 @@ where pub W_secondary_prime: RelaxedR1CSWitness, - pub spartan_proof: ark_spartan_snark::SNARK, PC>, + pub spartan_proof: spartan_snark::SNARK, PC>, pub folding_proof: NIMFSProof, C2, RO>, _random_oracle: PhantomData, @@ -75,6 +77,29 @@ pub struct SNARKKey> { snark_gens: SNARKGens, } +impl> SNARKKey { + /// convenience function to derive the minimum log size of the SRS + /// needed to support compession for a given `shape`. + pub fn get_min_srs_size(shape: &R1CSShape) -> usize { + let R1CSShape { + num_constraints, + num_vars, + num_io, + A, + B, + C, + } = shape; + // spartan uses the convention that num_inputs does not include the leading `u`. + let num_inputs = num_io - 1; + let num_nz_entries = max(A.len(), max(B.len(), C.len())); + SNARKGens::::get_min_num_vars( + *num_constraints, + *num_vars, + num_inputs, + num_nz_entries, + ) + } +} pub struct SNARK where G1: SWCurveConfig, @@ -115,7 +140,6 @@ where let PublicParams { shape: _shape, .. } = pp; // converts the R1CSShape from this crate into a CRR1CSShape from the Spartan crate let shape: CRR1CSShape = _shape.clone().try_into()?; - // the `try_into()` call above pads the number of constraints, variables, and inputs let (num_cons, num_vars, num_inputs) = ( shape.get_num_cons(), shape.get_num_vars(), @@ -125,7 +149,7 @@ where let num_nz_entries = max(_shape.A.len(), max(_shape.B.len(), _shape.C.len())); let snark_gens = SNARKGens::new(srs, num_cons, num_vars, num_inputs, num_nz_entries); let (computation_comm, computation_decomm) = - ark_spartan_snark::SNARK::, PC>::encode(&shape.inst, &snark_gens); + spartan_snark::SNARK::, PC>::encode(&shape.inst, &snark_gens); Ok(SNARKKey { shape, computation_comm, @@ -174,7 +198,7 @@ where let mut transcript = Transcript::new(b"spartan_snark"); // Now, we use Spartan to prove knowledge of the witness `W_prime` // for the committed relaxed r1cs instance `U_prime` - let spartan_proof = ark_spartan_snark::SNARK::, PC>::prove( + let spartan_proof = spartan_snark::SNARK::, PC>::prove( shape, &U_prime.try_into()?, W_prime.try_into()?, @@ -259,7 +283,7 @@ where // Finally, we verify the Spartan proof for the committed relaxed r1cs instance `U_prime`. let mut transcript = Transcript::new(b"spartan_snark"); - ark_spartan_snark::SNARK::, PC>::verify( + spartan_snark::SNARK::, PC>::verify( spartan_proof, &key.computation_comm, &U_prime.try_into()?, diff --git a/nova/src/folding/nova/cyclefold/nimfs/mod.rs b/nova/src/folding/nova/cyclefold/nimfs/mod.rs index 1f684f645..b030edcf7 100644 --- a/nova/src/folding/nova/cyclefold/nimfs/mod.rs +++ b/nova/src/folding/nova/cyclefold/nimfs/mod.rs @@ -1,6 +1,7 @@ use ark_crypto_primitives::sponge::{Absorb, CryptographicSponge}; use ark_ec::short_weierstrass::{Projective, SWCurveConfig}; use ark_ff::PrimeField; +use ark_serialize::{CanonicalDeserialize, CanonicalSerialize, Valid}; use ark_std::Zero; use super::{secondary, Error}; @@ -22,6 +23,7 @@ pub(crate) type RelaxedR1CSInstance = r1cs::RelaxedR1CSInstance = r1cs::RelaxedR1CSWitness>; /// Non-interactive multi-folding scheme proof. +#[derive(CanonicalSerialize)] pub struct NIMFSProof< G1: SWCurveConfig, G2: SWCurveConfig, @@ -35,6 +37,57 @@ pub struct NIMFSProof< pub(crate) proof_secondary: NIFSProof, C2, RO>, } +impl Valid for NIMFSProof +where + G1: SWCurveConfig, + G2: SWCurveConfig, + C1: CommitmentScheme>, + C2: CommitmentScheme>, + RO: Sync, +{ + fn check(&self) -> Result<(), ark_serialize::SerializationError> { + self.commitment_T.check()?; + self.commitment_E_proof[0].check()?; + self.commitment_E_proof[1].check()?; + self.commitment_W_proof.check()?; + self.proof_secondary.check() + } +} + +impl CanonicalDeserialize for NIMFSProof +where + G1: SWCurveConfig, + G2: SWCurveConfig, + C1: CommitmentScheme>, + C2: CommitmentScheme>, + RO: Sync, +{ + fn deserialize_with_mode( + mut reader: R, + compress: ark_serialize::Compress, + validate: ark_serialize::Validate, + ) -> Result { + let commitment_T = C1::Commitment::deserialize_with_mode(&mut reader, compress, validate)?; + let commitment_E_proof = [ + secondary::Proof::::deserialize_with_mode(&mut reader, compress, validate)?, + secondary::Proof::::deserialize_with_mode(&mut reader, compress, validate)?, + ]; + let commitment_W_proof = + secondary::Proof::::deserialize_with_mode(&mut reader, compress, validate)?; + let proof_secondary = NIFSProof::, C2, RO>::deserialize_with_mode( + &mut reader, + compress, + validate, + )?; + Ok(Self { + commitment_T, + commitment_E_proof, + commitment_W_proof, + proof_secondary, + }) + } +} + impl Clone for NIMFSProof where G1: SWCurveConfig, diff --git a/nova/src/folding/nova/cyclefold/secondary/mod.rs b/nova/src/folding/nova/cyclefold/secondary/mod.rs index b8179bdcf..b975243f7 100644 --- a/nova/src/folding/nova/cyclefold/secondary/mod.rs +++ b/nova/src/folding/nova/cyclefold/secondary/mod.rs @@ -16,6 +16,7 @@ use ark_r1cs_std::{ use ark_relations::r1cs::{ ConstraintSynthesizer, ConstraintSystem, ConstraintSystemRef, SynthesisError, SynthesisMode, }; +use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; use ark_std::Zero; use crate::commitment::CommitmentScheme; @@ -123,6 +124,7 @@ where } /// Folding scheme proof for a secondary circuit. +#[derive(CanonicalDeserialize, CanonicalSerialize)] pub struct Proof>> { pub(crate) U: R1CSInstance, pub(crate) commitment_T: C2::Commitment, diff --git a/nova/src/folding/nova/nifs.rs b/nova/src/folding/nova/nifs.rs index fd34bd124..555183453 100644 --- a/nova/src/folding/nova/nifs.rs +++ b/nova/src/folding/nova/nifs.rs @@ -3,6 +3,7 @@ use std::marker::PhantomData; use ark_crypto_primitives::sponge::{Absorb, CryptographicSponge, FieldElementSize}; use ark_ec::CurveGroup; use ark_ff::{PrimeField, ToConstraintField}; +use ark_serialize::{CanonicalDeserialize, CanonicalSerialize, SerializationError, Valid}; use crate::{ absorb::{AbsorbNonNative, CryptographicSpongeExt}, @@ -12,11 +13,32 @@ use crate::{ pub const SQUEEZE_ELEMENTS_BIT_SIZE: FieldElementSize = FieldElementSize::Truncated(127); +#[derive(CanonicalSerialize)] pub struct NIFSProof, RO> { pub(crate) commitment_T: C::Commitment, _random_oracle: PhantomData, } +impl, RO: Sync> Valid for NIFSProof { + fn check(&self) -> Result<(), SerializationError> { + self.commitment_T.check() + } +} + +impl, RO: Sync> CanonicalDeserialize for NIFSProof { + fn deserialize_with_mode( + mut reader: R, + compress: ark_serialize::Compress, + validate: ark_serialize::Validate, + ) -> Result { + let commitment_T = C::Commitment::deserialize_with_mode(&mut reader, compress, validate)?; + Ok(Self { + commitment_T, + _random_oracle: PhantomData, + }) + } +} + impl, RO> Default for NIFSProof { fn default() -> Self { Self { diff --git a/nova/src/lib.rs b/nova/src/lib.rs index c96a93949..941d224ba 100644 --- a/nova/src/lib.rs +++ b/nova/src/lib.rs @@ -9,7 +9,7 @@ mod provider; mod sparse; mod utils; -mod circuits; +pub mod circuits; mod folding; mod gadgets; diff --git a/prover/Cargo.toml b/prover/Cargo.toml index 61576aab9..0b341341b 100644 --- a/prover/Cargo.toml +++ b/prover/Cargo.toml @@ -15,11 +15,14 @@ clap.workspace = true #rayon = "1.8" snmalloc-rs = { version = "0.3.4", optional = true } +anyhow = "1.0" + zstd = { version = "0.12", default-features = false } nexus-vm = { path = "../vm" } nexus-nova = { path = "../nova", features = ["spartan"] } nexus-tui = { path = "../tools/tui" } +spartan = { path = "../spartan", package = "ark-spartan" } tracing = "0.1" tracing-subscriber = { version = "0.3.18", features = ["env-filter"] } @@ -36,7 +39,6 @@ ark-bn254.workspace = true ark-grumpkin.workspace = true [dev-dependencies] -anyhow = "1.0" nexus-config = { path = "../config" } [features] diff --git a/prover/examples/prove.rs b/prover/examples/prove.rs index 46a473fd3..85581761e 100644 --- a/prover/examples/prove.rs +++ b/prover/examples/prove.rs @@ -21,7 +21,7 @@ fn main() -> anyhow::Result<()> { .init(); tracing::info!("Setting up public parameters..."); - let public_params = nexus_prover::pp::gen_vm_pp(CONFIG.k)?; + let public_params = nexus_prover::pp::gen_vm_pp(CONFIG.k, &())?; // Run the program. let vm_opts = VMOpts { diff --git a/prover/src/error.rs b/prover/src/error.rs index 09d5666e6..e8e8508ca 100644 --- a/prover/src/error.rs +++ b/prover/src/error.rs @@ -3,7 +3,7 @@ use std::fmt::{self, Debug, Display, Formatter}; pub use ark_relations::r1cs::SynthesisError; pub use ark_serialize::SerializationError; -pub use nexus_nova::nova::Error as NovaError; +pub use nexus_nova::nova::{pcd::compression::SpartanError, Error as NovaError}; pub use nexus_nova::r1cs::Error as R1CSError; pub use nexus_vm::error::NexusVMError; @@ -33,6 +33,18 @@ pub enum ProofError { /// The Nova prover produced an invalid proof NovaProofError, + + /// SRS for polynomial commitment scheme is missing + MissingSRS, + + /// An error occured while sampling the test SRS + SRSSamplingError, + + /// An error occured while running the Spartan compression prover + CompressionError(SpartanError), + + /// A proof has been read from a file that does not match the expected format + InvalidProofFormat, } use ProofError::*; @@ -70,6 +82,12 @@ impl From for ProofError { } } +impl From for ProofError { + fn from(x: SpartanError) -> ProofError { + CompressionError(x) + } +} + impl Error for ProofError { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match self { @@ -81,6 +99,10 @@ impl Error for ProofError { InvalidPP => None, InvalidIndex(_) => None, NovaProofError => None, + MissingSRS => None, + SRSSamplingError => None, + CompressionError(e) => Some(e), + InvalidProofFormat => None, } } } @@ -96,6 +118,10 @@ impl Display for ProofError { InvalidPP => write!(f, "invalid public parameters"), InvalidIndex(i) => write!(f, "invalid step index {i}"), NovaProofError => write!(f, "invalid Nova proof"), + MissingSRS => write!(f, "missing SRS"), + SRSSamplingError => write!(f, "error sampling test SRS"), + CompressionError(e) => write!(f, "{e}"), + InvalidProofFormat => write!(f, "invalid proof format"), } } } diff --git a/prover/src/key.rs b/prover/src/key.rs new file mode 100644 index 000000000..268119fba --- /dev/null +++ b/prover/src/key.rs @@ -0,0 +1,146 @@ +use std::fs::File; +use zstd::stream::{Decoder, Encoder}; + +pub use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; +use nexus_nova::nova::pcd::compression::SNARK; + +use crate::error::*; +use crate::pp::load_pp; +use crate::srs::load_srs; +use crate::types::*; +use crate::{LOG_TARGET, TERMINAL_MODE}; + +pub fn gen_key(pp: &ComPP, srs: &SRS) -> Result { + let key = SNARK::setup(pp, srs)?; + Ok(key) +} + +pub fn save_key(key: SpartanKey, file: &str) -> Result<(), ProofError> { + let f = File::create(file)?; + let mut enc = Encoder::new(&f, 0)?; + key.serialize_compressed(&mut enc)?; + enc.finish()?; + f.sync_all()?; + Ok(()) +} + +pub fn load_key(file: &str) -> Result { + let f = File::open(file)?; + let mut dec = Decoder::new(&f)?; + let key = SpartanKey::deserialize_compressed_unchecked(&mut dec)?; + Ok(key) +} + +pub fn gen_key_to_file(pp_file: &str, srs_file: &str, key_file: &str) -> Result<(), ProofError> { + tracing::info!( + target: LOG_TARGET, + path =?srs_file, + "Reading the SRS", + ); + let mut term = nexus_tui::TerminalHandle::new(TERMINAL_MODE); + let srs = { + let mut term_ctx = term.context("Loading").on_step(|_step| "SRS".into()); + let _guard = term_ctx.display_step(); + + load_srs(srs_file)? + }; + + tracing::info!( + target: LOG_TARGET, + path =?srs_file, + "SRS found for a maximum of {} variables", + srs.max_num_vars + ); + + tracing::info!( + target: LOG_TARGET, + pp_file =?pp_file, + "Reading the Nova public parameters", + ); + + let pp: ComPP = { + let mut term_ctx = term + .context("Loading") + .on_step(|_step| "Nova public parameters".into()); + let _guard = term_ctx.display_step(); + + load_pp(pp_file)? + }; + + tracing::info!( + target: LOG_TARGET, + key_file =?key_file, + "Generating Spartan key parameters", + ); + + let mut term_ctx = term + .context("Generating") + .on_step(|_step| "Spartan key".into()); + let _guard = term_ctx.display_step(); + + let key: SpartanKey = gen_key(&pp, &srs)?; + save_key(key, key_file) +} + +pub fn gen_or_load_key( + gen: bool, + key_file: &str, + pp_file: Option<&str>, + srs_file: Option<&str>, +) -> Result { + let mut term = nexus_tui::TerminalHandle::new(TERMINAL_MODE); + let key: SpartanKey = if gen { + tracing::info!( + target: LOG_TARGET, + key_file =?key_file, + "Generating Spartan key parameters", + ); + + let srs_file = srs_file.ok_or(ProofError::MissingSRS)?; + tracing::info!( + target: LOG_TARGET, + path =?srs_file, + "Reading the SRS", + ); + let srs = { + let mut term_ctx = term.context("Loading").on_step(|_step| "SRS".into()); + let _guard = term_ctx.display_step(); + load_srs(srs_file)? + }; + tracing::info!( + target: LOG_TARGET, + path =?srs_file, + "SRS found for a maximum of {} variables", + srs.max_num_vars + ); + + let pp_file = pp_file.ok_or(ProofError::InvalidPP)?; + tracing::info!( + target: LOG_TARGET, + path =?srs_file, + "Reading the Nova public parameters", + ); + + let mut term_ctx = term + .context("Setting up") + .on_step(|_step| "Spartan key".into()); + let _guard = term_ctx.display_step(); + + let pp: ComPP = load_pp(pp_file)?; + + gen_key(&pp, &srs)? + } else { + tracing::info!( + target: LOG_TARGET, + path = ?pp_file, + "Loading Spartan key", + ); + let mut term_ctx = term + .context("Loading") + .on_step(|_step| "Spartan key".into()); + let _guard = term_ctx.display_step(); + + load_key(key_file)? + }; + Ok(key) +} diff --git a/prover/src/lib.rs b/prover/src/lib.rs index 466ad1e64..0a45bda21 100644 --- a/prover/src/lib.rs +++ b/prover/src/lib.rs @@ -1,19 +1,27 @@ pub mod circuit; pub mod error; +pub mod key; pub mod pp; -pub mod types; +pub mod srs; use std::time::Instant; +pub mod types; + +use std::path::Path; + +use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; use nexus_vm::{ riscv::{load_nvm, VMOpts}, trace::{trace, Trace}, }; +use nexus_nova::nova::pcd::compression::SNARK; + use crate::{ circuit::Tr, error::ProofError, - types::{IVCProof, PCDNode, ParPP, SeqPP}, + types::{ComPCDNode, ComPP, ComProof, IVCProof, PCDNode, ParPP, SeqPP, SpartanKey}, }; #[cfg(feature = "verbose")] @@ -21,7 +29,44 @@ const TERMINAL_MODE: nexus_tui::Mode = nexus_tui::Mode::Enabled; #[cfg(not(feature = "verbose"))] const TERMINAL_MODE: nexus_tui::Mode = nexus_tui::Mode::Disabled; -const LOG_TARGET: &str = "nexus-prover"; +pub const LOG_TARGET: &str = "nexus-prover"; + +pub fn save_proof(proof: P, path: &Path) -> anyhow::Result<()> { + tracing::info!( + target: LOG_TARGET, + path = %path.display(), + "Saving the proof", + ); + + let mut term = nexus_tui::TerminalHandle::new(TERMINAL_MODE); + let mut context = term.context("Saving").on_step(|_step| "proof".into()); + let _guard = context.display_step(); + + let mut buf = Vec::new(); + + proof.serialize_compressed(&mut buf)?; + std::fs::write(path, buf)?; + + Ok(()) +} + +pub fn load_proof(path: &Path) -> Result { + let file = std::fs::File::open(path)?; + let reader = std::io::BufReader::new(file); + tracing::info!( + target: LOG_TARGET, + path = %path.display(), + "Loading the proof", + ); + + let mut term = nexus_tui::TerminalHandle::new(TERMINAL_MODE); + let mut context = term.context("Loading").on_step(|_step| "proof".into()); + let _guard = context.display_step(); + + let proof: P = P::deserialize_compressed(reader)?; + + Ok(proof) +} fn estimate_size(tr: &Trace) -> usize { use std::mem::size_of_val as sizeof; @@ -79,66 +124,111 @@ pub fn prove_seq(pp: &SeqPP, trace: Trace) -> Result { Ok(proof) } -pub fn prove_par(pp: ParPP, trace: Trace) -> Result { - let k = trace.k; - let tr = Tr(trace); - - let num_steps = tr.steps(); - assert!((num_steps + 1).is_power_of_two()); - - let on_step = move |iter: usize| { - let b = (num_steps + 1).ilog2(); - let a = b - 1 - (num_steps - iter).ilog2(); - - let step = 2usize.pow(a + 1) * iter - (2usize.pow(a) - 1) * (2usize.pow(b + 1) - 1); - let step_type = if iter <= num_steps / 2 { - "leaf" - } else if iter == num_steps - 1 { - "root" - } else { - "node" - }; - format!("{step_type} {step}") +macro_rules! prove_par_impl { + ( $pp_type:ty, $node_type:ty, $name:ident ) => { + pub fn $name(pp: $pp_type, trace: Trace) -> Result<$node_type, ProofError> { + let k = trace.k; + let tr = Tr(trace); + + let num_steps = tr.steps(); + assert!((num_steps + 1).is_power_of_two()); + + let on_step = move |iter: usize| { + let b = (num_steps + 1).ilog2(); + let a = b - 1 - (num_steps - iter).ilog2(); + + let step = 2usize.pow(a + 1) * iter - (2usize.pow(a) - 1) * (2usize.pow(b + 1) - 1); + let step_type = if iter <= num_steps / 2 { + "leaf" + } else if iter == num_steps - 1 { + "root" + } else { + "node" + }; + format!("{step_type} {step}") + }; + + let mut term = nexus_tui::TerminalHandle::new(TERMINAL_MODE); + let mut term_ctx = term + .context("Computing") + .on_step(on_step) + .num_steps(num_steps) + .with_loading_bar("Proving") + .completion_header("Proved") + .completion_stats(move |elapsed| { + format!( + "tree root in {elapsed}; {:.2} instructions / second", + (k * num_steps) as f32 / elapsed.as_secs_f32() + ) + }); + + let mut vs = (0..num_steps) + .step_by(2) + .map(|i| { + let _guard = term_ctx.display_step(); + + let v = <$node_type>::prove_leaf(&pp, &tr, i, &tr.input(i)?)?; + Ok(v) + }) + .collect::, ProofError>>()?; + + loop { + if vs.len() == 1 { + break; + } + vs = vs + .chunks(2) + .map(|ab| { + let _guard = term_ctx.display_step(); + let c = <$node_type>::prove_parent(&pp, &tr, &ab[0], &ab[1])?; + Ok(c) + }) + .collect::, ProofError>>()?; + } + + Ok(vs.into_iter().next().unwrap()) + } }; +} +prove_par_impl!(ParPP, PCDNode, prove_par); +prove_par_impl!(ComPP, ComPCDNode, prove_par_com); + +pub fn compress( + compression_pp: &ComPP, + key: &SpartanKey, + node: ComPCDNode, +) -> Result { + tracing::info!( + target: LOG_TARGET, + "Compressing the proof", + ); let mut term = nexus_tui::TerminalHandle::new(TERMINAL_MODE); let mut term_ctx = term - .context("Computing") - .on_step(on_step) - .num_steps(num_steps) - .with_loading_bar("Proving") - .completion_header("Proved") - .completion_stats(move |elapsed| { - format!( - "tree root in {elapsed}; {:.2} instructions / second", - (k * num_steps) as f32 / elapsed.as_secs_f32() - ) - }); + .context("Compressing") + .on_step(|_step| "the proof".into()); + let _guard = term_ctx.display_step(); - let mut vs = (0..num_steps) - .step_by(2) - .map(|i| { - let _guard = term_ctx.display_step(); + let compressed_pcd_proof = SNARK::compress(compression_pp, key, node)?; - let v = PCDNode::prove_leaf(&pp, &tr, i, &tr.input(i)?)?; - Ok(v) - }) - .collect::, ProofError>>()?; + Ok(compressed_pcd_proof) +} - loop { - if vs.len() == 1 { - break; - } - vs = vs - .chunks(2) - .map(|ab| { - let _guard = term_ctx.display_step(); - - let c = PCDNode::prove_parent(&pp, &tr, &ab[0], &ab[1])?; - Ok(c) - }) - .collect::, ProofError>>()?; - } +pub fn verify_compressed( + key: &SpartanKey, + params: &ComPP, + proof: &ComProof, +) -> Result<(), ProofError> { + tracing::info!( + target: LOG_TARGET, + "Verifying the compressed proof", + ); + let mut term = nexus_tui::TerminalHandle::new(TERMINAL_MODE); + let mut term_ctx = term + .context("Verifying") + .on_step(|_step| "compressed proof".into()); + let _guard = term_ctx.display_step(); - Ok(vs.into_iter().next().unwrap()) + SNARK::verify(key, params, proof)?; + Ok(()) } diff --git a/prover/src/main.rs b/prover/src/main.rs index 30e64f82d..b8a1a302c 100644 --- a/prover/src/main.rs +++ b/prover/src/main.rs @@ -2,13 +2,19 @@ #[global_allocator] static ALLOC: snmalloc_rs::SnMalloc = snmalloc_rs::SnMalloc; +use ark_std::path::Path; use clap::{Parser, Subcommand}; use tracing_subscriber::EnvFilter; use nexus_prover::{ - error::ProofError, - pp::{gen_or_load, gen_to_file}, - prove_par, prove_seq, run, + compress, + key::{gen_key_to_file, gen_or_load_key}, + load_proof, + pp::{gen_or_load, gen_to_file, load_pp}, + prove_par, prove_par_com, prove_seq, run, save_proof, + srs::load_srs, + types::{ComPCDNode, ComPP}, + LOG_TARGET, }; use nexus_vm::riscv::VMOpts; @@ -38,6 +44,10 @@ enum Command { default_value = "nexus-public.zst" )] pp_file: String, + + /// SRS file: if not provided, proofs will not be compressible. + #[arg(short = 's', long = "srs")] + srs_file: Option, }, /// Prove execution of program @@ -50,21 +60,94 @@ enum Command { #[arg(short = 'P', default_value = "false")] par: bool, - /// private parameters file + /// public parameters file #[arg( short = 'p', - long = "private-params", + long = "public-params", default_value = "nexus-public.zst" )] pp_file: String, #[command(flatten)] vm: VMOpts, + + /// SRS file: if supplied, compressible proof is generated + #[arg(short = 's', long = "srs")] + srs_file: Option, + + /// File to save the proof + #[arg(short = 'f', long = "proof_file")] + proof_file: String, + }, + /// Generate public parameters file + SpartanKeyGen { + /// public parameters file + #[arg( + short = 'p', + long = "public-params", + default_value = "nexus-public.zst" + )] + pp_file: String, + + /// SRS file + #[arg(short = 's', long = "srs")] + srs_file: String, + + /// Spartan key file + #[arg( + short = 'k', + long = "spartan-key", + default_value = "nexus-spartan-key.zst" + )] + key_file: String, + }, + + /// Compress a Nexus proof via supernova + Compress { + /// generate Spartan key (ignore files) + #[arg(short)] + gen: bool, + + /// Spartan key file + #[arg( + short = 'k', + long = "spartan-key", + default_value = "nexus-spartan-key.zst" + )] + key_file: String, + + /// public parameters file + #[arg( + short = 'p', + long = "public-params", + default_value = "nexus-public.zst" + )] + pp_file: String, + + /// srs file; only needed if `gen` is `false` + #[arg( + short = 's', + long = "structured-reference-string", + default_value = "nexus-srs.zst" + )] + srs_file: Option, + + /// File containing uncompressed proof + #[arg(short = 'f', long = "proof-file", default_value = "nexus-proof.json")] + proof_file: String, + + /// File to save compressed proof + #[arg( + short = 'c', + long = "compressed-proof-file", + default_value = "nexus-proof-compressed.json" + )] + compressed_proof_file: String, }, } use Command::*; -fn main() -> Result<(), ProofError> { +fn main() -> anyhow::Result<()> { let filter = EnvFilter::from_default_env(); tracing_subscriber::fmt() .with_span_events(tracing_subscriber::fmt::format::FmtSpan::CLOSE) @@ -74,16 +157,71 @@ fn main() -> Result<(), ProofError> { let opts = Opts::parse(); match opts.command { - Gen { k, par, pp_file } => gen_to_file(k, par, &pp_file), + Gen { k, par, pp_file, srs_file } => { + gen_to_file(k, par, &pp_file, srs_file.as_deref())?; + Ok(()) + } - Prove { gen, par, pp_file, vm } => { + SpartanKeyGen { pp_file, srs_file, key_file } => { + gen_key_to_file(&pp_file, &srs_file, &key_file)?; + Ok(()) + } + + Prove { + gen, + par, + pp_file, + vm, + srs_file, + proof_file, + } => { let trace = run(&vm, par)?; + let path = Path::new(&proof_file); if par { - prove_par(gen_or_load(gen, vm.k, &pp_file)?, trace)?; + if srs_file.is_some() { + let srs = load_srs(&srs_file.unwrap())?; + let proof = + prove_par_com(gen_or_load(gen, vm.k, &pp_file, Some(&(srs)))?, trace)?; + save_proof(proof, path) + } else { + let proof = prove_par(gen_or_load(gen, vm.k, &pp_file, Some(&()))?, trace)?; + save_proof(proof, path) + } } else { - prove_seq(&gen_or_load(gen, vm.k, &pp_file)?, trace)?; + let proof = prove_seq(&gen_or_load(gen, vm.k, &pp_file, Some(&()))?, trace)?; + save_proof(proof, path) } - Ok(()) + } + + Compress { + gen, + key_file, + pp_file, + srs_file, + proof_file, + compressed_proof_file, + } => { + let key = gen_or_load_key(gen, &key_file, Some(&pp_file), srs_file.as_deref())?; + + tracing::info!( + target: LOG_TARGET, + path =?pp_file, + "Reading the Nova public parameters", + ); + + let pp: ComPP = load_pp(&pp_file)?; + + tracing::info!( + target: LOG_TARGET, + proof_file = %proof_file, + "Reading the proof", + ); + let proof_path = Path::new(&proof_file); + let node: ComPCDNode = load_proof(proof_path)?; + + let compressed_proof = compress(&pp, &key, node)?; + let compressed_proof_path = Path::new(&compressed_proof_file); + save_proof(compressed_proof, compressed_proof_path) } } } diff --git a/prover/src/pp.rs b/prover/src/pp.rs index 13673f622..77b54a65f 100644 --- a/prover/src/pp.rs +++ b/prover/src/pp.rs @@ -3,22 +3,25 @@ use zstd::stream::{Decoder, Encoder}; pub use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; +use super::srs::load_srs; use crate::circuit::{nop_circuit, Tr}; use crate::error::*; use crate::types::*; use crate::{LOG_TARGET, TERMINAL_MODE}; -pub fn gen_pp(circuit: &SC) -> Result, ProofError> +pub fn gen_pp(circuit: &SC, aux: &C::SetupAux) -> Result, ProofError> where - SP: SetupParams, + C: CommitmentScheme, + SP: SetupParams, { - Ok(SP::setup(ro_config(), circuit, &(), &())?) + Ok(SP::setup(ro_config(), circuit, aux, &())?) } -pub fn save_pp(pp: PP, file: &str) -> Result<(), ProofError> +pub fn save_pp(pp: PP, file: &str) -> Result<(), ProofError> where + C: CommitmentScheme, SC: StepCircuit, - SP: SetupParams, + SP: SetupParams, { let f = File::create(file)?; let mut enc = Encoder::new(&f, 0)?; @@ -28,28 +31,31 @@ where Ok(()) } -pub fn load_pp(file: &str) -> Result, ProofError> +pub fn load_pp(file: &str) -> Result, ProofError> where + C: CommitmentScheme, SC: StepCircuit + Sync, - SP: SetupParams + Sync, + SP: SetupParams + Sync, { let f = File::open(file)?; let mut dec = Decoder::new(&f)?; - let pp = PP::::deserialize_compressed(&mut dec)?; + let pp = PP::::deserialize_compressed(&mut dec)?; Ok(pp) } -pub fn gen_vm_pp(k: usize) -> Result, ProofError> +pub fn gen_vm_pp(k: usize, aux: &C::SetupAux) -> Result, ProofError> where - SP: SetupParams, + SP: SetupParams, + C: CommitmentScheme, { let tr = nop_circuit(k)?; - gen_pp(&tr) + gen_pp(&tr, aux) } -fn show_pp(pp: &PP) +fn show_pp(pp: &PP) where - SP: SetupParams, + SP: SetupParams, + C: CommitmentScheme, { tracing::debug!( target: LOG_TARGET, @@ -63,46 +69,120 @@ where ); } -pub fn gen_to_file(k: usize, par: bool, pp_file: &str) -> Result<(), ProofError> { +pub fn gen_to_file( + k: usize, + par: bool, + pp_file: &str, + srs_file_opt: Option<&str>, +) -> Result<(), ProofError> { tracing::info!( target: LOG_TARGET, path = ?pp_file, "Generating public parameters", ); let mut term = nexus_tui::TerminalHandle::new(TERMINAL_MODE); - let mut term_ctx = term - .context("Setting up") - .on_step(|_step| "public parameters".into()); - let _guard = term_ctx.display_step(); if par { - let pp: ParPP = gen_vm_pp(k)?; - show_pp(&pp); - save_pp(pp, pp_file) + match srs_file_opt { + Some(srs_file) => { + tracing::info!( + target: LOG_TARGET, + path =?srs_file, + "Reading the SRS", + ); + let srs: SRS = { + let mut term_ctx = term.context("Loading").on_step(|_step| "SRS".into()); + let _guard = term_ctx.display_step(); + load_srs(srs_file)? + }; + tracing::info!( + target: LOG_TARGET, + path =?srs_file, + "SRS found for a maximum of {} variables", + srs.max_num_vars + ); + let pp: ComPP = { + tracing::info!( + target: LOG_TARGET, + "Generating compressible PCD public parameters", + ); + let mut term_ctx = term + .context("Setting up") + .on_step(|_step| "public parameters for PCD (compression enabled)".into()); + let _guard = term_ctx.display_step(); + gen_vm_pp(k, &srs)? + }; + + show_pp(&pp); + save_pp(pp, pp_file) + } + None => { + tracing::info!( + target: LOG_TARGET, + "Generating non-compressible PCD public parameters", + ); + let pp: ParPP = { + let mut term_ctx = term + .context("Setting up") + .on_step(|_step| "public parameters for PCD (compression disabled)".into()); + let _guard = term_ctx.display_step(); + + gen_vm_pp(k, &())? + }; + show_pp(&pp); + save_pp(pp, pp_file) + } + } } else { - let pp: SeqPP = gen_vm_pp(k)?; + tracing::info!( + target: LOG_TARGET, + "Generating IVC public parameters", + ); + + let pp: SeqPP = { + let mut term_ctx = term + .context("Setting up") + .on_step(|_step| "public parameters for IVC".into()); + let _guard = term_ctx.display_step(); + gen_vm_pp(k, &())? + }; show_pp(&pp); save_pp(pp, pp_file) } } -pub fn gen_or_load(gen: bool, k: usize, pp_file: &str) -> Result, ProofError> +pub fn gen_or_load( + gen: bool, + k: usize, + pp_file: &str, + aux_opt: Option<&C::SetupAux>, +) -> Result, ProofError> where - SP: SetupParams + Sync, + SP: SetupParams + Sync, + C: CommitmentScheme, { let mut term = nexus_tui::TerminalHandle::new(TERMINAL_MODE); - let pp: PP = if gen { + let pp: PP = if gen { tracing::info!( target: LOG_TARGET, "Generating public parameters", ); + + let aux = if let Some(aux) = aux_opt { + aux + } else { + tracing::error!( + target: LOG_TARGET, + "Auxiliary setup parameters are not provided", + ); + return Err(ProofError::MissingSRS); + }; let mut term_ctx = term .context("Setting up") .on_step(|_step| "public parameters".into()); let _guard = term_ctx.display_step(); - - gen_vm_pp(k)? + gen_vm_pp(k, aux)? } else { tracing::info!( target: LOG_TARGET, @@ -116,7 +196,6 @@ where load_pp(pp_file)? }; - drop(term); show_pp(&pp); Ok(pp) diff --git a/prover/src/srs.rs b/prover/src/srs.rs new file mode 100644 index 000000000..b779ed8a1 --- /dev/null +++ b/prover/src/srs.rs @@ -0,0 +1,70 @@ +use std::fs::File; +use zstd::stream::Decoder; + +use crate::{ + error::ProofError, + pp::gen_vm_pp, + types::{ParPP, SpartanKey, SRS}, +}; + +pub use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; + +pub fn load_srs(file: &str) -> Result { + let f = File::open(file)?; + let mut dec = Decoder::new(&f)?; + // we can use `deserialize_compressed` instead of `deserialize_compressed_unchecked` here since + // the expensive operation of checking that group elements are in the correct subgroup + // has been disabled for prime-order curve groups in https://github.com/arkworks-rs/algebra/pull/771. + let srs = SRS::deserialize_compressed(&mut dec)?; + Ok(srs) +} + +/// Derive the minimum (log) size of the SRS to support compression for a +/// given `k`. +pub fn get_min_srs_size(k: usize) -> Result { + // these are only used to get the size of the r1cs matrices for a given k. + let dummy_pp: ParPP = gen_vm_pp(k, &())?; + let ParPP { shape, .. } = dummy_pp; + + Ok(SpartanKey::get_min_srs_size(&shape)) +} + +pub mod test_srs { + use super::*; + use ark_std::test_rng; + use zstd::stream::Encoder; + + use crate::types::{PolyCommitmentScheme, PC}; + /// This function should only be used for testing, as it is insescure: + /// the SRS should be generated by a trusted setup ceremony. + pub fn gen_test_srs(num_vars: usize) -> Result { + let mut rng = test_rng(); + PC::setup(num_vars, b"test_srs", &mut rng).map_err(|_| ProofError::SRSSamplingError) + } + + pub fn save_srs(srs: SRS, file: &str) -> Result<(), ProofError> { + let f = File::create(file)?; + let mut enc = Encoder::new(&f, 0)?; + srs.serialize_compressed(&mut enc)?; + enc.finish()?; + f.sync_all()?; + Ok(()) + } + + pub fn gen_test_srs_to_file(poly_length: usize, file: &str) -> Result<(), ProofError> { + let srs = gen_test_srs(poly_length)?; + save_srs(srs, file) + } +} + +#[cfg(test)] +mod tests { + #[test] + fn min_srs_size_k1() { + let k = 1; + println!( + "min srs size for k = 1: {}", + super::get_min_srs_size(k).unwrap() + ); + } +} diff --git a/prover/src/types.rs b/prover/src/types.rs index ab6cef41b..1a9f2e9e8 100644 --- a/prover/src/types.rs +++ b/prover/src/types.rs @@ -5,7 +5,7 @@ pub use std::marker::PhantomData; pub use ark_ff::{Field, PrimeField}; // concrete fields used -pub use ark_bn254::{g1::Config as G1, Fr as F1, G1Affine as A1, G1Projective as P1}; +pub use ark_bn254::{g1::Config as G1, Bn254 as E, Fr as F1, G1Affine as A1, G1Projective as P1}; pub use ark_grumpkin::{Affine as A2, Fr as F2, GrumpkinConfig as G2, Projective as P2}; // concrete sponge used @@ -13,10 +13,13 @@ pub use ark_crypto_primitives::sponge::poseidon::{PoseidonConfig, PoseidonSponge pub use ark_relations::r1cs::ConstraintSystemRef; +pub use spartan::polycommitments::{zeromorph::Zeromorph, PolyCommitmentScheme}; + // types and traits from nexus prover pub use nexus_nova::{ commitment::CommitmentScheme, nova::pcd, + nova::pcd::compression as com, nova::public_params::{PublicParams, SetupParams}, nova::sequential as seq, pedersen::PedersenCommitment, @@ -33,17 +36,28 @@ pub type RO = PoseidonSponge; pub use nexus_nova::poseidon_config as ro_config; // commitment scheme - pub type C1 = PedersenCommitment; pub type C2 = PedersenCommitment; +// polynomial commitment scheme and corresponding vector commmitment scheme +pub type PC = Zeromorph; +pub type PVC1 = com::PolyVectorCommitment; + +// structured reference string for polynomial commitment scheme +pub type SRS = >::SRS; + pub type SC = crate::circuit::Tr; // concrete public parameters -pub type PP = PublicParams; +pub type PP = PublicParams; pub type SeqPP = seq::PublicParams; pub type ParPP = pcd::PublicParams; +pub type ComPP = pcd::PublicParams; + +pub type SpartanKey = com::SNARKKey; pub type IVCProof = seq::IVCProof; pub type PCDNode = pcd::PCDNode; +pub type ComPCDNode = pcd::PCDNode; +pub type ComProof = com::CompressedPCDProof; diff --git a/spartan/src/crr1cs.rs b/spartan/src/crr1cs.rs index 4b25a951d..9d7dde58a 100644 --- a/spartan/src/crr1cs.rs +++ b/spartan/src/crr1cs.rs @@ -27,6 +27,7 @@ impl> CRR1CSKey { n.log_2() } } + #[derive(CanonicalDeserialize, CanonicalSerialize)] pub struct CRR1CSShape { pub inst: Instance, diff --git a/spartan/src/errors.rs b/spartan/src/errors.rs index 9223d2b9e..98bf16b77 100644 --- a/spartan/src/errors.rs +++ b/spartan/src/errors.rs @@ -1,5 +1,6 @@ use super::polycommitments::error::PCSError; use ark_serialize::SerializationError; +use ark_std::{error::Error, fmt::Display}; use core::fmt::Debug; use thiserror::Error; @@ -38,6 +39,34 @@ impl From for R1CSError { } } +impl Error for R1CSError { + fn source(&self) -> Option<&(dyn Error + 'static)> { + match self { + Self::NonPowerOfTwoCons => None, + Self::NonPowerOfTwoVars => None, + Self::InvalidNumberOfInputs => None, + Self::InvalidNumberOfVars => None, + Self::InvalidScalar => None, + Self::InvalidIndex => None, + Self::ArkSerializationError(e) => Some(e), + } + } +} + +impl Display for R1CSError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::NonPowerOfTwoCons => write!(f, "Non power of two constraints"), + Self::NonPowerOfTwoVars => write!(f, "Non power of two variables"), + Self::InvalidNumberOfInputs => write!(f, "Invalid number of inputs"), + Self::InvalidNumberOfVars => write!(f, "Invalid number of variables"), + Self::InvalidScalar => write!(f, "Invalid scalar"), + Self::InvalidIndex => write!(f, "Invalid index"), + Self::ArkSerializationError(e) => write!(f, "{e}"), + } + } +} + impl From for ProofVerifyError { fn from(e: PCSError) -> Self { Self::PolyCommitmentError(e) diff --git a/spartan/src/polycommitments/zeromorph/data_structures.rs b/spartan/src/polycommitments/zeromorph/data_structures.rs index 31488f144..c6e3ff8b6 100644 --- a/spartan/src/polycommitments/zeromorph/data_structures.rs +++ b/spartan/src/polycommitments/zeromorph/data_structures.rs @@ -1,5 +1,4 @@ use crate::{ - math::Math, polycommitments::{PolyCommitmentTrait, SRSTrait}, transcript::{AppendToTranscript, ProofTranscript}, }; @@ -57,7 +56,7 @@ where impl SRSTrait for ZeromorphSRS { fn max_num_vars(&self) -> usize { - self.powers_of_tau_g.len().log_2() + self.max_num_vars } } diff --git a/spartan/src/polycommitments/zeromorph/mod.rs b/spartan/src/polycommitments/zeromorph/mod.rs index 1d5db8da0..69c6f54cb 100644 --- a/spartan/src/polycommitments/zeromorph/mod.rs +++ b/spartan/src/polycommitments/zeromorph/mod.rs @@ -16,6 +16,7 @@ use super::{PCSKeys, PolyCommitmentScheme}; use crate::{ dense_mlpoly::DensePolynomial, math::Math, + polycommitments::SRSTrait, transcript::{AppendToTranscript, ProofTranscript}, }; @@ -317,9 +318,12 @@ where } fn trim(srs: &Self::SRS, supported_num_vars: usize) -> PCSKeys { let max_degree = srs.max_degree(); + let max_num_vars = srs.max_num_vars(); let supported_degree = Math::pow2(supported_num_vars) - 1; if supported_degree > max_degree { - panic!("Unsupported degree"); + panic!( + "required number of variables {supported_num_vars} is greater than SRS maximum number of variables {max_num_vars}", + ); } let powers_of_tau_g = srs.powers_of_tau_g[..=supported_degree].to_vec(); let shifted_powers_of_tau_g = diff --git a/spartan/src/product_tree.rs b/spartan/src/product_tree.rs index 18719edd4..b89b33b70 100644 --- a/spartan/src/product_tree.rs +++ b/spartan/src/product_tree.rs @@ -289,8 +289,8 @@ impl ProductCircuitEvalProof { impl ProductCircuitEvalProofBatched { pub fn prove( - prod_circuit_vec: &mut Vec<&mut ProductCircuit>, - dotp_circuit_vec: &mut Vec<&mut DotProductCircuit>, + prod_circuit_vec: &mut [&mut ProductCircuit], + dotp_circuit_vec: &mut [&mut DotProductCircuit], transcript: &mut Transcript, ) -> (Self, Vec) where diff --git a/spartan/src/sparse_mlpoly.rs b/spartan/src/sparse_mlpoly.rs index 1c3792ea5..1818bb001 100644 --- a/spartan/src/sparse_mlpoly.rs +++ b/spartan/src/sparse_mlpoly.rs @@ -1326,7 +1326,7 @@ impl ProductLayerProof { }; let (proof_ops, rand_ops) = ProductCircuitEvalProofBatched::::prove::( - &mut vec![ + &mut [ &mut row_read_A[0], &mut row_read_B[0], &mut row_read_C[0], @@ -1340,7 +1340,7 @@ impl ProductLayerProof { &mut col_write_B[0], &mut col_write_C[0], ], - &mut vec![ + &mut [ &mut dotp_left_A[0], &mut dotp_right_A[0], &mut dotp_left_B[0], @@ -1353,7 +1353,7 @@ impl ProductLayerProof { // produce a batched proof of memory-related product circuits let (proof_mem, rand_mem) = ProductCircuitEvalProofBatched::::prove::( - &mut vec![ + &mut [ &mut row_prod_layer.init, &mut row_prod_layer.audit, &mut col_prod_layer.init, diff --git a/tools/Cargo.toml b/tools/Cargo.toml index 607d9908c..3d2b5ed94 100644 --- a/tools/Cargo.toml +++ b/tools/Cargo.toml @@ -32,4 +32,5 @@ nexus-riscv = { path = "../riscv" } nexus-prover = { path = "../prover", features = ["verbose"] } nexus-tui = { path = "./tui" } +ark-spartan = { path = "../spartan" } ark-serialize.workspace = true diff --git a/tools/src/command/compress.rs b/tools/src/command/compress.rs new file mode 100644 index 000000000..31e8f7a59 --- /dev/null +++ b/tools/src/command/compress.rs @@ -0,0 +1,91 @@ +use anyhow::Context; +use std::io; + +use crate::{ + command::{cache_path, spartan_key::spartan_setup}, + LOG_TARGET, +}; +use nexus_config::{vm as vm_config, Config}; +use nexus_tools_dev::command::common::{ + compress::CompressArgs, public_params::format_params_file, spartan_key::SetupArgs, +}; + +pub fn handle_command(args: CompressArgs) -> anyhow::Result<()> { + compress_proof(args) +} + +pub fn compress_proof(args: CompressArgs) -> anyhow::Result<()> { + let vm_config = vm_config::VmConfig::from_env()?; + let k = args.k.unwrap_or(vm_config.k); + + let pp_file = match args.pp_file { + None => { + let nova_impl = vm_config::NovaImpl::ParallelCompressible; + + let pp_file_name = format_params_file(nova_impl, k); + let cache_path = cache_path()?; + + cache_path.join(pp_file_name) + } + Some(path) => path, + }; + if !pp_file.try_exists()? { + tracing::error!( + target: LOG_TARGET, + "path {} was not found", + pp_file.display(), + ); + return Err(io::Error::from(io::ErrorKind::NotFound).into()); + }; + tracing::info!( + target: LOG_TARGET, + path =?pp_file, + "Reading the Nova public parameters", + ); + let pp_file_str = pp_file.to_str().context("path is not valid utf8")?; + + let pp = nexus_prover::pp::load_pp(pp_file_str)?; + + let key_file = if let Some(path) = args.key_file { + // return early if the path was explicitly specified and doesn't exist + if !path.try_exists()? { + tracing::error!( + target: LOG_TARGET, + "path {} was not found", + path.display(), + ); + return Err(io::Error::from(io::ErrorKind::NotFound).into()); + } + path + } else { + spartan_setup(SetupArgs { + path: None, + force: false, + k: Some(k), + pp_file: Some(pp_file), + srs_file: args.srs_file, + })? + }; + let key_file_str = key_file.to_str().context("path is not valid utf8")?; + let key = nexus_prover::key::gen_or_load_key(false, key_file_str, None, None)?; + + let proof_file = args.proof_file; + if !proof_file.try_exists()? { + tracing::error!( + target: LOG_TARGET, + "path {} was not found", + proof_file.display(), + ); + return Err(io::Error::from(io::ErrorKind::NotFound).into()); + }; + let proof = nexus_prover::load_proof(&proof_file)?; + + let current_dir = std::env::current_dir()?; + let compressed_proof_path = current_dir.join("nexus-proof-compressed"); + + let compressed_proof = nexus_prover::compress(&pp, &key, proof)?; + + nexus_prover::save_proof(compressed_proof, &compressed_proof_path)?; + + Ok(()) +} diff --git a/tools/src/command/mod.rs b/tools/src/command/mod.rs index 8250f7f39..0f98dbd02 100644 --- a/tools/src/command/mod.rs +++ b/tools/src/command/mod.rs @@ -3,11 +3,13 @@ use std::path::PathBuf; use nexus_config::{Config, MiscConfig}; use nexus_tools_dev::{command::common::Command as CommonCommand, Command}; +pub mod compress; pub mod new; pub mod prove; pub mod public_params; pub mod request; pub mod run; +pub mod spartan_key; pub mod verify; /// Default environment variables for prover configuration. @@ -29,6 +31,8 @@ pub fn handle_command(cmd: Command) -> anyhow::Result<()> { CommonCommand::Request(args) => request::handle_command(args), CommonCommand::Verify(args) => verify::handle_command(args), CommonCommand::PublicParams(args) => public_params::handle_command(args), + CommonCommand::Compress(args) => compress::handle_command(args), + CommonCommand::SpartanKey(args) => spartan_key::handle_command(args), } } diff --git a/tools/src/command/prove.rs b/tools/src/command/prove.rs index d073996ef..f0e3d6377 100644 --- a/tools/src/command/prove.rs +++ b/tools/src/command/prove.rs @@ -4,7 +4,6 @@ use std::{ }; use anyhow::Context; -use ark_serialize::CanonicalSerialize; use nexus_config::{vm as vm_config, Config}; use nexus_tools_dev::{ @@ -42,10 +41,10 @@ pub fn handle_command(args: ProveArgs) -> anyhow::Result<()> { // build artifact if needed cargo(None, ["build", "--profile", &profile])?; - let LocalProveArgs { k, pp_file, nova_impl } = local_args; + let LocalProveArgs { k, pp_file, nova_impl, srs_file } = local_args; let k = k.unwrap_or(vm_config.k); let nova_impl = nova_impl.unwrap_or(vm_config.nova_impl); - local_prove(&path, k, nova_impl, pp_file) + local_prove(&path, k, nova_impl, pp_file, srs_file) } } @@ -75,6 +74,7 @@ fn local_prove( k: usize, nova_impl: vm_config::NovaImpl, pp_file: Option, + srs_file: Option, ) -> anyhow::Result<()> { // setup if necessary let pp_file = if let Some(path) = pp_file { @@ -94,6 +94,7 @@ fn local_prove( nova_impl: Some(nova_impl), path: None, force: false, + srs_file, })? }; let path_str = pp_file.to_str().context("path is not valid utf8")?; @@ -110,36 +111,45 @@ fn local_prove( let current_dir = std::env::current_dir()?; let proof_path = current_dir.join("nexus-proof"); - if nova_impl == vm_config::NovaImpl::Parallel { - let state = nexus_prover::pp::gen_or_load(false, k, path_str)?; - let root = nexus_prover::prove_par(state, trace)?; + match nova_impl { + vm_config::NovaImpl::Parallel => { + let state = nexus_prover::pp::gen_or_load(false, k, path_str, None)?; + let root = nexus_prover::prove_par(state, trace)?; - save_proof(root, &proof_path)?; - } else { - let state = nexus_prover::pp::gen_or_load(false, k, path_str)?; - let proof = nexus_prover::prove_seq(&state, trace)?; + nexus_prover::save_proof(root, &proof_path)?; + } + vm_config::NovaImpl::ParallelCompressible => { + let state = nexus_prover::pp::gen_or_load(false, k, path_str, None)?; + let root = nexus_prover::prove_par_com(state, trace)?; - save_proof(proof, &proof_path)?; + nexus_prover::save_proof(root, &proof_path)?; + } + vm_config::NovaImpl::Sequential => { + let state = nexus_prover::pp::gen_or_load(false, k, path_str, None)?; + let proof = nexus_prover::prove_seq(&state, trace)?; + + nexus_prover::save_proof(proof, &proof_path)?; + } } Ok(()) } -fn save_proof(proof: P, path: &Path) -> anyhow::Result<()> { - tracing::info!( - target: LOG_TARGET, - path = %path.display(), - "Saving the proof", - ); +// fn save_proof(proof: P, path: &Path) -> anyhow::Result<()> { +// tracing::info!( +// target: LOG_TARGET, +// path = %path.display(), +// "Saving the proof", +// ); - let mut term = nexus_tui::TerminalHandle::new_enabled(); - let mut context = term.context("Saving").on_step(|_step| "proof".into()); - let _guard = context.display_step(); +// let mut term = nexus_tui::TerminalHandle::new_enabled(); +// let mut context = term.context("Saving").on_step(|_step| "proof".into()); +// let _guard = context.display_step(); - let mut buf = Vec::new(); +// let mut buf = Vec::new(); - proof.serialize_compressed(&mut buf)?; - std::fs::write(path, buf)?; +// proof.serialize_compressed(&mut buf)?; +// std::fs::write(path, buf)?; - Ok(()) -} +// Ok(()) +// } diff --git a/tools/src/command/public_params.rs b/tools/src/command/public_params.rs index 977b8b38b..341e5b38c 100644 --- a/tools/src/command/public_params.rs +++ b/tools/src/command/public_params.rs @@ -1,10 +1,15 @@ -use std::path::{Path, PathBuf}; +use std::{ + io, + path::{Path, PathBuf}, +}; use anyhow::Context; use nexus_config::{vm as vm_config, Config}; +use nexus_prover::srs::{get_min_srs_size, test_srs::gen_test_srs_to_file}; use nexus_tools_dev::command::common::public_params::{ - format_params_file, PublicParamsAction, PublicParamsArgs, SetupArgs, + format_params_file, format_srs_file, PublicParamsAction, PublicParamsArgs, SRSSetupArgs, + SetupArgs, }; use crate::{command::cache_path, LOG_TARGET}; @@ -17,6 +22,9 @@ pub fn handle_command(args: PublicParamsArgs) -> anyhow::Result<()> { PublicParamsAction::Setup(setup_args) => { let _ = setup_params(setup_args)?; } + PublicParamsAction::SampleTestSRS(srs_setup_args) => { + let _ = sample_test_srs(srs_setup_args)?; + } } Ok(()) } @@ -27,6 +35,7 @@ pub(crate) fn setup_params(args: SetupArgs) -> anyhow::Result { let force = args.force; let k = args.k.unwrap_or(vm_config.k); let nova_impl = args.nova_impl.unwrap_or(vm_config.nova_impl); + let srs_file = args.srs_file; let path = match args.path { Some(path) => path, @@ -47,7 +56,7 @@ pub(crate) fn setup_params(args: SetupArgs) -> anyhow::Result { return Ok(path); } - setup_params_to_file(&path, nova_impl, k)?; + setup_params_to_file(&path, nova_impl, k, srs_file)?; Ok(path) } @@ -55,10 +64,69 @@ fn setup_params_to_file( path: &Path, nova_impl: vm_config::NovaImpl, k: usize, + srs_file: Option, ) -> anyhow::Result<()> { let path = path.to_str().context("path is not valid utf8")?; - let par = matches!(nova_impl, vm_config::NovaImpl::Parallel); - nexus_prover::pp::gen_to_file(k, par, path)?; + match nova_impl { + vm_config::NovaImpl::Sequential => nexus_prover::pp::gen_to_file(k, false, path, None)?, + vm_config::NovaImpl::Parallel => nexus_prover::pp::gen_to_file(k, true, path, None)?, + vm_config::NovaImpl::ParallelCompressible => { + let srs_file = match srs_file { + None => { + let srs_file_name = format_srs_file(get_min_srs_size(k)?); + let cache_path = cache_path()?; + cache_path.join(srs_file_name) + } + Some(file) => file, + }; + + if !srs_file.try_exists()? { + tracing::error!( + target: LOG_TARGET, + "path {} was not found", + srs_file.display(), + ); + return Err(io::Error::from(io::ErrorKind::NotFound).into()); + } + let srs_file_str = srs_file.to_str().context("path is not valid utf8")?; + nexus_prover::pp::gen_to_file(k, true, path, Some(srs_file_str))? + } + }; Ok(()) } + +pub fn sample_test_srs(args: SRSSetupArgs) -> anyhow::Result { + let num_vars = match args.num_vars { + None => { + let vm_config = vm_config::VmConfig::from_env()?; + let k = args.k.unwrap_or(vm_config.k); + get_min_srs_size(k)? + } + Some(num_vars) => num_vars, + }; + let force = args.force; + let path = match args.file { + Some(file) => file, + None => { + let srs_file_name = format_srs_file(num_vars); + let cache_path = cache_path()?; + + cache_path.join(srs_file_name) + } + }; + + if !force && path.try_exists()? { + tracing::info!( + target: LOG_TARGET, + "path {} already exists, use `setup --force` to overwrite", + path.display(), + ); + return Ok(path); + } + + let path_str = path.to_str().context("path is not valid utf8")?; + + gen_test_srs_to_file(num_vars, path_str)?; + Ok(path) +} diff --git a/tools/src/command/spartan_key.rs b/tools/src/command/spartan_key.rs new file mode 100644 index 000000000..c880586be --- /dev/null +++ b/tools/src/command/spartan_key.rs @@ -0,0 +1,98 @@ +use std::{ + io, + path::{Path, PathBuf}, +}; + +use anyhow::Context; + +use nexus_config::{vm as vm_config, Config}; +use nexus_prover::srs::get_min_srs_size; +use nexus_tools_dev::command::common::{ + public_params::{format_params_file, format_srs_file}, + spartan_key::{format_key_file, SetupArgs, SpartanSetupAction, SpartanSetupArgs}, +}; + +use crate::{command::cache_path, LOG_TARGET}; + +pub fn handle_command(args: SpartanSetupArgs) -> anyhow::Result<()> { + let action = args + .command + .unwrap_or_else(|| SpartanSetupAction::Setup(SetupArgs::default())); + match action { + SpartanSetupAction::Setup(setup_args) => { + let _ = spartan_setup(setup_args)?; + } + } + Ok(()) +} + +pub(crate) fn spartan_setup(args: SetupArgs) -> anyhow::Result { + let vm_config = vm_config::VmConfig::from_env()?; + + let force = args.force; + let k = args.k.unwrap_or(vm_config.k); + let nova_impl = vm_config::NovaImpl::ParallelCompressible; + let pp_file = match args.pp_file { + None => { + let pp_file = format_params_file(nova_impl, k); + let cache_path = cache_path()?; + + cache_path.join(pp_file) + } + Some(path) => path, + }; + if !pp_file.try_exists()? { + tracing::error!( + target: LOG_TARGET, + "path {} was not found", + pp_file.display(), + ); + return Err(io::Error::from(io::ErrorKind::NotFound).into()); + } + + let srs_file = match args.srs_file { + None => { + let srs_file_name = format_srs_file(get_min_srs_size(k)?); + let cache_path = cache_path()?; + + cache_path.join(srs_file_name) + } + Some(path) => path, + }; + if !srs_file.try_exists()? { + tracing::error!( + target: LOG_TARGET, + "path {} was not found", + srs_file.display(), + ); + return Err(io::Error::from(io::ErrorKind::NotFound).into()); + } + + let key_path = match args.path { + Some(path) => path, + None => { + let key_file_name = format_key_file(vm_config.k); + let cache_path = cache_path()?; + cache_path.join(key_file_name) + } + }; + if !force && key_path.try_exists()? { + tracing::info!( + target: LOG_TARGET, + "path {} already exists, use `setup --force` to overwrite", + key_path.display(), + ); + return Ok(key_path); + } + spartan_setup_to_file(&key_path, &pp_file, &srs_file)?; + Ok(key_path) +} + +fn spartan_setup_to_file(key_path: &Path, pp_path: &Path, srs_path: &Path) -> anyhow::Result<()> { + let key_path = key_path.to_str().context("path is not valid utf8")?; + let pp_path_str = pp_path.to_str().context("path is not valid utf8")?; + let srs_path_str = srs_path.to_str().context("path is not valid utf8")?; + nexus_prover::key::gen_key_to_file(pp_path_str, srs_path_str, key_path)?; + + Ok(()) +} diff --git a/tools/src/command/verify.rs b/tools/src/command/verify.rs index e025f0fe9..9da2cac6d 100644 --- a/tools/src/command/verify.rs +++ b/tools/src/command/verify.rs @@ -10,9 +10,10 @@ use nexus_config::{ vm::{NovaImpl, VmConfig}, Config, }; -use nexus_prover::types::{IVCProof, PCDNode, ParPP, SeqPP}; +use nexus_prover::types::{ComPCDNode, ComPP, ComProof, IVCProof, PCDNode, ParPP, SeqPP}; use nexus_tools_dev::command::common::{ - prove::LocalProveArgs, public_params::format_params_file, VerifyArgs, + prove::LocalProveArgs, public_params::format_params_file, spartan_key::format_key_file, + VerifyArgs, }; use crate::{command::cache_path, LOG_TARGET}; @@ -20,17 +21,97 @@ use crate::{command::cache_path, LOG_TARGET}; pub fn handle_command(args: VerifyArgs) -> anyhow::Result<()> { let VerifyArgs { file, - prover_args: LocalProveArgs { k, pp_file, nova_impl }, + compressed, + prover_args: LocalProveArgs { k, pp_file, nova_impl, .. }, + key_file, } = args; let vm_config = VmConfig::from_env()?; + if compressed { + verify_proof_compressed(&file, k.unwrap_or(vm_config.k), pp_file, key_file) + } else { + verify_proof( + &file, + k.unwrap_or(vm_config.k), + nova_impl.unwrap_or(vm_config.nova_impl), + pp_file, + ) + } +} + +fn verify_proof_compressed( + path: &Path, + k: usize, + pp_file: Option, + key_file: Option, +) -> anyhow::Result<()> { + let file = File::open(path)?; + let reader = BufReader::new(file); + + let pp_path = match pp_file { + Some(path) => path, + None => { + let pp_file_name = format_params_file(NovaImpl::ParallelCompressible, k); + let cache_path = cache_path()?; + + cache_path.join(pp_file_name) + } + } + .to_str() + .context("path is not utf-8")? + .to_owned(); + + let key_path = match key_file { + Some(path) => path, + None => { + let key_file_name = format_key_file(k); + let cache_path = cache_path()?; - verify_proof( - &file, - k.unwrap_or(vm_config.k), - nova_impl.unwrap_or(vm_config.nova_impl), - pp_file, - ) + cache_path.join(key_file_name) + } + } + .to_str() + .context("path is not utf-8")? + .to_owned(); + + let mut term = nexus_tui::TerminalHandle::new_enabled(); + let mut ctx = term + .context("Verifying compressed") + .on_step(move |_step| "proof".into()); + let mut _guard = Default::default(); + + let result = { + let proof = ComProof::deserialize_compressed(reader)?; + let params = nexus_prover::pp::gen_or_load(false, k, &pp_path, None)?; + let key = nexus_prover::key::gen_or_load_key(false, &key_path, Some(&pp_path), None)?; + + _guard = ctx.display_step(); + nexus_prover::verify_compressed(&key, ¶ms, &proof).map_err(anyhow::Error::from) + }; + + match result { + Ok(_) => { + drop(_guard); + + tracing::info!( + target: LOG_TARGET, + "Compressed proof is valid", + ); + } + Err(err) => { + _guard.abort(); + + tracing::error!( + target: LOG_TARGET, + err = ?err, + ?k, + "Compressed proof is invalid", + ); + std::process::exit(1); + } + } + + Ok(()) } fn verify_proof( @@ -59,6 +140,7 @@ fn verify_proof( let mut ctx = term.context("Verifying").on_step(move |_step| { match nova_impl { NovaImpl::Parallel => "root", + NovaImpl::ParallelCompressible => "root", NovaImpl::Sequential => "proof", } .into() @@ -68,14 +150,21 @@ fn verify_proof( let result = match nova_impl { NovaImpl::Parallel => { let root = PCDNode::deserialize_compressed(reader)?; - let params: ParPP = nexus_prover::pp::gen_or_load(false, k, &path)?; + let params: ParPP = nexus_prover::pp::gen_or_load(false, k, &path, None)?; + + _guard = ctx.display_step(); + root.verify(¶ms).map_err(anyhow::Error::from) + } + NovaImpl::ParallelCompressible => { + let root = ComPCDNode::deserialize_compressed(reader)?; + let params: ComPP = nexus_prover::pp::gen_or_load(false, k, &path, None)?; _guard = ctx.display_step(); root.verify(¶ms).map_err(anyhow::Error::from) } NovaImpl::Sequential => { let proof = IVCProof::deserialize_compressed(reader)?; - let params: SeqPP = nexus_prover::pp::gen_or_load(false, k, &path)?; + let params: SeqPP = nexus_prover::pp::gen_or_load(false, k, &path, None)?; _guard = ctx.display_step(); proof diff --git a/tools/tools-dev/src/command/common/compress.rs b/tools/tools-dev/src/command/common/compress.rs new file mode 100644 index 000000000..01edbace4 --- /dev/null +++ b/tools/tools-dev/src/command/common/compress.rs @@ -0,0 +1,25 @@ +use clap::Args; +use std::path::PathBuf; + +#[derive(Debug, Args)] +pub struct CompressArgs { + /// Number of vm instructions per fold + #[arg(short, name = "k")] + pub k: Option, + + /// Spartan key file + #[arg(long = "key")] + pub key_file: Option, + + /// public parameters file; only needed if generating a new Spartan key + #[arg(short = 'p', long = "public-params")] + pub pp_file: Option, + + /// srs file; only needed if generating a new Spartan key + #[arg(short = 's', long = "structured-reference-string")] + pub srs_file: Option, + + /// File containing uncompressed proof + #[arg(short = 'f', long = "proof-file")] + pub proof_file: PathBuf, +} diff --git a/tools/tools-dev/src/command/common/mod.rs b/tools/tools-dev/src/command/common/mod.rs index 73a0be541..22778e65b 100644 --- a/tools/tools-dev/src/command/common/mod.rs +++ b/tools/tools-dev/src/command/common/mod.rs @@ -1,14 +1,17 @@ use clap::Subcommand; +pub mod compress; pub mod new; pub mod prove; pub mod public_params; pub mod request; pub mod run; +pub mod spartan_key; pub mod verify; pub use self::{ - new::NewArgs, prove::ProveArgs, request::RequestArgs, run::RunArgs, verify::VerifyArgs, + compress::CompressArgs, new::NewArgs, prove::ProveArgs, request::RequestArgs, run::RunArgs, + spartan_key::SpartanSetupArgs, verify::VerifyArgs, }; #[derive(Debug, Subcommand)] @@ -26,6 +29,10 @@ pub enum Command { /// Nova public parameters management. #[clap(name = "pp")] PublicParams(public_params::PublicParamsArgs), + /// Spartan key management. + SpartanKey(spartan_key::SpartanSetupArgs), + /// Compress a Nova proof. + Compress(CompressArgs), } #[cfg(feature = "dev")] diff --git a/tools/tools-dev/src/command/common/prove.rs b/tools/tools-dev/src/command/common/prove.rs index 91a24cc81..0182ba58f 100644 --- a/tools/tools-dev/src/command/common/prove.rs +++ b/tools/tools-dev/src/command/common/prove.rs @@ -31,6 +31,10 @@ pub struct LocalProveArgs { #[arg(long("impl"))] pub nova_impl: Option, + + /// Path to the SRS file: only needed when pp_file is None and nova_impl is ParallelCompressible. + #[arg(long("srs-file"))] + pub srs_file: Option, } #[derive(Debug, Args)] diff --git a/tools/tools-dev/src/command/common/public_params.rs b/tools/tools-dev/src/command/common/public_params.rs index f480a4c34..5d640b411 100644 --- a/tools/tools-dev/src/command/common/public_params.rs +++ b/tools/tools-dev/src/command/common/public_params.rs @@ -13,6 +13,27 @@ pub struct PublicParamsArgs { pub enum PublicParamsAction { /// Generate public parameters to file. Setup(SetupArgs), + /// Sample SRS for testing to file: NOT SECURE, and memory-heavy operation. + SampleTestSRS(SRSSetupArgs), +} + +#[derive(Debug, Default, Args)] +pub struct SRSSetupArgs { + /// Number of vm instructions per fold; defaults to reading value from vm config. + #[arg(short, name = "k")] + pub k: Option, + + /// Number of variables: defaults to minimum needed for compression for the given `k`. + #[arg(short = 'n', long = "num-vars")] + pub num_vars: Option, + + /// File to save test SRS + #[arg(short, long)] + pub file: Option, + + /// Overwrite the file if it already exists. + #[arg(long)] + pub force: bool, } #[derive(Debug, Default, Args)] @@ -31,9 +52,17 @@ pub struct SetupArgs { /// Overwrite the file if it already exists. #[arg(long)] pub force: bool, + + /// Path to the SRS file (only required for compressible PCD proofs). + #[arg(long("srs_file"))] + pub srs_file: Option, } // TODO: make it accessible to all crates. pub fn format_params_file(nova_impl: vm_config::NovaImpl, k: usize) -> String { format!("nexus-public-{nova_impl}-{k}.zst") } + +pub fn format_srs_file(num_vars: usize) -> String { + format!("nexus-srs-{num_vars}.zst") +} diff --git a/tools/tools-dev/src/command/common/spartan_key.rs b/tools/tools-dev/src/command/common/spartan_key.rs new file mode 100644 index 000000000..f265ce751 --- /dev/null +++ b/tools/tools-dev/src/command/common/spartan_key.rs @@ -0,0 +1,42 @@ +use std::path::PathBuf; + +use clap::{Args, Subcommand}; + +#[derive(Debug, Args)] +pub struct SpartanSetupArgs { + #[command(subcommand)] + pub command: Option, +} + +#[derive(Debug, Subcommand)] +pub enum SpartanSetupAction { + /// Generate Spartan key to file. + Setup(SetupArgs), +} + +#[derive(Debug, Default, Args)] +pub struct SetupArgs { + /// Where to save the file. + #[arg(short, long)] + pub path: Option, + + /// Overwrite the file if it already exists + #[arg(long)] + pub force: bool, + + /// Number of vm instructions per fold. + #[arg(short, name = "k")] + pub k: Option, + + /// Path to Nova public parameters file. + #[arg(short = 'p', long = "public_params")] + pub pp_file: Option, + + /// Path to the Zeromorph structured reference string. + #[arg(short = 's', long = "srs")] + pub srs_file: Option, +} + +pub fn format_key_file(k: usize) -> String { + format!("nexus-spartan-key-{k}.zst") +} diff --git a/tools/tools-dev/src/command/common/verify.rs b/tools/tools-dev/src/command/common/verify.rs index ebe5f55bc..0bfce9313 100644 --- a/tools/tools-dev/src/command/common/verify.rs +++ b/tools/tools-dev/src/command/common/verify.rs @@ -10,6 +10,14 @@ pub struct VerifyArgs { #[arg(default_value = "nexus-proof")] pub file: PathBuf, + /// whether the proof has been compressed + #[arg(long, short, default_value = "false")] + pub compressed: bool, + #[clap(flatten)] pub prover_args: LocalProveArgs, + + /// File containing the Spartan key; only needed when 'compressed' is true + #[arg(long = "key-file", short = 'k')] + pub key_file: Option, } diff --git a/tools/tools-dev/src/command/dev/common_impl/prove.rs b/tools/tools-dev/src/command/dev/common_impl/prove.rs index 31fff8051..65527545d 100644 --- a/tools/tools-dev/src/command/dev/common_impl/prove.rs +++ b/tools/tools-dev/src/command/dev/common_impl/prove.rs @@ -52,10 +52,10 @@ pub fn handle_command(args: ProveArgs) -> anyhow::Result<()> { // build artifact if needed cargo(None, ["build", "--profile", &profile])?; - let LocalProveArgs { k, pp_file, nova_impl } = local_args; + let LocalProveArgs { k, pp_file, nova_impl, srs_file } = local_args; let k = k.unwrap_or(vm_config.k); let nova_impl = nova_impl.unwrap_or(vm_config.nova_impl); - local_prove(&path, k, nova_impl, pp_file) + local_prove(&path, k, nova_impl, pp_file, srs_file) } } @@ -80,6 +80,7 @@ fn local_prove( k: usize, nova_impl: NovaImpl, pp_file: Option, + srs_file: Option, ) -> anyhow::Result<()> { // setup if necessary let pp_file = if let Some(path) = pp_file { @@ -99,6 +100,7 @@ fn local_prove( nova_impl: Some(nova_impl), path: None, force: false, + srs_file, })? }; diff --git a/tools/tools-dev/src/command/dev/common_impl/public_params.rs b/tools/tools-dev/src/command/dev/common_impl/public_params.rs index 46e4aeee8..493b24fd9 100644 --- a/tools/tools-dev/src/command/dev/common_impl/public_params.rs +++ b/tools/tools-dev/src/command/dev/common_impl/public_params.rs @@ -6,7 +6,7 @@ use nexus_config::{vm as vm_config, Config}; use crate::{ command::{ common::public_params::{ - format_params_file, PublicParamsAction, PublicParamsArgs, SetupArgs, + format_params_file, PublicParamsAction, PublicParamsArgs, SRSSetupArgs, SetupArgs, }, dev::{cache_path, compile_env_configs}, }, @@ -22,6 +22,9 @@ pub(crate) fn handle_command(args: PublicParamsArgs) -> anyhow::Result<()> { PublicParamsAction::Setup(setup_args) => { let _ = setup_params_from_env(setup_args)?; } + PublicParamsAction::SampleTestSRS(srs_setup_args) => { + let _ = sample_test_srs(srs_setup_args)?; + } } Ok(()) } @@ -34,12 +37,13 @@ pub(crate) fn setup_params_from_env(args: SetupArgs) -> anyhow::Result let force = args.force; let k = args.k.unwrap_or(vm_config.k); let nova_impl = args.nova_impl.unwrap_or(vm_config.nova_impl); + let srs_file = args.srs_file.as_deref(); let path = match args.path { Some(path) => path, None => { let pp_file_name = format_params_file(nova_impl, k); - cache_path()?.join(&pp_file_name) + cache_path()?.join(pp_file_name) } }; @@ -52,7 +56,7 @@ pub(crate) fn setup_params_from_env(args: SetupArgs) -> anyhow::Result return Ok(path); } - setup_params_to_file(&path, nova_impl, k)?; + setup_params_to_file(&path, nova_impl, k, srs_file)?; Ok(path) } @@ -60,6 +64,7 @@ fn setup_params_to_file( path: &Path, nova_impl: vm_config::NovaImpl, k: usize, + _srs_file: Option<&Path>, ) -> anyhow::Result<()> { // -k= [-p=] [--par] let mut prover_opts = vec![ @@ -69,6 +74,7 @@ fn setup_params_to_file( if let vm_config::NovaImpl::Parallel = nova_impl { prover_opts.push("-P".into()); } + // TODO: handle case of NovaImpl::ParallelCompressible let mut cargo_opts: Vec = ["run", "--release", "-p", "nexus-prover", "gen"] .into_iter() @@ -79,3 +85,8 @@ fn setup_params_to_file( // run from workspace cargo(cargo_manifest_dir_path!().into(), cargo_opts) } + +// TODO: fill this in +fn sample_test_srs(_args: SRSSetupArgs) -> anyhow::Result { + unimplemented!() +} diff --git a/tools/tools-dev/src/command/dev/common_impl/run.rs b/tools/tools-dev/src/command/dev/common_impl/run.rs index f0d6e8bd9..6c17d32db 100644 --- a/tools/tools-dev/src/command/dev/common_impl/run.rs +++ b/tools/tools-dev/src/command/dev/common_impl/run.rs @@ -14,10 +14,10 @@ pub fn handle_command(args: RunArgs) -> anyhow::Result<()> { fn run_vm(bin: Option, verbose: bool, release: bool) -> anyhow::Result<()> { // build the artifact let profile = if release { - cargo(None, &["build", "--release"])?; + cargo(None, ["build", "--release"])?; "release" } else { - cargo(None, &["build"])?; + cargo(None, ["build"])?; "debug" }; diff --git a/tools/tools-dev/src/command/dev/common_impl/verify.rs b/tools/tools-dev/src/command/dev/common_impl/verify.rs index 20cc3e35f..e42521e4c 100644 --- a/tools/tools-dev/src/command/dev/common_impl/verify.rs +++ b/tools/tools-dev/src/command/dev/common_impl/verify.rs @@ -11,13 +11,19 @@ pub fn handle_command(args: VerifyArgs) -> anyhow::Result<()> { let VerifyArgs { file, prover_args: LocalProveArgs { pp_file, .. }, + compressed, + key_file, } = args; // make sure configs are compiled compile_env_configs(false)?; let vm_config = VmConfig::from_env()?; - verify_proof(&file, vm_config.k, pp_file) + if compressed { + verify_proof_compressed(&file, vm_config.k, pp_file, key_file) + } else { + verify_proof(&file, vm_config.k, pp_file) + } } fn verify_proof(_path: &Path, _k: usize, _pp_file: Option) -> anyhow::Result<()> { @@ -27,3 +33,16 @@ fn verify_proof(_path: &Path, _k: usize, _pp_file: Option) -> anyhow::R // TODO: extract encoding into a separate crate. unimplemented!() } + +fn verify_proof_compressed( + _path: &Path, + _k: usize, + _pp_file: Option, + _key_file: Option, +) -> anyhow::Result<()> { + // This command should be part of `nexus-prover`, which doesn't support decoding + // proofs from the network. + // + // TODO: extract encoding into a separate crate. + unimplemented!() +} diff --git a/tools/tools-dev/src/command/dev/config.rs b/tools/tools-dev/src/command/dev/config.rs index 0da580711..01e8a5cef 100644 --- a/tools/tools-dev/src/command/dev/config.rs +++ b/tools/tools-dev/src/command/dev/config.rs @@ -54,7 +54,7 @@ pub(crate) fn compile_to_env_from_bases(force: bool) -> anyhow::Result<()> { } // rust envs are parsed separately let config_path = Path::new(CONFIG_BASE_DIR).join(RUST_ENV_FILE); - let raw_file = fs::read_to_string(&config_path)?; + let raw_file = fs::read_to_string(config_path)?; parse_rust_env(&raw_file, &mut flat_config)?; let env_out = flat_config