From ead5a5fe4113ce0d758765557bde50a96da7fad6 Mon Sep 17 00:00:00 2001 From: mattstam Date: Tue, 13 Aug 2024 11:58:27 -0700 Subject: [PATCH] feat: add groth16 --- book/developers/building-plonk-artifacts.md | 8 +- crates/prover/Cargo.toml | 4 + crates/prover/Makefile | 11 +- crates/prover/scripts/build_groth16_bn254.rs | 18 +++ crates/prover/scripts/e2e.rs | 37 ++++- crates/prover/src/build.rs | 45 +++++- crates/prover/src/init.rs | 3 +- crates/prover/src/lib.rs | 2 +- crates/prover/src/types.rs | 2 +- crates/recursion/gnark-cli/src/main.rs | 87 +++++++++--- crates/recursion/gnark-ffi/go/.gitignore | 2 +- crates/recursion/gnark-ffi/go/main.go | 71 +++++++++- crates/recursion/gnark-ffi/go/sp1/build.go | 112 ++++++++++++++- crates/recursion/gnark-ffi/go/sp1/prove.go | 83 ++++++++++- crates/recursion/gnark-ffi/go/sp1/sp1.go | 12 +- crates/recursion/gnark-ffi/go/sp1/utils.go | 23 +++ crates/recursion/gnark-ffi/go/sp1/verify.go | 51 ++++++- crates/recursion/gnark-ffi/src/ffi/docker.rs | 75 +++++++++- crates/recursion/gnark-ffi/src/ffi/native.rs | 89 +++++++++++- .../recursion/gnark-ffi/src/groth16_bn254.rs | 133 ++++++++++++++++++ crates/recursion/gnark-ffi/src/lib.rs | 5 +- crates/recursion/gnark-ffi/src/plonk_bn254.rs | 13 +- crates/recursion/gnark-ffi/src/proof.rs | 25 ++++ 23 files changed, 834 insertions(+), 77 deletions(-) create mode 100644 crates/prover/scripts/build_groth16_bn254.rs create mode 100644 crates/recursion/gnark-ffi/src/groth16_bn254.rs create mode 100644 crates/recursion/gnark-ffi/src/proof.rs diff --git a/book/developers/building-plonk-artifacts.md b/book/developers/building-plonk-artifacts.md index fd0ba2a68b..a1a951791d 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..6572bc06de --- /dev/null +++ b/crates/prover/scripts/build_groth16_bn254.rs @@ -0,0 +1,18 @@ +use std::path::PathBuf; + +use clap::Parser; +use sp1_core::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..7e55193b0a 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, default_value = "plonk")] + 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..044f5cf5a7 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,11 +27,32 @@ 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") } +/// 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("groth16_bn254").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("groth16_bn254").join("dev") +} + /// Build the plonk bn254 artifacts to the given directory for the given verification key and /// template proof. pub fn build_plonk_bn254_artifacts( @@ -45,6 +66,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 +88,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..0e48e123ca 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::proof::PlonkBn254Proof; +pub use sp1_recursion_program::machine::ReduceProgramType; 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..1768d62095 100644 --- a/crates/prover/src/lib.rs +++ b/crates/prover/src/lib.rs @@ -45,8 +45,8 @@ 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::plonk_bn254::PlonkBn254Prover; +pub use sp1_recursion_gnark_ffi::proof::PlonkBn254Proof; use sp1_recursion_program::hints::Hintable; pub use sp1_recursion_program::machine::{ ReduceProgramType, SP1CompressMemoryLayout, SP1DeferredMemoryLayout, SP1RecursionMemoryLayout, diff --git a/crates/prover/src/types.rs b/crates/prover/src/types.rs index 93da49ac04..6ff24199eb 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::PlonkBn254Proof; use sp1_recursion_program::machine::{ SP1CompressMemoryLayout, SP1DeferredMemoryLayout, SP1RecursionMemoryLayout, }; diff --git a/crates/recursion/gnark-cli/src/main.rs b/crates/recursion/gnark-cli/src/main.rs index 61f5e0602a..e181a554a8 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, default_value = "plonk")] + system: String, } #[derive(Debug, Args)] @@ -36,6 +41,8 @@ struct ProveArgs { data_dir: String, witness_path: String, output_path: String, + #[arg(short, long, default_value = "plonk")] + system: String, } #[derive(Debug, Args)] @@ -45,34 +52,68 @@ struct VerifyArgs { vkey_hash: String, committed_values_digest: String, output_path: String, + #[arg(short, long, default_value = "plonk")] + system: String, } #[derive(Debug, Args)] struct TestArgs { witness_json: String, constraints_json: String, + #[arg(short, long, default_value = "plonk")] + 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 +123,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/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..6810352a41 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 ( @@ -15,6 +21,7 @@ import ( "fmt" "os" "sync" + "unsafe" "github.com/consensys/gnark-crypto/ecc" "github.com/consensys/gnark/backend/groth16" @@ -35,7 +42,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 +62,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 +72,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 +97,67 @@ 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.size_t(unsafe.Sizeof(C.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) { + 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..93a251cccc 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 + "/plonk_witness.json" 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,105 @@ 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 + "/groth16_witness.json" + 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..975a8de7db 100644 --- a/crates/recursion/gnark-ffi/go/sp1/sp1.go +++ b/crates/recursion/gnark-ffi/go/sp1/sp1.go @@ -14,10 +14,14 @@ 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" 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..45cc7e69e0 100644 --- a/crates/recursion/gnark-ffi/src/ffi/docker.rs +++ b/crates/recursion/gnark-ffi/src/ffi/docker.rs @@ -1,6 +1,6 @@ use sp1_core_machine::SP1_CIRCUIT_VERSION; -use crate::PlonkBn254Proof; +use crate::{Groth16Bn254Proof, PlonkBn254Proof}; use std::{io::Write, process::Command}; /// Checks that docker is installed and running. @@ -46,7 +46,7 @@ pub fn prove_plonk_bn254(data_dir: &str, witness_path: &str) -> PlonkBn254Proof (output_file.path().to_str().unwrap(), "/output"), ]; assert_docker(); - call_docker(&["prove-plonk", "/circuit", "/witness", "/output"], &mounts) + call_docker(&["prove", "--system", "plonk", "/circuit", "/witness", "/output"], &mounts) .expect("failed to prove with docker"); bincode::deserialize_from(&output_file).expect("failed to deserialize result") } @@ -55,7 +55,8 @@ pub fn build_plonk_bn254(data_dir: &str) { 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", "plonk", circuit_dir], &mounts) + .expect("failed to build with docker"); } pub fn verify_plonk_bn254( @@ -90,7 +91,73 @@ pub fn verify_plonk_bn254( pub fn test_plonk_bn254(witness_json: &str, constraints_json: &str) { let mounts = [(constraints_json, "/constraints"), (witness_json, "/witness")]; assert_docker(); - call_docker(&["test-plonk", "/constraints", "/witness"], &mounts) + call_docker(&["test", "--system", "plonk", "/constraints", "/witness"], &mounts) + .expect("failed to test with docker"); +} + +pub fn prove_groth16_bn254(data_dir: &str, witness_path: &str) -> Groth16Bn254Proof { + let output_file = tempfile::NamedTempFile::new().unwrap(); + let mounts = [ + (data_dir, "/circuit"), + (witness_path, "/witness"), + (output_file.path().to_str().unwrap(), "/output"), + ]; + assert_docker(); + call_docker(&["prove", "--system", "groth16", "/circuit", "/witness", "/output"], &mounts) + .expect("failed to prove with docker"); + bincode::deserialize_from(&output_file).expect("failed to deserialize result") +} + +pub fn build_groth16_bn254(data_dir: &str) { + let circuit_dir = if data_dir.ends_with("dev") { "/circuit_dev" } else { "/circuit" }; + let mounts = [(data_dir, circuit_dir)]; + assert_docker(); + call_docker(&["build", "--system", "groth16", circuit_dir], &mounts) + .expect("failed to build with docker"); +} + +pub fn verify_groth16_bn254( + 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(); + let mounts = [ + (data_dir, "/circuit"), + (proof_file.path().to_str().unwrap(), "/proof"), + (output_file.path().to_str().unwrap(), "/output"), + ]; + assert_docker(); + call_docker( + &[ + "verify", + "--system", + "groth16", + "/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(); + if result == "OK" { + Ok(()) + } else { + Err(result) + } +} + +pub fn test_groth16_bn254(witness_json: &str, constraints_json: &str) { + let mounts = [(constraints_json, "/constraints"), (witness_json, "/witness")]; + assert_docker(); + call_docker(&["test", "--system", "groth16", "/constraints", "/witness"], &mounts) .expect("failed to test with docker"); } diff --git a/crates/recursion/gnark-ffi/src/ffi/native.rs b/crates/recursion/gnark-ffi/src/ffi/native.rs index 9a197eb1bc..c8aaf7ea3c 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}; @@ -85,6 +85,75 @@ pub fn test_plonk_bn254(witness_json: &str, constraints_json: &str) { } } +pub fn build_groth16_bn254(data_dir: &str) { + let data_dir = CString::new(data_dir).expect("CString::new failed"); + + unsafe { + bind::BuildGroth16Bn254(data_dir.as_ptr() as *mut c_char); + } +} + +pub fn prove_groth16_bn254(data_dir: &str, witness_path: &str) -> Groth16Bn254Proof { + let data_dir = CString::new(data_dir).expect("CString::new failed"); + let witness_path = CString::new(witness_path).expect("CString::new failed"); + + let proof = unsafe { + let proof = bind::ProveGroth16Bn254( + 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 + }; + + proof.into_rust() +} + +pub fn verify_groth16_bn254( + data_dir: &str, + proof: &str, + vkey_hash: &str, + committed_values_digest: &str, +) -> Result<(), String> { + let data_dir = CString::new(data_dir).expect("CString::new failed"); + let proof = CString::new(proof).expect("CString::new failed"); + let vkey_hash = CString::new(vkey_hash).expect("CString::new failed"); + let committed_values_digest = + CString::new(committed_values_digest).expect("CString::new failed"); + + let err_ptr = unsafe { + bind::VerifyGroth16Bn254( + data_dir.as_ptr() as *mut c_char, + proof.as_ptr() as *mut c_char, + vkey_hash.as_ptr() as *mut c_char, + committed_values_digest.as_ptr() as *mut c_char, + ) + }; + if err_ptr.is_null() { + Ok(()) + } else { + // Safety: The error message is returned from the go code and is guaranteed to be valid. + let err = unsafe { CString::from_raw(err_ptr) }; + Err(err.into_string().unwrap()) + } +} + +pub fn test_groth16_bn254(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::TestGroth16Bn254( + witness_json.as_ptr() as *mut c_char, + build_dir.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!("TestGroth16Bn254 failed: {}", err.into_string().unwrap()); + } + } +} + pub fn test_babybear_poseidon2() { unsafe { let err_ptr = bind::TestPoseidonBabyBear2(); @@ -127,6 +196,24 @@ impl C_PlonkBn254Proof { } } +impl C_Groth16Bn254Proof { + /// Converts a C Groth16Bn254Proof into a Rust Groth16Bn254Proof, freeing the C strings. + fn into_rust(self) -> Groth16Bn254Proof { + // Safety: The raw pointers are not used anymore after converted into Rust strings. + unsafe { + Groth16Bn254Proof { + public_inputs: [ + c_char_ptr_to_string(self.PublicInputs[0]), + c_char_ptr_to_string(self.PublicInputs[1]), + ], + encoded_proof: c_char_ptr_to_string(self.EncodedProof), + raw_proof: c_char_ptr_to_string(self.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..cdb5928aa9 --- /dev/null +++ b/crates/recursion/gnark-ffi/src/groth16_bn254.rs @@ -0,0 +1,133 @@ +use std::{ + fs::File, + io::Write, + path::{Path, PathBuf}, +}; + +use crate::witness::GnarkWitness; +use crate::{ + ffi::{build_groth16_bn254, prove_groth16_bn254, test_groth16_bn254, verify_groth16_bn254}, + Groth16Bn254Proof, +}; + +use num_bigint::BigUint; +use sha2::Digest; +use sha2::Sha256; +use sp1_core::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("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("SP1Verifier.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(), + ); + 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..dad94f205d 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 { @@ -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(); 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], +}