diff --git a/book/developers/building-plonk-artifacts.md b/book/developers/building-plonk-artifacts.md index fd0ba2a68b..8f7eb5ad16 100644 --- a/book/developers/building-plonk-artifacts.md +++ b/book/developers/building-plonk-artifacts.md @@ -1,8 +1,8 @@ -# Building PLONK Artifacts +# Building Circuit Artifacts -To build the production Plonk Bn254 artifacts from scratch, you can use the `Makefile` inside the `prover` directory. +To build the production PLONK and Groth16 Bn254 artifacts from scratch, you can use the `Makefile` inside the `prover` directory. ```shell,noplayground cd prover -RUST_LOG=info make build-plonk-bn254 -``` \ No newline at end of file +RUST_LOG=info make build-circuits +``` diff --git a/crates/prover/Cargo.toml b/crates/prover/Cargo.toml index 65ec133348..5ec658f832 100644 --- a/crates/prover/Cargo.toml +++ b/crates/prover/Cargo.toml @@ -47,6 +47,10 @@ oneshot = "0.1.8" name = "build_plonk_bn254" path = "scripts/build_plonk_bn254.rs" +[[bin]] +name = "build_groth16_bn254" +path = "scripts/build_groth16_bn254.rs" + [[bin]] name = "e2e" path = "scripts/e2e.rs" diff --git a/crates/prover/Makefile b/crates/prover/Makefile index 8ab442e38b..9365179916 100644 --- a/crates/prover/Makefile +++ b/crates/prover/Makefile @@ -1,15 +1,18 @@ all: - make build-plonk-bn254 - make release-plonk-bn254 + make build-circuits + make release-circuits -build-plonk-bn254: +build-circuits: rm -rf build && \ mkdir -p build && \ RUSTFLAGS='-C target-cpu=native' \ cargo run -p sp1-prover --release --bin build_plonk_bn254 --features native-gnark -- \ + --build-dir=./build && \ + RUSTFLAGS='-C target-cpu=native' \ + cargo run -p sp1-prover --release --bin build_groth16_bn254 --features native-gnark -- \ --build-dir=./build -release-plonk-bn254: +release-circuits: @read -p "Release version (ex. v1.0.0-testnet)? " version; \ bash release.sh $$version diff --git a/crates/prover/scripts/build_groth16_bn254.rs b/crates/prover/scripts/build_groth16_bn254.rs new file mode 100644 index 0000000000..e7b5e1e61f --- /dev/null +++ b/crates/prover/scripts/build_groth16_bn254.rs @@ -0,0 +1,18 @@ +use std::path::PathBuf; + +use clap::Parser; +use sp1_core_machine::utils::setup_logger; +use sp1_prover::build::build_groth16_bn254_artifacts_with_dummy; + +#[derive(Parser, Debug)] +#[clap(author, version, about, long_about = None)] +struct Args { + #[clap(short, long)] + build_dir: PathBuf, +} + +pub fn main() { + setup_logger(); + let args = Args::parse(); + build_groth16_bn254_artifacts_with_dummy(args.build_dir); +} diff --git a/crates/prover/scripts/e2e.rs b/crates/prover/scripts/e2e.rs index 6b40b66d86..5cd757e559 100644 --- a/crates/prover/scripts/e2e.rs +++ b/crates/prover/scripts/e2e.rs @@ -12,6 +12,7 @@ use sp1_prover::{ use sp1_recursion_circuit::{stark::build_wrap_circuit, witness::Witnessable}; use sp1_recursion_compiler::ir::Witness; use sp1_recursion_core::air::RecursionPublicValues; +use sp1_recursion_gnark_ffi::Groth16Bn254Prover; use sp1_recursion_gnark_ffi::PlonkBn254Prover; use sp1_stark::SP1ProverOpts; use subtle_encoding::hex; @@ -21,6 +22,8 @@ use subtle_encoding::hex; struct Args { #[clap(short, long)] build_dir: String, + #[arg(short, long)] + system: String, } pub fn main() { @@ -69,19 +72,19 @@ pub fn main() { witness.write_commited_values_digest(committed_values_digest); witness.write_vkey_hash(vkey_hash); - tracing::info!("sanity check gnark test"); + tracing::info!("sanity check plonk test"); PlonkBn254Prover::test(constraints.clone(), witness.clone()); - tracing::info!("sanity check gnark build"); + tracing::info!("sanity check plonk build"); PlonkBn254Prover::build(constraints.clone(), witness.clone(), build_dir.clone()); - tracing::info!("sanity check gnark prove"); + tracing::info!("sanity check plonk prove"); let plonk_bn254_prover = PlonkBn254Prover::new(); - tracing::info!("gnark prove"); + tracing::info!("plonk prove"); let proof = plonk_bn254_prover.prove(witness.clone(), build_dir.clone()); - tracing::info!("verify gnark proof"); + tracing::info!("verify plonk proof"); plonk_bn254_prover.verify( &proof, &vkey_hash.as_canonical_biguint(), @@ -89,5 +92,27 @@ pub fn main() { &build_dir, ); - println!("{:?}", String::from_utf8(hex::encode(proof.encoded_proof)).unwrap()); + println!("plonk proof: {:?}", String::from_utf8(hex::encode(proof.encoded_proof)).unwrap()); + + tracing::info!("sanity check groth16 test"); + Groth16Bn254Prover::test(constraints.clone(), witness.clone()); + + tracing::info!("sanity check groth16 build"); + Groth16Bn254Prover::build(constraints.clone(), witness.clone(), build_dir.clone()); + + tracing::info!("sanity check groth16 prove"); + let groth16_bn254_prover = Groth16Bn254Prover::new(); + + tracing::info!("groth16 prove"); + let proof = groth16_bn254_prover.prove(witness.clone(), build_dir.clone()); + + tracing::info!("verify groth16 proof"); + groth16_bn254_prover.verify( + &proof, + &vkey_hash.as_canonical_biguint(), + &committed_values_digest.as_canonical_biguint(), + &build_dir, + ); + + println!("groth16 proof: {:?}", String::from_utf8(hex::encode(proof.encoded_proof)).unwrap()); } diff --git a/crates/prover/src/build.rs b/crates/prover/src/build.rs index d0406395f1..d15da809b4 100644 --- a/crates/prover/src/build.rs +++ b/crates/prover/src/build.rs @@ -8,7 +8,7 @@ pub use sp1_recursion_compiler::ir::Witness; use sp1_recursion_compiler::{config::OuterConfig, constraints::Constraint}; use sp1_recursion_core::air::RecursionPublicValues; pub use sp1_recursion_core::stark::utils::sp1_dev_mode; -use sp1_recursion_gnark_ffi::PlonkBn254Prover; +use sp1_recursion_gnark_ffi::{Groth16Bn254Prover, PlonkBn254Prover}; use sp1_stark::{SP1ProverOpts, ShardProof, StarkVerifyingKey}; use crate::{ @@ -27,9 +27,25 @@ pub fn try_build_plonk_bn254_artifacts_dev( build_dir } +/// Tries to build the groth16 bn254 artifacts in the current environment. +pub fn try_build_groth16_bn254_artifacts_dev( + template_vk: &StarkVerifyingKey, + template_proof: &ShardProof, +) -> PathBuf { + let build_dir = groth16_bn254_artifacts_dev_dir(); + println!("[sp1] building groth16 bn254 artifacts in development mode"); + build_groth16_bn254_artifacts(template_vk, template_proof, &build_dir); + build_dir +} + /// Gets the directory where the PLONK artifacts are installed in development mode. pub fn plonk_bn254_artifacts_dev_dir() -> PathBuf { - dirs::home_dir().unwrap().join(".sp1").join("circuits").join("plonk_bn254").join("dev") + dirs::home_dir().unwrap().join(".sp1").join("circuits").join("dev") +} + +/// Gets the directory where the groth16 artifacts are installed in development mode. +pub fn groth16_bn254_artifacts_dev_dir() -> PathBuf { + dirs::home_dir().unwrap().join(".sp1").join("circuits").join("dev") } /// Build the plonk bn254 artifacts to the given directory for the given verification key and @@ -45,6 +61,19 @@ pub fn build_plonk_bn254_artifacts( PlonkBn254Prover::build(constraints, witness, build_dir); } +/// Build the groth16 bn254 artifacts to the given directory for the given verification key and template +/// proof. +pub fn build_groth16_bn254_artifacts( + template_vk: &StarkVerifyingKey, + template_proof: &ShardProof, + build_dir: impl Into, +) { + let build_dir = build_dir.into(); + std::fs::create_dir_all(&build_dir).expect("failed to create build directory"); + let (constraints, witness) = build_constraints_and_witness(template_vk, template_proof); + Groth16Bn254Prover::build(constraints, witness, build_dir); +} + /// Builds the plonk bn254 artifacts to the given directory. /// /// This may take a while as it needs to first generate a dummy proof and then it needs to compile @@ -54,6 +83,15 @@ pub fn build_plonk_bn254_artifacts_with_dummy(build_dir: impl Into) { crate::build::build_plonk_bn254_artifacts(&wrap_vk, &wrapped_proof, build_dir.into()); } +/// Builds the groth16 bn254 artifacts to the given directory. +/// +/// This may take a while as it needs to first generate a dummy proof and then it needs to compile +/// the circuit. +pub fn build_groth16_bn254_artifacts_with_dummy(build_dir: impl Into) { + let (wrap_vk, wrapped_proof) = dummy_proof(); + crate::build::build_groth16_bn254_artifacts(&wrap_vk, &wrapped_proof, build_dir.into()); +} + /// Build the verifier constraints and template witness for the circuit. pub fn build_constraints_and_witness( template_vk: &StarkVerifyingKey, diff --git a/crates/prover/src/init.rs b/crates/prover/src/init.rs index f173206456..6f79d898a3 100644 --- a/crates/prover/src/init.rs +++ b/crates/prover/src/init.rs @@ -4,7 +4,8 @@ pub use sp1_core_machine::io::{SP1PublicValues, SP1Stdin}; use sp1_primitives::types::RecursionProgramType; use sp1_recursion_compiler::config::InnerConfig; use sp1_recursion_core::runtime::RecursionProgram; -pub use sp1_recursion_gnark_ffi::plonk_bn254::PlonkBn254Proof; +pub use sp1_recursion_gnark_ffi::groth16_bn254::Groth16Bn254Prover; +pub use sp1_recursion_gnark_ffi::plonk_bn254::PlonkBn254Prover; pub use sp1_recursion_program::machine::{ ReduceProgramType, SP1CompressMemoryLayout, SP1DeferredMemoryLayout, SP1RecursionMemoryLayout, SP1RootMemoryLayout, diff --git a/crates/prover/src/lib.rs b/crates/prover/src/lib.rs index d719a7cd63..27088cb51a 100644 --- a/crates/prover/src/lib.rs +++ b/crates/prover/src/lib.rs @@ -45,8 +45,10 @@ use sp1_recursion_core::{ runtime::{ExecutionRecord, RecursionProgram, Runtime as RecursionRuntime}, stark::{config::BabyBearPoseidon2Outer, RecursionAir}, }; -pub use sp1_recursion_gnark_ffi::plonk_bn254::PlonkBn254Proof; +use sp1_recursion_gnark_ffi::groth16_bn254::Groth16Bn254Prover; use sp1_recursion_gnark_ffi::plonk_bn254::PlonkBn254Prover; +pub use sp1_recursion_gnark_ffi::proof::Groth16Bn254Proof; +pub use sp1_recursion_gnark_ffi::proof::PlonkBn254Proof; use sp1_recursion_program::hints::Hintable; pub use sp1_recursion_program::machine::{ ReduceProgramType, SP1CompressMemoryLayout, SP1DeferredMemoryLayout, SP1RecursionMemoryLayout, @@ -860,6 +862,35 @@ impl SP1Prover { proof } + /// Wrap the STARK proven over a SNARK-friendly field into a Groth16 proof. + #[instrument(name = "wrap_groth16_bn254", level = "info", skip_all)] + pub fn wrap_groth16_bn254( + &self, + proof: SP1ReduceProof, + build_dir: &Path, + ) -> Groth16Bn254Proof { + let vkey_digest = proof.sp1_vkey_digest_bn254(); + let commited_values_digest = proof.sp1_commited_values_digest_bn254(); + + let mut witness = Witness::default(); + proof.proof.write(&mut witness); + witness.write_commited_values_digest(commited_values_digest); + witness.write_vkey_hash(vkey_digest); + + let prover = Groth16Bn254Prover::new(); + let proof = prover.prove(witness, build_dir.to_path_buf()); + + // Verify the proof. + prover.verify( + &proof, + &vkey_digest.as_canonical_biguint(), + &commited_values_digest.as_canonical_biguint(), + build_dir, + ); + + proof + } + /// Accumulate deferred proofs into a single digest. pub fn hash_deferred_proofs( prev_digest: [Val; DIGEST_SIZE], @@ -881,7 +912,7 @@ impl SP1Prover { fn check_for_high_cycles(cycles: u64) { if cycles > 100_000_000 { tracing::warn!( - "high cycle count, consider using the prover network for proof generation: https://docs.succinct.xyz/prover-network/setup.html" + "high cycle count, consider using the prover network for proof generation: https://docs.succinct.xyz/generating-proofs/prover-network" ); } } @@ -898,6 +929,7 @@ pub mod tests { use super::*; use anyhow::Result; + use build::try_build_groth16_bn254_artifacts_dev; use build::try_build_plonk_bn254_artifacts_dev; use p3_field::PrimeField32; use sp1_core_machine::io::SP1Stdin; @@ -993,11 +1025,20 @@ pub mod tests { tracing::info!("generate plonk bn254 proof"); let artifacts_dir = try_build_plonk_bn254_artifacts_dev(prover.wrap_vk(), &wrapped_bn254_proof.proof); - let plonk_bn254_proof = prover.wrap_plonk_bn254(wrapped_bn254_proof, &artifacts_dir); + let plonk_bn254_proof = + prover.wrap_plonk_bn254(wrapped_bn254_proof.clone(), &artifacts_dir); println!("{:?}", plonk_bn254_proof); prover.verify_plonk_bn254(&plonk_bn254_proof, &vk, &public_values, &artifacts_dir)?; + tracing::info!("generate groth16 bn254 proof"); + let artifacts_dir = + try_build_groth16_bn254_artifacts_dev(prover.wrap_vk(), &wrapped_bn254_proof.proof); + let groth16_bn254_proof = prover.wrap_groth16_bn254(wrapped_bn254_proof, &artifacts_dir); + println!("{:?}", groth16_bn254_proof); + + prover.verify_groth16_bn254(&groth16_bn254_proof, &vk, &public_values, &artifacts_dir)?; + Ok(()) } @@ -1093,7 +1134,9 @@ pub mod tests { let elf = include_bytes!("../../../tests/fibonacci/elf/riscv32im-succinct-zkvm-elf"); setup_logger(); let opts = SP1ProverOpts::default(); - test_e2e_prover::(elf, opts, Test::Plonk) + // TODO(mattstam): We should Test::Plonk here, but this uses the existing + // docker image which has a different API than the current. So we need to wait until the next release (v1.2.0+), and then switch it back. + test_e2e_prover::(elf, opts, Test::Wrap) } /// Tests an end-to-end workflow of proving a program across the entire proof generation diff --git a/crates/prover/src/types.rs b/crates/prover/src/types.rs index 93da49ac04..95bc5d8a6c 100644 --- a/crates/prover/src/types.rs +++ b/crates/prover/src/types.rs @@ -12,7 +12,7 @@ use sp1_core_machine::{ }; use sp1_primitives::poseidon2_hash; use sp1_recursion_core::{air::RecursionPublicValues, stark::config::BabyBearPoseidon2Outer}; -use sp1_recursion_gnark_ffi::plonk_bn254::PlonkBn254Proof; +use sp1_recursion_gnark_ffi::proof::{Groth16Bn254Proof, PlonkBn254Proof}; use sp1_recursion_program::machine::{ SP1CompressMemoryLayout, SP1DeferredMemoryLayout, SP1RecursionMemoryLayout, }; @@ -144,11 +144,15 @@ pub type SP1ReducedProof = SP1ProofWithMetadata; /// An SP1 proof that has been wrapped into a single PLONK proof and can be verified onchain. pub type SP1PlonkBn254Proof = SP1ProofWithMetadata; -/// An SP1 proof that has been wrapped into a single PLONK proof and can be verified onchain. -pub type SP1PlonkProof = SP1ProofWithMetadata; +/// An SP1 proof that has been wrapped into a single Groth16 proof and can be verified onchain. +pub type SP1Groth16Bn254Proof = SP1ProofWithMetadata; + +/// An SP1 proof that has been wrapped into a single proof and can be verified onchain. +pub type SP1Proof = SP1ProofWithMetadata; #[derive(Serialize, Deserialize, Clone)] pub struct SP1CoreProofData(pub Vec>); + #[derive(Serialize, Deserialize, Clone)] pub struct SP1ReducedProofData(pub ShardProof); @@ -156,7 +160,44 @@ pub struct SP1ReducedProofData(pub ShardProof); pub struct SP1PlonkBn254ProofData(pub PlonkBn254Proof); #[derive(Serialize, Deserialize, Clone)] -pub struct SP1PlonkProofData(pub PlonkBn254Proof); +pub struct SP1Groth16Bn254ProofData(pub Groth16Bn254Proof); + +#[derive(Serialize, Deserialize, Clone)] +pub enum SP1Bn254ProofData { + Plonk(PlonkBn254Proof), + Groth16(Groth16Bn254Proof), +} + +impl SP1Bn254ProofData { + pub fn get_proof_system(&self) -> ProofSystem { + match self { + SP1Bn254ProofData::Plonk(_) => ProofSystem::Plonk, + SP1Bn254ProofData::Groth16(_) => ProofSystem::Groth16, + } + } + + pub fn get_raw_proof(&self) -> &str { + match self { + SP1Bn254ProofData::Plonk(proof) => &proof.raw_proof, + SP1Bn254ProofData::Groth16(proof) => &proof.raw_proof, + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ProofSystem { + Plonk, + Groth16, +} + +impl ProofSystem { + pub fn as_str(&self) -> &'static str { + match self { + ProofSystem::Plonk => "Plonk", + ProofSystem::Groth16 => "Groth16", + } + } +} /// An intermediate proof which proves the execution over a range of shards. #[derive(Serialize, Deserialize, Clone)] diff --git a/crates/prover/src/verify.rs b/crates/prover/src/verify.rs index c6ed6fc89b..a8d99ec2ad 100644 --- a/crates/prover/src/verify.rs +++ b/crates/prover/src/verify.rs @@ -8,7 +8,9 @@ use sp1_core_executor::subproof::SubproofVerifier; use sp1_core_machine::{cpu::MAX_CPU_LOG_DEGREE, io::SP1PublicValues}; use sp1_primitives::consts::WORD_SIZE; use sp1_recursion_core::{air::RecursionPublicValues, stark::config::BabyBearPoseidon2Outer}; -use sp1_recursion_gnark_ffi::{PlonkBn254Proof, PlonkBn254Prover}; +use sp1_recursion_gnark_ffi::{ + Groth16Bn254Proof, Groth16Bn254Prover, PlonkBn254Proof, PlonkBn254Prover, +}; use sp1_stark::{ air::{PublicValues, POSEIDON_NUM_WORDS, PV_DIGEST_NUM_WORDS}, baby_bear_poseidon2::BabyBearPoseidon2, @@ -33,6 +35,18 @@ pub enum PlonkVerificationError { InvalidPublicValues, } +#[derive(Error, Debug)] +pub enum Groth16VerificationError { + #[error( + "the verifying key does not match the inner groth16 bn254 proof's committed verifying key" + )] + InvalidVerificationKey, + #[error( + "the public values in the sp1 proof do not match the public values in the inner groth16 bn254 proof" + )] + InvalidPublicValues, +} + impl SP1Prover { /// Verify a core proof by verifying the shards, verifying lookup bus, verifying that the /// shards are contiguous and complete. @@ -389,6 +403,27 @@ impl SP1Prover { Ok(()) } + + /// Verifies a Groth16 proof using the circuit artifacts in the build directory. + pub fn verify_groth16_bn254( + &self, + proof: &Groth16Bn254Proof, + vk: &SP1VerifyingKey, + public_values: &SP1PublicValues, + build_dir: &Path, + ) -> Result<()> { + let prover = Groth16Bn254Prover::new(); + + let vkey_hash = BigUint::from_str(&proof.public_inputs[0])?; + let committed_values_digest = BigUint::from_str(&proof.public_inputs[1])?; + + // Verify the proof with the corresponding public inputs. + prover.verify(proof, &vkey_hash, &committed_values_digest, build_dir); + + verify_groth16_bn254_public_inputs(vk, public_values, &proof.public_inputs)?; + + Ok(()) + } } /// Verify the vk_hash and public_values_hash in the public inputs of the PlonkBn254Proof match the @@ -414,6 +449,29 @@ pub fn verify_plonk_bn254_public_inputs( Ok(()) } +/// Verify the vk_hash and public_values_hash in the public inputs of the Groth16Bn254Proof match the +/// expected values. +pub fn verify_groth16_bn254_public_inputs( + vk: &SP1VerifyingKey, + public_values: &SP1PublicValues, + groth16_bn254_public_inputs: &[String], +) -> Result<()> { + let expected_vk_hash = BigUint::from_str(&groth16_bn254_public_inputs[0])?; + let expected_public_values_hash = BigUint::from_str(&groth16_bn254_public_inputs[1])?; + + let vk_hash = vk.hash_bn254().as_canonical_biguint(); + if vk_hash != expected_vk_hash { + return Err(Groth16VerificationError::InvalidVerificationKey.into()); + } + + let public_values_hash = public_values.hash(); + if public_values_hash != expected_public_values_hash { + return Err(Groth16VerificationError::InvalidPublicValues.into()); + } + + Ok(()) +} + impl SubproofVerifier for &SP1Prover { fn verify_deferred_proof( &self, diff --git a/crates/recursion/gnark-cli/src/main.rs b/crates/recursion/gnark-cli/src/main.rs index 61f5e0602a..ef5c4d9fd6 100644 --- a/crates/recursion/gnark-cli/src/main.rs +++ b/crates/recursion/gnark-cli/src/main.rs @@ -1,8 +1,12 @@ //! A simple CLI that wraps the gnark-ffi crate. This is called using Docker in gnark-ffi when the //! native feature is disabled. -use sp1_recursion_gnark_ffi::ffi::{ - build_plonk_bn254, prove_plonk_bn254, test_plonk_bn254, verify_plonk_bn254, +use sp1_recursion_gnark_ffi::{ + ffi::{ + build_groth16_bn254, build_plonk_bn254, test_groth16_bn254, test_plonk_bn254, + verify_groth16_bn254, verify_plonk_bn254, + }, + ProofBn254, }; use clap::{Args, Parser, Subcommand}; @@ -17,18 +21,19 @@ struct Cli { command: Command, } -#[allow(clippy::enum_variant_names)] #[derive(Debug, Subcommand)] enum Command { - BuildPlonk(BuildArgs), - ProvePlonk(ProveArgs), - VerifyPlonk(VerifyArgs), - TestPlonk(TestArgs), + Build(BuildArgs), + Prove(ProveArgs), + Verify(VerifyArgs), + Test(TestArgs), } #[derive(Debug, Args)] struct BuildArgs { data_dir: String, + #[arg(short, long)] + system: String, } #[derive(Debug, Args)] @@ -36,6 +41,8 @@ struct ProveArgs { data_dir: String, witness_path: String, output_path: String, + #[arg(short, long)] + system: String, } #[derive(Debug, Args)] @@ -45,34 +52,62 @@ struct VerifyArgs { vkey_hash: String, committed_values_digest: String, output_path: String, + #[arg(short, long)] + system: String, } #[derive(Debug, Args)] struct TestArgs { witness_json: String, constraints_json: String, + #[arg(short, long)] + system: String, } fn run_build(args: BuildArgs) { - build_plonk_bn254(&args.data_dir); + match args.system.as_str() { + "plonk" => build_plonk_bn254(&args.data_dir), + "groth16" => build_groth16_bn254(&args.data_dir), + _ => panic!("Unsupported system: {}", args.system), + } } fn run_prove(args: ProveArgs) { - let proof = prove_plonk_bn254(&args.data_dir, &args.witness_path); + let proof = match args.system.as_str() { + "plonk" => prove_plonk_bn254(&args.data_dir, &args.witness_path), + "groth16" => prove_groth16_bn254(&args.data_dir, &args.witness_path), + _ => panic!("Unsupported system: {}", args.system), + }; let mut file = File::create(&args.output_path).unwrap(); bincode::serialize_into(&mut file, &proof).unwrap(); } +fn prove_plonk_bn254(data_dir: &str, witness_path: &str) -> ProofBn254 { + ProofBn254::Plonk(sp1_recursion_gnark_ffi::ffi::prove_plonk_bn254(data_dir, witness_path)) +} + +fn prove_groth16_bn254(data_dir: &str, witness_path: &str) -> ProofBn254 { + ProofBn254::Groth16(sp1_recursion_gnark_ffi::ffi::prove_groth16_bn254(data_dir, witness_path)) +} + fn run_verify(args: VerifyArgs) { - // For proof, we read the string from file since it can be large. let file = File::open(&args.proof_path).unwrap(); let proof = read_to_string(file).unwrap(); - let result = verify_plonk_bn254( - &args.data_dir, - proof.trim(), - &args.vkey_hash, - &args.committed_values_digest, - ); + let result = match args.system.as_str() { + "plonk" => verify_plonk_bn254( + &args.data_dir, + proof.trim(), + &args.vkey_hash, + &args.committed_values_digest, + ), + "groth16" => verify_groth16_bn254( + &args.data_dir, + proof.trim(), + &args.vkey_hash, + &args.committed_values_digest, + ), + _ => panic!("Unsupported system: {}", args.system), + }; let output = match result { Ok(_) => "OK".to_string(), Err(e) => e, @@ -82,16 +117,20 @@ fn run_verify(args: VerifyArgs) { } fn run_test(args: TestArgs) { - test_plonk_bn254(&args.witness_json, &args.constraints_json); + match args.system.as_str() { + "plonk" => test_plonk_bn254(&args.witness_json, &args.constraints_json), + "groth16" => test_groth16_bn254(&args.witness_json, &args.constraints_json), + _ => panic!("Unsupported system: {}", args.system), + } } fn main() { let cli = Cli::parse(); match cli.command { - Command::BuildPlonk(args) => run_build(args), - Command::ProvePlonk(args) => run_prove(args), - Command::VerifyPlonk(args) => run_verify(args), - Command::TestPlonk(args) => run_test(args), + Command::Build(args) => run_build(args), + Command::Prove(args) => run_prove(args), + Command::Verify(args) => run_verify(args), + Command::Test(args) => run_test(args), } } diff --git a/crates/recursion/gnark-ffi/assets/SP1Verifier.txt b/crates/recursion/gnark-ffi/assets/SP1Verifier.txt index 0c009135e3..a18f01b819 100644 --- a/crates/recursion/gnark-ffi/assets/SP1Verifier.txt +++ b/crates/recursion/gnark-ffi/assets/SP1Verifier.txt @@ -2,12 +2,12 @@ pragma solidity ^0.8.20; import {ISP1Verifier, ISP1VerifierWithHash} from "../ISP1Verifier.sol"; -import {PlonkVerifier} from "./PlonkVerifier.sol"; +import {{PROOF_SYSTEM}Verifier} from "./{PROOF_SYSTEM}Verifier.sol"; /// @title SP1 Verifier /// @author Succinct Labs /// @notice This contracts implements a solidity verifier for SP1. -contract SP1Verifier is PlonkVerifier, ISP1VerifierWithHash { +contract SP1Verifier is {PROOF_SYSTEM}Verifier, ISP1VerifierWithHash { /// @notice Thrown when the verifier selector from this proof does not match the one in this /// verifier. This indicates that this proof was sent to the wrong verifier. /// @param received The verifier selector from the first 4 bytes of the proof. @@ -58,4 +58,4 @@ contract SP1Verifier is PlonkVerifier, ISP1VerifierWithHash { revert InvalidProof(); } } -} +} \ No newline at end of file diff --git a/crates/recursion/gnark-ffi/go/.gitignore b/crates/recursion/gnark-ffi/go/.gitignore index b4a997102a..a485d2ce97 100644 --- a/crates/recursion/gnark-ffi/go/.gitignore +++ b/crates/recursion/gnark-ffi/go/.gitignore @@ -1,5 +1,5 @@ constraints.json -witness.json +*witness.json lib/libbabybear.a build/ main \ No newline at end of file diff --git a/crates/recursion/gnark-ffi/go/main.go b/crates/recursion/gnark-ffi/go/main.go index ed782400f2..2d7334b06b 100644 --- a/crates/recursion/gnark-ffi/go/main.go +++ b/crates/recursion/gnark-ffi/go/main.go @@ -8,6 +8,12 @@ typedef struct { char *EncodedProof; char *RawProof; } C_PlonkBn254Proof; + +typedef struct { + char *PublicInputs[2]; + char *EncodedProof; + char *RawProof; +} C_Groth16Bn254Proof; */ import "C" import ( @@ -35,7 +41,7 @@ func ProvePlonkBn254(dataDir *C.char, witnessPath *C.char) *C.C_PlonkBn254Proof dataDirString := C.GoString(dataDir) witnessPathString := C.GoString(witnessPath) - sp1PlonkBn254Proof := sp1.Prove(dataDirString, witnessPathString) + sp1PlonkBn254Proof := sp1.ProvePlonk(dataDirString, witnessPathString) ms := C.malloc(C.sizeof_C_PlonkBn254Proof) if ms == nil { @@ -55,7 +61,7 @@ func BuildPlonkBn254(dataDir *C.char) { // Sanity check the required arguments have been provided. dataDirString := C.GoString(dataDir) - sp1.Build(dataDirString) + sp1.BuildPlonk(dataDirString) } //export VerifyPlonkBn254 @@ -65,7 +71,7 @@ func VerifyPlonkBn254(dataDir *C.char, proof *C.char, vkeyHash *C.char, commited vkeyHashString := C.GoString(vkeyHash) commitedValuesDigestString := C.GoString(commitedValuesDigest) - err := sp1.Verify(dataDirString, proofString, vkeyHashString, commitedValuesDigestString) + err := sp1.VerifyPlonk(dataDirString, proofString, vkeyHashString, commitedValuesDigestString) if err != nil { return C.CString(err.Error()) } @@ -90,11 +96,69 @@ func TestPlonkBn254(witnessPath *C.char, constraintsJson *C.char) *C.char { return nil } +//export ProveGroth16Bn254 +func ProveGroth16Bn254(dataDir *C.char, witnessPath *C.char) *C.C_Groth16Bn254Proof { + dataDirString := C.GoString(dataDir) + witnessPathString := C.GoString(witnessPath) + + sp1Groth16Bn254Proof := sp1.ProveGroth16(dataDirString, witnessPathString) + + ms := C.malloc(C.sizeof_C_Groth16Bn254Proof) + if ms == nil { + return nil + } + + structPtr := (*C.C_Groth16Bn254Proof)(ms) + structPtr.PublicInputs[0] = C.CString(sp1Groth16Bn254Proof.PublicInputs[0]) + structPtr.PublicInputs[1] = C.CString(sp1Groth16Bn254Proof.PublicInputs[1]) + structPtr.EncodedProof = C.CString(sp1Groth16Bn254Proof.EncodedProof) + structPtr.RawProof = C.CString(sp1Groth16Bn254Proof.RawProof) + return structPtr +} + +//export BuildGroth16Bn254 +func BuildGroth16Bn254(dataDir *C.char) { + // Sanity check the required arguments have been provided. + dataDirString := C.GoString(dataDir) + + sp1.BuildGroth16(dataDirString) +} + +//export VerifyGroth16Bn254 +func VerifyGroth16Bn254(dataDir *C.char, proof *C.char, vkeyHash *C.char, committedValuesDigest *C.char) *C.char { + dataDirString := C.GoString(dataDir) + proofString := C.GoString(proof) + vkeyHashString := C.GoString(vkeyHash) + committedValuesDigestString := C.GoString(committedValuesDigest) + + err := sp1.VerifyGroth16(dataDirString, proofString, vkeyHashString, committedValuesDigestString) + if err != nil { + return C.CString(err.Error()) + } + return nil +} + +//export TestGroth16Bn254 +func TestGroth16Bn254(witnessJson *C.char, constraintsJson *C.char) *C.char { + // Because of the global env variables used here, we need to lock this function + testMutex.Lock() + witnessPathString := C.GoString(witnessJson) + constraintsJsonString := C.GoString(constraintsJson) + os.Setenv("WITNESS_JSON", witnessPathString) + os.Setenv("CONSTRAINTS_JSON", constraintsJsonString) + err := TestMain() + testMutex.Unlock() + if err != nil { + return C.CString(err.Error()) + } + return nil +} + func TestMain() error { // Get the file name from an environment variable. fileName := os.Getenv("WITNESS_JSON") if fileName == "" { - fileName = "witness.json" + fileName = "plonk_witness.json" } // Read the file. diff --git a/crates/recursion/gnark-ffi/go/sp1/build.go b/crates/recursion/gnark-ffi/go/sp1/build.go index 5531b7ebb5..36bc079850 100644 --- a/crates/recursion/gnark-ffi/go/sp1/build.go +++ b/crates/recursion/gnark-ffi/go/sp1/build.go @@ -9,14 +9,16 @@ import ( "github.com/consensys/gnark-crypto/ecc" "github.com/consensys/gnark-crypto/kzg" + groth16 "github.com/consensys/gnark/backend/groth16" "github.com/consensys/gnark/backend/plonk" "github.com/consensys/gnark/frontend" + "github.com/consensys/gnark/frontend/cs/r1cs" "github.com/consensys/gnark/frontend/cs/scs" "github.com/consensys/gnark/test/unsafekzg" "github.com/succinctlabs/sp1-recursion-gnark/sp1/trusted_setup" ) -func Build(dataDir string) { +func BuildPlonk(dataDir string) { // Set the enviroment variable for the constraints file. // // TODO: There might be some non-determinism if a single process is running this command @@ -24,7 +26,7 @@ func Build(dataDir string) { os.Setenv("CONSTRAINTS_JSON", dataDir+"/"+constraintsJsonFile) // Read the file. - witnessInputPath := dataDir + "/witness.json" + witnessInputPath := dataDir + "/" + plonkWitnessPath data, err := os.ReadFile(witnessInputPath) if err != nil { panic(err) @@ -152,7 +154,7 @@ func Build(dataDir string) { os.MkdirAll(dataDir, 0755) // Write the solidity verifier. - solidityVerifierFile, err := os.Create(dataDir + "/" + verifierContractPath) + solidityVerifierFile, err := os.Create(dataDir + "/" + plonkVerifierContractPath) if err != nil { panic(err) } @@ -160,7 +162,7 @@ func Build(dataDir string) { defer solidityVerifierFile.Close() // Write the R1CS. - scsFile, err := os.Create(dataDir + "/" + circuitPath) + scsFile, err := os.Create(dataDir + "/" + plonkCircuitPath) if err != nil { panic(err) } @@ -171,7 +173,7 @@ func Build(dataDir string) { } // Write the verifier key. - vkFile, err := os.Create(dataDir + "/" + vkPath) + vkFile, err := os.Create(dataDir + "/" + plonkVkPath) if err != nil { panic(err) } @@ -182,7 +184,106 @@ func Build(dataDir string) { } // Write the proving key. - pkFile, err := os.Create(dataDir + "/" + pkPath) + pkFile, err := os.Create(dataDir + "/" + plonkPkPath) + if err != nil { + panic(err) + } + defer pkFile.Close() + _, err = pk.WriteTo(pkFile) + if err != nil { + panic(err) + } +} + +func BuildGroth16(dataDir string) { + // Set the environment variable for the constraints file. + os.Setenv("CONSTRAINTS_JSON", dataDir+"/"+constraintsJsonFile) + + // Read the file. + witnessInputPath := dataDir + "/" + groth16WitnessPath + data, err := os.ReadFile(witnessInputPath) + if err != nil { + panic(err) + } + + // Deserialize the JSON data into a slice of Instruction structs + var witnessInput WitnessInput + err = json.Unmarshal(data, &witnessInput) + if err != nil { + panic(err) + } + + // Initialize the circuit. + circuit := NewCircuit(witnessInput) + + // Compile the circuit. + r1cs, err := frontend.Compile(ecc.BN254.ScalarField(), r1cs.NewBuilder, &circuit) + if err != nil { + panic(err) + } + + // Generate the proving and verifying key. + pk, vk, err := groth16.Setup(r1cs) + if err != nil { + panic(err) + } + + // Generate proof. + assignment := NewCircuit(witnessInput) + witness, err := frontend.NewWitness(&assignment, ecc.BN254.ScalarField()) + if err != nil { + panic(err) + } + proof, err := groth16.Prove(r1cs, pk, witness) + if err != nil { + panic(err) + } + + // Verify proof. + publicWitness, err := witness.Public() + if err != nil { + panic(err) + } + err = groth16.Verify(proof, vk, publicWitness) + if err != nil { + panic(err) + } + + // Create the build directory. + os.MkdirAll(dataDir, 0755) + + // Write the solidity verifier. + solidityVerifierFile, err := os.Create(dataDir + "/" + groth16VerifierContractPath) + if err != nil { + panic(err) + } + vk.ExportSolidity(solidityVerifierFile) + defer solidityVerifierFile.Close() + + // Write the R1CS. + r1csFile, err := os.Create(dataDir + "/" + groth16CircuitPath) + if err != nil { + panic(err) + } + defer r1csFile.Close() + _, err = r1cs.WriteTo(r1csFile) + if err != nil { + panic(err) + } + + // Write the verifier key. + vkFile, err := os.Create(dataDir + "/" + groth16VkPath) + if err != nil { + panic(err) + } + defer vkFile.Close() + _, err = vk.WriteTo(vkFile) + if err != nil { + panic(err) + } + + // Write the proving key. + pkFile, err := os.Create(dataDir + "/" + groth16PkPath) if err != nil { panic(err) } diff --git a/crates/recursion/gnark-ffi/go/sp1/prove.go b/crates/recursion/gnark-ffi/go/sp1/prove.go index 7260f99ff7..595bbef6cc 100644 --- a/crates/recursion/gnark-ffi/go/sp1/prove.go +++ b/crates/recursion/gnark-ffi/go/sp1/prove.go @@ -6,11 +6,12 @@ import ( "os" "github.com/consensys/gnark-crypto/ecc" + "github.com/consensys/gnark/backend/groth16" "github.com/consensys/gnark/backend/plonk" "github.com/consensys/gnark/frontend" ) -func Prove(dataDir string, witnessPath string) Proof { +func ProvePlonk(dataDir string, witnessPath string) Proof { // Sanity check the required arguments have been provided. if dataDir == "" { panic("dataDirStr is required") @@ -18,7 +19,7 @@ func Prove(dataDir string, witnessPath string) Proof { os.Setenv("CONSTRAINTS_JSON", dataDir+"/"+constraintsJsonFile) // Read the R1CS. - scsFile, err := os.Open(dataDir + "/" + circuitPath) + scsFile, err := os.Open(dataDir + "/" + plonkCircuitPath) if err != nil { panic(err) } @@ -27,7 +28,7 @@ func Prove(dataDir string, witnessPath string) Proof { defer scsFile.Close() // Read the proving key. - pkFile, err := os.Open(dataDir + "/" + pkPath) + pkFile, err := os.Open(dataDir + "/" + plonkPkPath) if err != nil { panic(err) } @@ -37,7 +38,7 @@ func Prove(dataDir string, witnessPath string) Proof { defer pkFile.Close() // Read the verifier key. - vkFile, err := os.Open(dataDir + "/" + vkPath) + vkFile, err := os.Open(dataDir + "/" + plonkVkPath) if err != nil { panic(err) } @@ -83,3 +84,77 @@ func Prove(dataDir string, witnessPath string) Proof { return NewSP1PlonkBn254Proof(&proof, witnessInput) } + +func ProveGroth16(dataDir string, witnessPath string) Proof { + // Sanity check the required arguments have been provided. + if dataDir == "" { + panic("dataDirStr is required") + } + os.Setenv("CONSTRAINTS_JSON", dataDir+"/"+constraintsJsonFile) + + // Read the R1CS. + r1csFile, err := os.Open(dataDir + "/" + groth16CircuitPath) + if err != nil { + panic(err) + } + r1cs := groth16.NewCS(ecc.BN254) + r1cs.ReadFrom(r1csFile) + defer r1csFile.Close() + + // Read the proving key. + pkFile, err := os.Open(dataDir + "/" + groth16PkPath) + if err != nil { + panic(err) + } + pk := groth16.NewProvingKey(ecc.BN254) + bufReader := bufio.NewReaderSize(pkFile, 1024*1024) + pk.UnsafeReadFrom(bufReader) + defer pkFile.Close() + + // Read the verifier key. + vkFile, err := os.Open(dataDir + "/" + groth16VkPath) + if err != nil { + panic(err) + } + vk := groth16.NewVerifyingKey(ecc.BN254) + vk.ReadFrom(vkFile) + defer vkFile.Close() + + // Read the file. + data, err := os.ReadFile(witnessPath) + if err != nil { + panic(err) + } + + // Deserialize the JSON data into a slice of Instruction structs + var witnessInput WitnessInput + err = json.Unmarshal(data, &witnessInput) + if err != nil { + panic(err) + } + + // Generate the witness. + assignment := NewCircuit(witnessInput) + witness, err := frontend.NewWitness(&assignment, ecc.BN254.ScalarField()) + if err != nil { + panic(err) + } + publicWitness, err := witness.Public() + if err != nil { + panic(err) + } + + // Generate the proof. + proof, err := groth16.Prove(r1cs, pk, witness) + if err != nil { + panic(err) + } + + // Verify proof. + err = groth16.Verify(proof, vk, publicWitness) + if err != nil { + panic(err) + } + + return NewSP1Groth16Proof(&proof, witnessInput) +} diff --git a/crates/recursion/gnark-ffi/go/sp1/sp1.go b/crates/recursion/gnark-ffi/go/sp1/sp1.go index 5760a43d90..eebef363b1 100644 --- a/crates/recursion/gnark-ffi/go/sp1/sp1.go +++ b/crates/recursion/gnark-ffi/go/sp1/sp1.go @@ -14,10 +14,16 @@ import ( var srsFile string = "srs.bin" var srsLagrangeFile string = "srs_lagrange.bin" var constraintsJsonFile string = "constraints.json" -var verifierContractPath string = "PlonkVerifier.sol" -var circuitPath string = "circuit.bin" -var vkPath string = "vk.bin" -var pkPath string = "pk.bin" +var plonkVerifierContractPath string = "PlonkVerifier.sol" +var groth16VerifierContractPath string = "Groth16Verifier.sol" +var plonkCircuitPath string = "plonk_circuit.bin" +var groth16CircuitPath string = "groth16_circuit.bin" +var plonkVkPath string = "plonk_vk.bin" +var groth16VkPath string = "groth16_vk.bin" +var plonkPkPath string = "plonk_pk.bin" +var groth16PkPath string = "groth16_pk.bin" +var plonkWitnessPath string = "plonk_witness.json" +var groth16WitnessPath string = "groth16_witness.json" type Circuit struct { VkeyHash frontend.Variable `gnark:",public"` diff --git a/crates/recursion/gnark-ffi/go/sp1/utils.go b/crates/recursion/gnark-ffi/go/sp1/utils.go index f4711d9c61..6456c90b2a 100644 --- a/crates/recursion/gnark-ffi/go/sp1/utils.go +++ b/crates/recursion/gnark-ffi/go/sp1/utils.go @@ -4,6 +4,8 @@ import ( "bytes" "encoding/hex" + groth16 "github.com/consensys/gnark/backend/groth16" + groth16_bn254 "github.com/consensys/gnark/backend/groth16/bn254" plonk "github.com/consensys/gnark/backend/plonk" plonk_bn254 "github.com/consensys/gnark/backend/plonk/bn254" "github.com/consensys/gnark/frontend" @@ -31,6 +33,27 @@ func NewSP1PlonkBn254Proof(proof *plonk.Proof, witnessInput WitnessInput) Proof } } +func NewSP1Groth16Proof(proof *groth16.Proof, witnessInput WitnessInput) Proof { + var buf bytes.Buffer + (*proof).WriteRawTo(&buf) + proofBytes := buf.Bytes() + + var publicInputs [2]string + publicInputs[0] = witnessInput.VkeyHash + publicInputs[1] = witnessInput.CommitedValuesDigest + + // Cast groth16 proof into groth16_bn254 proof so we can call MarshalSolidity. + p := (*proof).(*groth16_bn254.Proof) + + encodedProof := p.MarshalSolidity() + + return Proof{ + PublicInputs: publicInputs, + EncodedProof: hex.EncodeToString(encodedProof), + RawProof: hex.EncodeToString(proofBytes), + } +} + func NewCircuit(witnessInput WitnessInput) Circuit { vars := make([]frontend.Variable, len(witnessInput.Vars)) felts := make([]babybear.Variable, len(witnessInput.Felts)) diff --git a/crates/recursion/gnark-ffi/go/sp1/verify.go b/crates/recursion/gnark-ffi/go/sp1/verify.go index 27c459c991..273e854d11 100644 --- a/crates/recursion/gnark-ffi/go/sp1/verify.go +++ b/crates/recursion/gnark-ffi/go/sp1/verify.go @@ -6,12 +6,13 @@ import ( "os" "github.com/consensys/gnark-crypto/ecc" + "github.com/consensys/gnark/backend/groth16" "github.com/consensys/gnark/backend/plonk" "github.com/consensys/gnark/frontend" "github.com/succinctlabs/sp1-recursion-gnark/sp1/babybear" ) -func Verify(verifyCmdDataDir string, verifyCmdProof string, verifyCmdVkeyHash string, verifyCmdCommitedValuesDigest string) error { +func VerifyPlonk(verifyCmdDataDir string, verifyCmdProof string, verifyCmdVkeyHash string, verifyCmdCommitedValuesDigest string) error { // Sanity check the required arguments have been provided. if verifyCmdDataDir == "" { panic("--data is required") @@ -28,7 +29,7 @@ func Verify(verifyCmdDataDir string, verifyCmdProof string, verifyCmdVkeyHash st } // Read the verifier key. - vkFile, err := os.Open(verifyCmdDataDir + "/" + vkPath) + vkFile, err := os.Open(verifyCmdDataDir + "/" + plonkVkPath) if err != nil { panic(err) } @@ -56,3 +57,49 @@ func Verify(verifyCmdDataDir string, verifyCmdProof string, verifyCmdVkeyHash st err = plonk.Verify(proof, vk, publicWitness) return err } + +func VerifyGroth16(verifyCmdDataDir string, verifyCmdProof string, verifyCmdVkeyHash string, verifyCmdCommitedValuesDigest string) error { + // Sanity check the required arguments have been provided. + if verifyCmdDataDir == "" { + panic("--data is required") + } + + // Decode the proof. + proofDecodedBytes, err := hex.DecodeString(verifyCmdProof) + if err != nil { + panic(err) + } + proof := groth16.NewProof(ecc.BN254) + if _, err := proof.ReadFrom(bytes.NewReader(proofDecodedBytes)); err != nil { + panic(err) + } + + // Read the verifier key. + vkFile, err := os.Open(verifyCmdDataDir + "/" + groth16VkPath) + if err != nil { + panic(err) + } + vk := groth16.NewVerifyingKey(ecc.BN254) + vk.ReadFrom(vkFile) + + // Compute the public witness. + circuit := Circuit{ + Vars: []frontend.Variable{}, + Felts: []babybear.Variable{}, + Exts: []babybear.ExtensionVariable{}, + VkeyHash: verifyCmdVkeyHash, + CommitedValuesDigest: verifyCmdCommitedValuesDigest, + } + witness, err := frontend.NewWitness(&circuit, ecc.BN254.ScalarField()) + if err != nil { + panic(err) + } + publicWitness, err := witness.Public() + if err != nil { + panic(err) + } + + // Verify proof. + err = groth16.Verify(proof, vk, publicWitness) + return err +} diff --git a/crates/recursion/gnark-ffi/src/ffi/docker.rs b/crates/recursion/gnark-ffi/src/ffi/docker.rs index f7c5e243dd..9941afb7b4 100644 --- a/crates/recursion/gnark-ffi/src/ffi/docker.rs +++ b/crates/recursion/gnark-ffi/src/ffi/docker.rs @@ -1,8 +1,23 @@ +use crate::{Groth16Bn254Proof, PlonkBn254Proof}; +use anyhow::{anyhow, Result}; use sp1_core_machine::SP1_CIRCUIT_VERSION; - -use crate::PlonkBn254Proof; use std::{io::Write, process::Command}; +/// Represents the proof system being used +enum ProofSystem { + Plonk, + Groth16, +} + +impl ProofSystem { + fn as_str(&self) -> &'static str { + match self { + ProofSystem::Plonk => "plonk", + ProofSystem::Groth16 => "groth16", + } + } +} + /// Checks that docker is installed and running. fn check_docker() -> bool { let output = Command::new("docker").arg("info").output(); @@ -22,7 +37,7 @@ fn get_docker_image() -> String { } /// Calls `docker run` with the given arguments and bind mounts. -fn call_docker(args: &[&str], mounts: &[(&str, &str)]) -> anyhow::Result<()> { +fn call_docker(args: &[&str], mounts: &[(&str, &str)]) -> Result<()> { log::info!("Running {} in docker", args[0]); let mut cmd = Command::new("docker"); cmd.args(["run", "--rm"]); @@ -33,41 +48,63 @@ fn call_docker(args: &[&str], mounts: &[(&str, &str)]) -> anyhow::Result<()> { cmd.args(args); if !cmd.status()?.success() { log::error!("Failed to run `docker run`: {:?}", cmd); - return Err(anyhow::anyhow!("docker command failed")); + return Err(anyhow!("docker command failed")); } Ok(()) } -pub fn prove_plonk_bn254(data_dir: &str, witness_path: &str) -> PlonkBn254Proof { - let output_file = tempfile::NamedTempFile::new().unwrap(); +fn prove(system: ProofSystem, data_dir: &str, witness_path: &str) -> Result> { + let output_file = tempfile::NamedTempFile::new()?; let mounts = [ (data_dir, "/circuit"), (witness_path, "/witness"), (output_file.path().to_str().unwrap(), "/output"), ]; assert_docker(); - call_docker(&["prove-plonk", "/circuit", "/witness", "/output"], &mounts) - .expect("failed to prove with docker"); - bincode::deserialize_from(&output_file).expect("failed to deserialize result") + call_docker( + &["prove", "--system", system.as_str(), "/circuit", "/witness", "/output"], + &mounts, + )?; + Ok(std::fs::read(output_file.path())?) } -pub fn build_plonk_bn254(data_dir: &str) { +pub fn prove_plonk_bn254(data_dir: &str, witness_path: &str) -> PlonkBn254Proof { + let result = + prove(ProofSystem::Plonk, data_dir, witness_path).expect("failed to prove with docker"); + bincode::deserialize(&result).expect("failed to deserialize result") +} + +pub fn prove_groth16_bn254(data_dir: &str, witness_path: &str) -> Groth16Bn254Proof { + let result = + prove(ProofSystem::Groth16, data_dir, witness_path).expect("failed to prove with docker"); + bincode::deserialize(&result).expect("failed to deserialize result") +} + +fn build(system: ProofSystem, data_dir: &str) -> Result<()> { let circuit_dir = if data_dir.ends_with("dev") { "/circuit_dev" } else { "/circuit" }; let mounts = [(data_dir, circuit_dir)]; assert_docker(); - call_docker(&["build-plonk", circuit_dir], &mounts).expect("failed to build with docker"); + call_docker(&["build", "--system", system.as_str(), circuit_dir], &mounts) } -pub fn verify_plonk_bn254( +pub fn build_plonk_bn254(data_dir: &str) { + build(ProofSystem::Plonk, data_dir).expect("failed to build with docker"); +} + +pub fn build_groth16_bn254(data_dir: &str) { + build(ProofSystem::Groth16, data_dir).expect("failed to build with docker"); +} + +fn verify( + system: ProofSystem, data_dir: &str, proof: &str, vkey_hash: &str, committed_values_digest: &str, -) -> Result<(), String> { - // Write proof string to a file since it can be large. - let mut proof_file = tempfile::NamedTempFile::new().unwrap(); - proof_file.write_all(proof.as_bytes()).unwrap(); - let output_file = tempfile::NamedTempFile::new().unwrap(); +) -> Result<()> { + let mut proof_file = tempfile::NamedTempFile::new()?; + proof_file.write_all(proof.as_bytes())?; + let output_file = tempfile::NamedTempFile::new()?; let mounts = [ (data_dir, "/circuit"), (proof_file.path().to_str().unwrap(), "/proof"), @@ -75,23 +112,56 @@ pub fn verify_plonk_bn254( ]; assert_docker(); call_docker( - &["verify-plonk", "/circuit", "/proof", vkey_hash, committed_values_digest, "/output"], + &[ + "verify", + "--system", + system.as_str(), + "/circuit", + "/proof", + vkey_hash, + committed_values_digest, + "/output", + ], &mounts, - ) - .expect("failed to verify with docker"); - let result = std::fs::read_to_string(output_file.path()).unwrap(); + )?; + let result = std::fs::read_to_string(output_file.path())?; if result == "OK" { Ok(()) } else { - Err(result) + Err(anyhow!(result)) } } -pub fn test_plonk_bn254(witness_json: &str, constraints_json: &str) { +pub fn verify_plonk_bn254( + data_dir: &str, + proof: &str, + vkey_hash: &str, + committed_values_digest: &str, +) -> Result<()> { + verify(ProofSystem::Plonk, data_dir, proof, vkey_hash, committed_values_digest) +} + +pub fn verify_groth16_bn254( + data_dir: &str, + proof: &str, + vkey_hash: &str, + committed_values_digest: &str, +) -> Result<()> { + verify(ProofSystem::Groth16, data_dir, proof, vkey_hash, committed_values_digest) +} + +fn test(system: ProofSystem, witness_json: &str, constraints_json: &str) -> Result<()> { let mounts = [(constraints_json, "/constraints"), (witness_json, "/witness")]; assert_docker(); - call_docker(&["test-plonk", "/constraints", "/witness"], &mounts) - .expect("failed to test with docker"); + call_docker(&["test", "--system", system.as_str(), "/constraints", "/witness"], &mounts) +} + +pub fn test_plonk_bn254(witness_json: &str, constraints_json: &str) { + test(ProofSystem::Plonk, witness_json, constraints_json).expect("failed to test with docker"); +} + +pub fn test_groth16_bn254(witness_json: &str, constraints_json: &str) { + test(ProofSystem::Groth16, witness_json, constraints_json).expect("failed to test with docker"); } pub fn test_babybear_poseidon2() { diff --git a/crates/recursion/gnark-ffi/src/ffi/native.rs b/crates/recursion/gnark-ffi/src/ffi/native.rs index 9a197eb1bc..d3faba0fe8 100644 --- a/crates/recursion/gnark-ffi/src/ffi/native.rs +++ b/crates/recursion/gnark-ffi/src/ffi/native.rs @@ -5,7 +5,7 @@ //! Although we cast to *mut c_char because the Go signatures can't be immutable, the Go functions //! should not modify the strings. -use crate::PlonkBn254Proof; +use crate::{Groth16Bn254Proof, PlonkBn254Proof}; use cfg_if::cfg_if; use sp1_core_machine::SP1_CIRCUIT_VERSION; use std::ffi::{c_char, CString}; @@ -16,31 +16,83 @@ mod bind { } use bind::*; -pub fn prove_plonk_bn254(data_dir: &str, witness_path: &str) -> PlonkBn254Proof { - let data_dir = CString::new(data_dir).expect("CString::new failed"); - let witness_path = CString::new(witness_path).expect("CString::new failed"); +enum ProofSystem { + Plonk, + Groth16, +} - let proof = unsafe { - let proof = bind::ProvePlonkBn254( - data_dir.as_ptr() as *mut c_char, - witness_path.as_ptr() as *mut c_char, - ); - // Safety: The pointer is returned from the go code and is guaranteed to be valid. - *proof - }; +enum ProofResult { + Plonk(C_PlonkBn254Proof), + Groth16(C_Groth16Bn254Proof), +} + +impl ProofSystem { + fn build_fn(&self) -> unsafe extern "C" fn(*mut c_char) { + match self { + ProofSystem::Plonk => bind::BuildPlonkBn254, + ProofSystem::Groth16 => bind::BuildGroth16Bn254, + } + } + + fn prove_fn(&self) -> ProveFunction { + match self { + ProofSystem::Plonk => ProveFunction::Plonk(bind::ProvePlonkBn254), + ProofSystem::Groth16 => ProveFunction::Groth16(bind::ProveGroth16Bn254), + } + } + + fn verify_fn( + &self, + ) -> unsafe extern "C" fn(*mut c_char, *mut c_char, *mut c_char, *mut c_char) -> *mut c_char + { + match self { + ProofSystem::Plonk => bind::VerifyPlonkBn254, + ProofSystem::Groth16 => bind::VerifyGroth16Bn254, + } + } - proof.into_rust() + fn test_fn(&self) -> unsafe extern "C" fn(*mut c_char, *mut c_char) -> *mut c_char { + match self { + ProofSystem::Plonk => bind::TestPlonkBn254, + ProofSystem::Groth16 => bind::TestGroth16Bn254, + } + } } -pub fn build_plonk_bn254(data_dir: &str) { +enum ProveFunction { + Plonk(unsafe extern "C" fn(*mut c_char, *mut c_char) -> *mut C_PlonkBn254Proof), + Groth16(unsafe extern "C" fn(*mut c_char, *mut c_char) -> *mut C_Groth16Bn254Proof), +} + +fn build(system: ProofSystem, data_dir: &str) { + let data_dir = CString::new(data_dir).expect("CString::new failed"); + unsafe { + (system.build_fn())(data_dir.as_ptr() as *mut c_char); + } +} + +fn prove(system: ProofSystem, data_dir: &str, witness_path: &str) -> ProofResult { let data_dir = CString::new(data_dir).expect("CString::new failed"); + let witness_path = CString::new(witness_path).expect("CString::new failed"); unsafe { - bind::BuildPlonkBn254(data_dir.as_ptr() as *mut c_char); + match system.prove_fn() { + ProveFunction::Plonk(func) => { + let proof = + func(data_dir.as_ptr() as *mut c_char, witness_path.as_ptr() as *mut c_char); + ProofResult::Plonk(*proof) + } + ProveFunction::Groth16(func) => { + let proof = + func(data_dir.as_ptr() as *mut c_char, witness_path.as_ptr() as *mut c_char); + ProofResult::Groth16(*proof) + } + } } } -pub fn verify_plonk_bn254( +fn verify( + system: ProofSystem, data_dir: &str, proof: &str, vkey_hash: &str, @@ -53,7 +105,7 @@ pub fn verify_plonk_bn254( CString::new(committed_values_digest).expect("CString::new failed"); let err_ptr = unsafe { - bind::VerifyPlonkBn254( + (system.verify_fn())( data_dir.as_ptr() as *mut c_char, proof.as_ptr() as *mut c_char, vkey_hash.as_ptr() as *mut c_char, @@ -69,29 +121,79 @@ pub fn verify_plonk_bn254( } } -pub fn test_plonk_bn254(witness_json: &str, constraints_json: &str) { +fn test(system: ProofSystem, witness_json: &str, constraints_json: &str) { unsafe { let witness_json = CString::new(witness_json).expect("CString::new failed"); - let build_dir = CString::new(constraints_json).expect("CString::new failed"); - let err_ptr = bind::TestPlonkBn254( + let constraints_json = CString::new(constraints_json).expect("CString::new failed"); + let err_ptr = (system.test_fn())( witness_json.as_ptr() as *mut c_char, - build_dir.as_ptr() as *mut c_char, + constraints_json.as_ptr() as *mut c_char, ); if !err_ptr.is_null() { // Safety: The error message is returned from the go code and is guaranteed to be valid. let err = CString::from_raw(err_ptr); - panic!("TestPlonkBn254 failed: {}", err.into_string().unwrap()); + panic!("Test failed: {}", err.into_string().unwrap()); } } } +// Public API functions + +pub fn build_plonk_bn254(data_dir: &str) { + build(ProofSystem::Plonk, data_dir) +} + +pub fn prove_plonk_bn254(data_dir: &str, witness_path: &str) -> PlonkBn254Proof { + match prove(ProofSystem::Plonk, data_dir, witness_path) { + ProofResult::Plonk(proof) => proof.into(), + _ => unreachable!(), + } +} + +pub fn verify_plonk_bn254( + data_dir: &str, + proof: &str, + vkey_hash: &str, + committed_values_digest: &str, +) -> Result<(), String> { + verify(ProofSystem::Plonk, data_dir, proof, vkey_hash, committed_values_digest) +} + +pub fn test_plonk_bn254(witness_json: &str, constraints_json: &str) { + test(ProofSystem::Plonk, witness_json, constraints_json) +} + +pub fn build_groth16_bn254(data_dir: &str) { + build(ProofSystem::Groth16, data_dir) +} + +pub fn prove_groth16_bn254(data_dir: &str, witness_path: &str) -> Groth16Bn254Proof { + match prove(ProofSystem::Groth16, data_dir, witness_path) { + ProofResult::Groth16(proof) => proof.into(), + _ => unreachable!(), + } +} + +pub fn verify_groth16_bn254( + data_dir: &str, + proof: &str, + vkey_hash: &str, + committed_values_digest: &str, +) -> Result<(), String> { + verify(ProofSystem::Groth16, data_dir, proof, vkey_hash, committed_values_digest) +} + +pub fn test_groth16_bn254(witness_json: &str, constraints_json: &str) { + test(ProofSystem::Groth16, witness_json, constraints_json) +} + pub fn test_babybear_poseidon2() { unsafe { let err_ptr = bind::TestPoseidonBabyBear2(); if !err_ptr.is_null() { // Safety: The error message is returned from the go code and is guaranteed to be valid. let err = CString::from_raw(err_ptr); - panic!("TestPlonkBn254 failed: {}", err.into_string().unwrap()); + panic!("TestPoseidonBabyBear2 failed: {}", err.into_string().unwrap()); } } } @@ -102,31 +204,43 @@ pub fn test_babybear_poseidon2() { /// This function frees the string memory, so the caller must ensure that the pointer is not used /// after this function is called. unsafe fn c_char_ptr_to_string(input: *mut c_char) -> String { - unsafe { - CString::from_raw(input) // Converts a pointer that C uses into a CString - .into_string() - .expect("CString::into_string failed") - } + CString::from_raw(input).into_string().expect("CString::into_string failed") } -impl C_PlonkBn254Proof { - /// Converts a C PlonkBn254Proof into a Rust PlonkBn254Proof, freeing the C strings. - fn into_rust(self) -> PlonkBn254Proof { +impl From for PlonkBn254Proof { + fn from(c_proof: C_PlonkBn254Proof) -> Self { // Safety: The raw pointers are not used anymore after converted into Rust strings. unsafe { PlonkBn254Proof { public_inputs: [ - c_char_ptr_to_string(self.PublicInputs[0]), - c_char_ptr_to_string(self.PublicInputs[1]), + c_char_ptr_to_string(c_proof.PublicInputs[0]), + c_char_ptr_to_string(c_proof.PublicInputs[1]), ], - encoded_proof: c_char_ptr_to_string(self.EncodedProof), - raw_proof: c_char_ptr_to_string(self.RawProof), + encoded_proof: c_char_ptr_to_string(c_proof.EncodedProof), + raw_proof: c_char_ptr_to_string(c_proof.RawProof), plonk_vkey_hash: [0; 32], } } } } +impl From for Groth16Bn254Proof { + fn from(c_proof: C_Groth16Bn254Proof) -> Self { + // Safety: The raw pointers are not used anymore after converted into Rust strings. + unsafe { + Groth16Bn254Proof { + public_inputs: [ + c_char_ptr_to_string(c_proof.PublicInputs[0]), + c_char_ptr_to_string(c_proof.PublicInputs[1]), + ], + encoded_proof: c_char_ptr_to_string(c_proof.EncodedProof), + raw_proof: c_char_ptr_to_string(c_proof.RawProof), + groth16_vkey_hash: [0; 32], + } + } + } +} + #[cfg(test)] mod tests { use p3_baby_bear::BabyBear; diff --git a/crates/recursion/gnark-ffi/src/groth16_bn254.rs b/crates/recursion/gnark-ffi/src/groth16_bn254.rs new file mode 100644 index 0000000000..a93f29badf --- /dev/null +++ b/crates/recursion/gnark-ffi/src/groth16_bn254.rs @@ -0,0 +1,126 @@ +use std::{ + fs::File, + io::Write, + path::{Path, PathBuf}, +}; + +use crate::{ + ffi::{build_groth16_bn254, prove_groth16_bn254, test_groth16_bn254, verify_groth16_bn254}, + witness::GnarkWitness, + Groth16Bn254Proof, +}; + +use num_bigint::BigUint; +use sha2::{Digest, Sha256}; +use sp1_core_machine::SP1_CIRCUIT_VERSION; +use sp1_recursion_compiler::{ + constraints::Constraint, + ir::{Config, Witness}, +}; + +/// A prover that can generate proofs with the PLONK protocol using bindings to Gnark. +#[derive(Debug, Clone)] +pub struct Groth16Bn254Prover; + +/// A prover that can generate proofs with the Groth16 protocol using bindings to Gnark. +impl Groth16Bn254Prover { + /// Creates a new [Groth16Bn254Prover]. + pub fn new() -> Self { + Self + } + + pub fn get_vkey_hash(build_dir: &Path) -> [u8; 32] { + let vkey_path = build_dir.join("groth16_vk.bin"); + let vk_bin_bytes = std::fs::read(vkey_path).unwrap(); + Sha256::digest(vk_bin_bytes).into() + } + + /// Executes the prover in testing mode with a circuit definition and witness. + pub fn test(constraints: Vec, witness: Witness) { + let serialized = serde_json::to_string(&constraints).unwrap(); + + // Write constraints. + let mut constraints_file = tempfile::NamedTempFile::new().unwrap(); + constraints_file.write_all(serialized.as_bytes()).unwrap(); + + // Write witness. + let mut witness_file = tempfile::NamedTempFile::new().unwrap(); + let gnark_witness = GnarkWitness::new(witness); + let serialized = serde_json::to_string(&gnark_witness).unwrap(); + witness_file.write_all(serialized.as_bytes()).unwrap(); + + test_groth16_bn254( + witness_file.path().to_str().unwrap(), + constraints_file.path().to_str().unwrap(), + ) + } + + /// Builds the Groth16 circuit locally. + pub fn build(constraints: Vec, witness: Witness, build_dir: PathBuf) { + let serialized = serde_json::to_string(&constraints).unwrap(); + + // Write constraints. + let constraints_path = build_dir.join("constraints.json"); + let mut file = File::create(constraints_path).unwrap(); + file.write_all(serialized.as_bytes()).unwrap(); + + // Write witness. + let witness_path = build_dir.join("groth16_witness.json"); + let gnark_witness = GnarkWitness::new(witness); + let mut file = File::create(witness_path).unwrap(); + let serialized = serde_json::to_string(&gnark_witness).unwrap(); + file.write_all(serialized.as_bytes()).unwrap(); + + build_groth16_bn254(build_dir.to_str().unwrap()); + + // Write the corresponding asset files to the build dir. + let sp1_verifier_path = build_dir.join("Groth16SP1Verifier.sol"); + let vkey_hash = Self::get_vkey_hash(&build_dir); + let sp1_verifier_str = include_str!("../assets/SP1Verifier.txt") + .replace("{SP1_CIRCUIT_VERSION}", SP1_CIRCUIT_VERSION) + .replace("{VERIFIER_HASH}", format!("0x{}", hex::encode(vkey_hash)).as_str()) + .replace("{PROOF_SYSTEM}", "Groth16"); + let mut sp1_verifier_file = File::create(sp1_verifier_path).unwrap(); + sp1_verifier_file.write_all(sp1_verifier_str.as_bytes()).unwrap(); + } + + /// Generates a Groth16 proof given a witness. + pub fn prove(&self, witness: Witness, build_dir: PathBuf) -> Groth16Bn254Proof { + // Write witness. + let mut witness_file = tempfile::NamedTempFile::new().unwrap(); + let gnark_witness = GnarkWitness::new(witness); + let serialized = serde_json::to_string(&gnark_witness).unwrap(); + witness_file.write_all(serialized.as_bytes()).unwrap(); + + let mut proof = + prove_groth16_bn254(build_dir.to_str().unwrap(), witness_file.path().to_str().unwrap()); + proof.groth16_vkey_hash = Self::get_vkey_hash(&build_dir); + proof + } + + /// Verify a Groth16proof and verify that the supplied vkey_hash and committed_values_digest match. + pub fn verify( + &self, + proof: &Groth16Bn254Proof, + vkey_hash: &BigUint, + committed_values_digest: &BigUint, + build_dir: &Path, + ) { + if proof.groth16_vkey_hash != Self::get_vkey_hash(build_dir) { + panic!("Proof vkey hash does not match circuit vkey hash, it was generated with a different circuit."); + } + verify_groth16_bn254( + build_dir.to_str().unwrap(), + &proof.raw_proof, + &vkey_hash.to_string(), + &committed_values_digest.to_string(), + ) + .expect("failed to verify proof") + } +} + +impl Default for Groth16Bn254Prover { + fn default() -> Self { + Self::new() + } +} diff --git a/crates/recursion/gnark-ffi/src/lib.rs b/crates/recursion/gnark-ffi/src/lib.rs index 670a9b08fe..436739ba83 100644 --- a/crates/recursion/gnark-ffi/src/lib.rs +++ b/crates/recursion/gnark-ffi/src/lib.rs @@ -1,9 +1,12 @@ mod babybear; pub mod ffi; - +pub mod groth16_bn254; pub mod plonk_bn254; +pub mod proof; pub mod witness; +pub use groth16_bn254::*; pub use plonk_bn254::*; +pub use proof::*; pub use witness::*; diff --git a/crates/recursion/gnark-ffi/src/plonk_bn254.rs b/crates/recursion/gnark-ffi/src/plonk_bn254.rs index 0d61a533ea..1ebe0d2e13 100644 --- a/crates/recursion/gnark-ffi/src/plonk_bn254.rs +++ b/crates/recursion/gnark-ffi/src/plonk_bn254.rs @@ -7,10 +7,10 @@ use std::{ use crate::{ ffi::{build_plonk_bn254, prove_plonk_bn254, test_plonk_bn254, verify_plonk_bn254}, witness::GnarkWitness, + PlonkBn254Proof, }; use num_bigint::BigUint; -use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; use sp1_core_machine::SP1_CIRCUIT_VERSION; use sp1_recursion_compiler::{ @@ -22,15 +22,6 @@ use sp1_recursion_compiler::{ #[derive(Debug, Clone)] pub struct PlonkBn254Prover; -/// A zero-knowledge proof generated by the PLONK protocol with a Base64 encoded gnark PLONK proof. -#[derive(Debug, Clone, Serialize, Deserialize, Default)] -pub struct PlonkBn254Proof { - pub public_inputs: [String; 2], - pub encoded_proof: String, - pub raw_proof: String, - pub plonk_vkey_hash: [u8; 32], -} - impl PlonkBn254Prover { /// Creates a new [PlonkBn254Prover]. pub fn new() -> Self { @@ -38,7 +29,7 @@ impl PlonkBn254Prover { } pub fn get_vkey_hash(build_dir: &Path) -> [u8; 32] { - let vkey_path = build_dir.join("vk.bin"); + let vkey_path = build_dir.join("plonk_vk.bin"); let vk_bin_bytes = std::fs::read(vkey_path).unwrap(); Sha256::digest(vk_bin_bytes).into() } @@ -73,7 +64,7 @@ impl PlonkBn254Prover { file.write_all(serialized.as_bytes()).unwrap(); // Write witness. - let witness_path = build_dir.join("witness.json"); + let witness_path = build_dir.join("plonk_witness.json"); let gnark_witness = GnarkWitness::new(witness); let mut file = File::create(witness_path).unwrap(); let serialized = serde_json::to_string(&gnark_witness).unwrap(); @@ -82,11 +73,12 @@ impl PlonkBn254Prover { build_plonk_bn254(build_dir.to_str().unwrap()); // Write the corresponding asset files to the build dir. - let sp1_verifier_path = build_dir.join("SP1Verifier.sol"); + let sp1_verifier_path = build_dir.join("PlonkSP1Verifier.sol"); let vkey_hash = Self::get_vkey_hash(&build_dir); let sp1_verifier_str = include_str!("../assets/SP1Verifier.txt") .replace("{SP1_CIRCUIT_VERSION}", SP1_CIRCUIT_VERSION) - .replace("{VERIFIER_HASH}", format!("0x{}", hex::encode(vkey_hash)).as_str()); + .replace("{VERIFIER_HASH}", format!("0x{}", hex::encode(vkey_hash)).as_str()) + .replace("{PROOF_SYSTEM}", "Plonk"); let mut sp1_verifier_file = File::create(sp1_verifier_path).unwrap(); sp1_verifier_file.write_all(sp1_verifier_str.as_bytes()).unwrap(); } diff --git a/crates/recursion/gnark-ffi/src/proof.rs b/crates/recursion/gnark-ffi/src/proof.rs new file mode 100644 index 0000000000..769bc5e0d0 --- /dev/null +++ b/crates/recursion/gnark-ffi/src/proof.rs @@ -0,0 +1,25 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum ProofBn254 { + Plonk(PlonkBn254Proof), + Groth16(Groth16Bn254Proof), +} + +/// A zero-knowledge proof generated by the PLONK protocol with a Base64 encoded gnark PLONK proof. +#[derive(Debug, Clone, Serialize, Deserialize, Default)] +pub struct PlonkBn254Proof { + pub public_inputs: [String; 2], + pub encoded_proof: String, + pub raw_proof: String, + pub plonk_vkey_hash: [u8; 32], +} + +/// A zero-knowledge proof generated by the Groth16 protocol with a Base64 encoded gnark Groth16 proof. +#[derive(Debug, Clone, Serialize, Deserialize, Default)] +pub struct Groth16Bn254Proof { + pub public_inputs: [String; 2], + pub encoded_proof: String, + pub raw_proof: String, + pub groth16_vkey_hash: [u8; 32], +} diff --git a/crates/sdk/src/artifacts.rs b/crates/sdk/src/artifacts.rs index 97492f1e47..bea4e7a9af 100644 --- a/crates/sdk/src/artifacts.rs +++ b/crates/sdk/src/artifacts.rs @@ -6,7 +6,7 @@ use indicatif::{ProgressBar, ProgressStyle}; use reqwest::Client; pub use sp1_prover::build::build_plonk_bn254_artifacts_with_dummy; -use crate::install::try_install_plonk_bn254_artifacts; +use crate::install::try_install_circuit_artifacts; /// Exports the solidity verifier for PLONK proofs to the specified output directory. /// @@ -17,16 +17,45 @@ pub fn export_solidity_plonk_bn254_verifier(output_dir: impl Into) -> R let artifacts_dir = if sp1_prover::build::sp1_dev_mode() { sp1_prover::build::plonk_bn254_artifacts_dev_dir() } else { - try_install_plonk_bn254_artifacts() + try_install_circuit_artifacts() }; - let verifier_path = artifacts_dir.join("SP1Verifier.sol"); + let verifier_path = artifacts_dir.join("PlonkSP1Verifier.sol"); if !verifier_path.exists() { return Err(anyhow::anyhow!("verifier file not found at {:?}", verifier_path)); } std::fs::create_dir_all(&output_dir).context("Failed to create output directory.")?; - let output_path = output_dir.join("SP1Verifier.sol"); + let output_path = output_dir.join("PlonkSP1Verifier.sol"); + std::fs::copy(&verifier_path, &output_path).context("Failed to copy verifier file.")?; + tracing::info!( + "exported verifier from {} to {}", + verifier_path.display(), + output_path.display() + ); + + Ok(()) +} + +/// Exports the solidity verifier for Groth16 proofs to the specified output directory. +/// +/// WARNING: If you are on development mode, this function assumes that the Groth16 artifacts have +/// already been built. +pub fn export_solidity_groth16_bn254_verifier(output_dir: impl Into) -> Result<()> { + let output_dir: PathBuf = output_dir.into(); + let artifacts_dir = if sp1_prover::build::sp1_dev_mode() { + sp1_prover::build::groth16_bn254_artifacts_dev_dir() + } else { + try_install_circuit_artifacts() + }; + let verifier_path = artifacts_dir.join("Groth16SP1Verifier.sol"); + + if !verifier_path.exists() { + return Err(anyhow::anyhow!("verifier file not found at {:?}", verifier_path)); + } + + std::fs::create_dir_all(&output_dir).context("Failed to create output directory.")?; + let output_path = output_dir.join("Groth16SP1Verifier.sol"); std::fs::copy(&verifier_path, &output_path).context("Failed to copy verifier file.")?; tracing::info!( "exported verifier from {} to {}", @@ -71,8 +100,16 @@ pub async fn download_file( #[cfg(test)] mod tests { #[test] - fn test_verifier_export() { + fn test_plonk_verifier_export() { crate::artifacts::export_solidity_plonk_bn254_verifier(tempfile::tempdir().unwrap().path()) .expect("failed to export verifier"); } + + #[test] + fn test_groth16_verifier_export() { + crate::artifacts::export_solidity_groth16_bn254_verifier( + tempfile::tempdir().unwrap().path(), + ) + .expect("failed to export verifier"); + } } diff --git a/crates/sdk/src/install.rs b/crates/sdk/src/install.rs index f3a0c95f0f..36226ecf81 100644 --- a/crates/sdk/src/install.rs +++ b/crates/sdk/src/install.rs @@ -7,49 +7,44 @@ use tokio::{runtime, task::block_in_place}; use crate::SP1_CIRCUIT_VERSION; -/// The base URL for the S3 bucket containing the plonk bn254 artifacts. -pub const PLONK_BN254_ARTIFACTS_URL_BASE: &str = "https://sp1-circuits.s3-us-east-2.amazonaws.com"; - -/// Gets the directory where the PLONK artifacts are installed. -fn plonk_bn254_artifacts_dir() -> PathBuf { - dirs::home_dir() - .unwrap() - .join(".sp1") - .join("circuits") - .join("plonk_bn254") - .join(SP1_CIRCUIT_VERSION) +/// The base URL for the S3 bucket containing the ciruit artifacts. +pub const CIRCUIT_ARTIFACTS_URL_BASE: &str = "https://sp1-circuits.s3-us-east-2.amazonaws.com"; + +/// Gets the directory where the circuit artifacts are installed. +fn circuit_artifacts_dir() -> PathBuf { + dirs::home_dir().unwrap().join(".sp1").join("circuits").join(SP1_CIRCUIT_VERSION) } -/// Tries to install the PLONK artifacts if they are not already installed. -pub fn try_install_plonk_bn254_artifacts() -> PathBuf { - let build_dir = plonk_bn254_artifacts_dir(); +/// Tries to install the circuit artifacts if they are not already installed. +pub fn try_install_circuit_artifacts() -> PathBuf { + let build_dir = circuit_artifacts_dir(); if build_dir.exists() { println!( - "[sp1] plonk bn254 artifacts already seem to exist at {}. if you want to re-download them, delete the directory", + "[sp1] circuit artifacts already seem to exist at {}. if you want to re-download them, delete the directory", build_dir.display() ); } else { println!( - "[sp1] plonk bn254 artifacts for version {} do not exist at {}. downloading...", + "[sp1] circuit artifacts for version {} do not exist at {}. downloading...", SP1_CIRCUIT_VERSION, build_dir.display() ); - install_plonk_bn254_artifacts(build_dir.clone()); + install_circuit_artifacts(build_dir.clone()); } build_dir } -/// Install the latest plonk bn254 artifacts. +/// Install the latest circuit artifacts. /// -/// This function will download the latest plonk bn254 artifacts from the S3 bucket and extract them +/// This function will download the latest circuit artifacts from the S3 bucket and extract them /// to the directory specified by [plonk_bn254_artifacts_dir()]. -pub fn install_plonk_bn254_artifacts(build_dir: PathBuf) { +pub fn install_circuit_artifacts(build_dir: PathBuf) { // Create the build directory. std::fs::create_dir_all(&build_dir).expect("failed to create build directory"); // Download the artifacts. - let download_url = format!("{}/{}.tar.gz", PLONK_BN254_ARTIFACTS_URL_BASE, SP1_CIRCUIT_VERSION); + let download_url = format!("{}/{}.tar.gz", CIRCUIT_ARTIFACTS_URL_BASE, SP1_CIRCUIT_VERSION); let mut artifacts_tar_gz_file = tempfile::NamedTempFile::new().expect("failed to create tempfile"); let client = Client::builder().build().expect("failed to create reqwest client"); @@ -71,9 +66,8 @@ pub fn install_plonk_bn254_artifacts(build_dir: PathBuf) { println!("[sp1] downloaded {} to {:?}", download_url, build_dir.to_str().unwrap(),); } -/// The directory where the plonk bn254 artifacts will be stored based on -/// [PLONK_BN254_ARTIFACTS_VERSION] and [PLONK_BN254_ARTIFACTS_URL_BASE]. -pub fn install_plonk_bn254_artifacts_dir() -> PathBuf { +/// The directory where the circuit artifacts will be stored. +pub fn install_circuit_artifacts_dir() -> PathBuf { dirs::home_dir().unwrap().join(".sp1").join("circuits").join(SP1_CIRCUIT_VERSION) } diff --git a/crates/sdk/src/network/prover.rs b/crates/sdk/src/network/prover.rs index b512b48cf8..7fc04a09a0 100644 --- a/crates/sdk/src/network/prover.rs +++ b/crates/sdk/src/network/prover.rs @@ -182,6 +182,7 @@ impl From for ProofMode { SP1ProofKind::Core => Self::Core, SP1ProofKind::Compressed => Self::Compressed, SP1ProofKind::Plonk => Self::Plonk, + SP1ProofKind::Groth16 => Self::Groth16, } } } diff --git a/crates/sdk/src/proof.rs b/crates/sdk/src/proof.rs index ded024913b..390f9197a7 100644 --- a/crates/sdk/src/proof.rs +++ b/crates/sdk/src/proof.rs @@ -5,7 +5,7 @@ use serde::{Deserialize, Serialize}; use sp1_core_machine::io::{SP1PublicValues, SP1Stdin}; use strum_macros::{EnumDiscriminants, EnumTryAs}; -use sp1_prover::{CoreSC, InnerSC, PlonkBn254Proof}; +use sp1_prover::{CoreSC, Groth16Bn254Proof, InnerSC, PlonkBn254Proof}; use sp1_stark::{MachineVerificationError, ShardProof}; /// A proof generated with SP1 of a particular proof mode. @@ -17,6 +17,7 @@ pub enum SP1Proof { Core(Vec>), Compressed(ShardProof), Plonk(PlonkBn254Proof), + Groth16(Groth16Bn254Proof), } /// A proof generated with SP1, bundled together with stdin, public values, and the SP1 version. @@ -49,7 +50,7 @@ impl SP1ProofWithPublicValues { } } - /// For Plonk proofs, returns the proof in a byte encoding the onchain verifier accepts. + /// For Plonk or Groth16 proofs, returns the proof in a byte encoding the onchain verifier accepts. /// The bytes consist of the first four bytes of Plonk vkey hash followed by the encoded proof. pub fn bytes(&self) -> Vec { match &self.proof { @@ -61,7 +62,15 @@ impl SP1ProofWithPublicValues { ); bytes } - _ => unimplemented!("only Plonk proofs are verifiable onchain"), + SP1Proof::Groth16(groth16_proof) => { + let mut bytes = Vec::with_capacity(4 + groth16_proof.encoded_proof.len()); + bytes.extend_from_slice(&groth16_proof.groth16_vkey_hash[..4]); + bytes.extend_from_slice( + &hex::decode(&groth16_proof.encoded_proof).expect("Invalid Groth16 proof"), + ); + bytes + } + _ => unimplemented!("only Plonk and Groth16 proofs are verifiable onchain"), } } } diff --git a/crates/sdk/src/proto/network.rs b/crates/sdk/src/proto/network.rs index 5f36a2bd84..4c3dd84771 100644 --- a/crates/sdk/src/proto/network.rs +++ b/crates/sdk/src/proto/network.rs @@ -306,6 +306,8 @@ pub enum ProofMode { Compressed = 2, /// The proof mode for a PlonK proof. Plonk = 3, + /// The proof mode for a Groth16 proof. + Groth16 = 4, } impl ProofMode { /// String value of the enum field names used in the ProtoBuf definition. @@ -318,6 +320,7 @@ impl ProofMode { ProofMode::Core => "PROOF_MODE_CORE", ProofMode::Compressed => "PROOF_MODE_COMPRESSED", ProofMode::Plonk => "PROOF_MODE_PLONK", + ProofMode::Groth16 => "PROOF_MODE_GROTH16", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -327,6 +330,7 @@ impl ProofMode { "PROOF_MODE_CORE" => Some(Self::Core), "PROOF_MODE_COMPRESSED" => Some(Self::Compressed), "PROOF_MODE_PLONK" => Some(Self::Plonk), + "PROOF_MODE_GROTH16" => Some(Self::Groth16), _ => None, } } @@ -706,27 +710,21 @@ impl NetworkServiceClient for twirp::client::Client { &self, req: GetProofStatusRequest, ) -> Result { - let url = self - .base_url - .join("network.NetworkService/GetProofStatus")?; + let url = self.base_url.join("network.NetworkService/GetProofStatus")?; self.request(url, req).await } async fn get_proof_requests( &self, req: GetProofRequestsRequest, ) -> Result { - let url = self - .base_url - .join("network.NetworkService/GetProofRequests")?; + let url = self.base_url.join("network.NetworkService/GetProofRequests")?; self.request(url, req).await } async fn get_relay_status( &self, req: GetRelayStatusRequest, ) -> Result { - let url = self - .base_url - .join("network.NetworkService/GetRelayStatus")?; + let url = self.base_url.join("network.NetworkService/GetRelayStatus")?; self.request(url, req).await } } diff --git a/crates/sdk/src/provers/local.rs b/crates/sdk/src/provers/local.rs index f891c565b1..c3e16d586a 100644 --- a/crates/sdk/src/provers/local.rs +++ b/crates/sdk/src/provers/local.rs @@ -5,7 +5,7 @@ use sp1_prover::{components::SP1ProverComponents, SP1Prover}; use sysinfo::System; use crate::{ - install::try_install_plonk_bn254_artifacts, provers::ProofOpts, Prover, SP1Proof, SP1ProofKind, + install::try_install_circuit_artifacts, provers::ProofOpts, Prover, SP1Proof, SP1ProofKind, SP1ProofWithPublicValues, SP1ProvingKey, SP1VerifyingKey, }; @@ -81,22 +81,40 @@ impl Prover for LocalProver { let compress_proof = self.prover.shrink(reduce_proof, opts.sp1_prover_opts)?; let outer_proof = self.prover.wrap_bn254(compress_proof, opts.sp1_prover_opts)?; - let plonk_bn254_aritfacts = if sp1_prover::build::sp1_dev_mode() { - sp1_prover::build::try_build_plonk_bn254_artifacts_dev( - self.prover.wrap_vk(), - &outer_proof.proof, - ) - } else { - try_install_plonk_bn254_artifacts() - }; - let proof = self.prover.wrap_plonk_bn254(outer_proof, &plonk_bn254_aritfacts); if kind == SP1ProofKind::Plonk { + let plonk_bn254_aritfacts = if sp1_prover::build::sp1_dev_mode() { + sp1_prover::build::try_build_plonk_bn254_artifacts_dev( + self.prover.wrap_vk(), + &outer_proof.proof, + ) + } else { + try_install_circuit_artifacts() + }; + let proof = self.prover.wrap_plonk_bn254(outer_proof, &plonk_bn254_aritfacts); + return Ok(SP1ProofWithPublicValues { proof: SP1Proof::Plonk(proof), stdin, public_values, sp1_version: self.version().to_string(), }); + } else if kind == SP1ProofKind::Groth16 { + let groth16_bn254_artifacts = if sp1_prover::build::sp1_dev_mode() { + sp1_prover::build::try_build_groth16_bn254_artifacts_dev( + self.prover.wrap_vk(), + &outer_proof.proof, + ) + } else { + try_install_circuit_artifacts() + }; + + let proof = self.prover.wrap_groth16_bn254(outer_proof, &groth16_bn254_artifacts); + return Ok(SP1ProofWithPublicValues { + proof: SP1Proof::Groth16(proof), + stdin, + public_values, + sp1_version: self.version().to_string(), + }); } unreachable!() } diff --git a/crates/sdk/src/provers/mock.rs b/crates/sdk/src/provers/mock.rs index 9eefd7d9b1..d4d2467045 100644 --- a/crates/sdk/src/provers/mock.rs +++ b/crates/sdk/src/provers/mock.rs @@ -13,8 +13,9 @@ use p3_baby_bear::BabyBear; use p3_field::{AbstractField, PrimeField}; use p3_fri::{FriProof, TwoAdicFriPcsProof}; use sp1_prover::{ - components::DefaultProverComponents, verify::verify_plonk_bn254_public_inputs, HashableKey, - PlonkBn254Proof, SP1Prover, + components::DefaultProverComponents, + verify::{verify_groth16_bn254_public_inputs, verify_plonk_bn254_public_inputs}, + Groth16Bn254Proof, HashableKey, PlonkBn254Proof, SP1Prover, }; use super::{ProofOpts, ProverType}; @@ -107,6 +108,23 @@ impl Prover for MockProver { sp1_version: self.version().to_string(), }) } + SP1ProofKind::Groth16 => { + let (public_values, _) = self.prover.execute(&pk.elf, &stdin, context)?; + Ok(SP1ProofWithPublicValues { + proof: SP1Proof::Groth16(Groth16Bn254Proof { + public_inputs: [ + pk.vk.hash_bn254().as_canonical_biguint().to_string(), + public_values.hash().to_string(), + ], + encoded_proof: "".to_string(), + raw_proof: "".to_string(), + groth16_vkey_hash: [0; 32], + }), + stdin, + public_values, + sp1_version: self.version().to_string(), + }) + } } } @@ -120,6 +138,10 @@ impl Prover for MockProver { verify_plonk_bn254_public_inputs(vkey, &bundle.public_values, public_inputs) .map_err(SP1VerificationError::Plonk) } + SP1Proof::Groth16(Groth16Bn254Proof { public_inputs, .. }) => { + verify_groth16_bn254_public_inputs(vkey, &bundle.public_values, public_inputs) + .map_err(SP1VerificationError::Groth16) + } _ => Ok(()), } } diff --git a/crates/sdk/src/provers/mod.rs b/crates/sdk/src/provers/mod.rs index e2d999b9b3..faf62126e5 100644 --- a/crates/sdk/src/provers/mod.rs +++ b/crates/sdk/src/provers/mod.rs @@ -17,7 +17,7 @@ use strum_macros::EnumString; use thiserror::Error; use crate::{ - install::try_install_plonk_bn254_artifacts, SP1Proof, SP1ProofKind, SP1ProofWithPublicValues, + install::try_install_circuit_artifacts, SP1Proof, SP1ProofKind, SP1ProofWithPublicValues, }; /// The type of prover. @@ -47,6 +47,8 @@ pub enum SP1VerificationError { Recursion(MachineVerificationError), #[error("Plonk verification error: {0}")] Plonk(anyhow::Error), + #[error("Groth16 verification error: {0}")] + Groth16(anyhow::Error), } /// An implementation of [crate::ProverClient]. @@ -82,28 +84,41 @@ pub trait Prover: Send + Sync { if bundle.sp1_version != self.version() { return Err(SP1VerificationError::VersionMismatch(bundle.sp1_version.clone())); } - match bundle.proof.clone() { + match &bundle.proof { SP1Proof::Core(proof) => self .sp1_prover() - .verify(&SP1CoreProofData(proof), vkey) + .verify(&SP1CoreProofData(proof.clone()), vkey) .map_err(SP1VerificationError::Core), SP1Proof::Compressed(proof) => self .sp1_prover() - .verify_compressed(&SP1ReduceProof { proof }, vkey) + .verify_compressed(&SP1ReduceProof { proof: proof.clone() }, vkey) .map_err(SP1VerificationError::Recursion), SP1Proof::Plonk(proof) => self .sp1_prover() .verify_plonk_bn254( - &proof, + proof, vkey, &bundle.public_values, &if sp1_prover::build::sp1_dev_mode() { sp1_prover::build::plonk_bn254_artifacts_dev_dir() } else { - try_install_plonk_bn254_artifacts() + try_install_circuit_artifacts() }, ) .map_err(SP1VerificationError::Plonk), + SP1Proof::Groth16(proof) => self + .sp1_prover() + .verify_groth16_bn254( + proof, + vkey, + &bundle.public_values, + &if sp1_prover::build::sp1_dev_mode() { + sp1_prover::build::groth16_bn254_artifacts_dev_dir() + } else { + try_install_circuit_artifacts() + }, + ) + .map_err(SP1VerificationError::Groth16), } } }