diff --git a/book/verification/off-chain-verification.md b/book/verification/off-chain-verification.md index 3dd4bc601f..100e554eb4 100644 --- a/book/verification/off-chain-verification.md +++ b/book/verification/off-chain-verification.md @@ -4,7 +4,7 @@ You can verify SP1 Groth16 and Plonk proofs in `no_std` environments with [`sp1-verifier`](https://docs.rs/sp1-verifier/latest/sp1_verifier/). -`sp1-verifier` is also patched to verify Groth16 and Plonk proofs within the SP1 ZKVM, using +`sp1-verifier` is also patched to verify Groth16 and Plonk proofs within the SP1 zkVM, using [bn254](https://blog.succinct.xyz/succinctshipsprecompiles/) precompiles. For an example of this, see the [Groth16 Example](https://github.com/succinctlabs/sp1/tree/main/examples/groth16/). @@ -43,6 +43,45 @@ Here, the proof, public inputs, and vkey hash are read from stdin. See the follo > Note that the SP1 SDK itself is *not* `no_std` compatible. +### Advanced: `verify_gnark_proof` + +`sp1-verifier` also exposes [`Groth16Verifier::verify_gnark_proof`](https://docs.rs/sp1-verifier/latest/sp1_verifier/struct.Groth16Verifier.html#method.verify_gnark_proof) and [`PlonkVerifier::verify_gnark_proof`](https://docs.rs/sp1-verifier/latest/sp1_verifier/struct.PlonkVerifier.html#method.verify_gnark_proof), +which verifies any Groth16 or Plonk proof from Gnark. This is especially useful for verifying custom Groth16 and Plonk proofs +efficiently in the SP1 zkVM. + +The following snippet demonstrates how you might serialize a Gnark proof in a way that `sp1-verifier` can use. + +```go +// Write the verifier key. +vkFile, err := os.Create("vk.bin") +if err != nil { + panic(err) +} +defer vkFile.Close() + +// Here, `vk` is a `groth16_bn254.VerifyingKey` or `plonk_bn254.VerifyingKey`. +_, err = vk.WriteTo(vkFile) +if err != nil { + panic(err) +} + +// Write the proof. +proofFile, err := os.Create("proof.bin") +if err != nil { + panic(err) +} +defer proofFile.Close() + +// Here, `proof` is a `groth16_bn254.Proof` or `plonk_bn254.Proof`. +_, err = proof.WriteTo(proofFile) +if err != nil { + panic(err) +} +``` + +Public values are serialized as big-endian `Fr` values. The default Gnark serialization will work +out of the box. + ## Wasm Verification The [`example-sp1-wasm-verifier`](https://github.com/succinctlabs/example-sp1-wasm-verifier) demonstrates how to diff --git a/crates/verifier/README.md b/crates/verifier/README.md index 6a4042e04e..8755cfa7c1 100644 --- a/crates/verifier/README.md +++ b/crates/verifier/README.md @@ -6,7 +6,7 @@ to be generated using the [SP1 SDK](../sdk). ## Features Groth16 and Plonk proof verification are supported in `no-std` environments. Verification in the -SP1 ZKVM context is patched, in order to make use of the +SP1 zkVM context is patched, in order to make use of the [bn254 precompiles](https://blog.succinct.xyz/succinctshipsprecompiles/). ### Pre-generated verification keys diff --git a/crates/verifier/src/groth16/converter.rs b/crates/verifier/src/groth16/converter.rs index 6c7a5e3b97..6648eb95c9 100644 --- a/crates/verifier/src/groth16/converter.rs +++ b/crates/verifier/src/groth16/converter.rs @@ -13,7 +13,7 @@ use super::error::Groth16Error; /// Load the Groth16 proof from the given byte slice. /// /// The byte slice is represented as 2 uncompressed g1 points, and one uncompressed g2 point, -/// as outputted from gnark. +/// as outputted from Gnark. pub(crate) fn load_groth16_proof_from_bytes(buffer: &[u8]) -> Result { let ar = uncompressed_bytes_to_g1_point(&buffer[..64])?; let bs = uncompressed_bytes_to_g2_point(&buffer[64..192])?; diff --git a/crates/verifier/src/groth16/mod.rs b/crates/verifier/src/groth16/mod.rs index c6cf98a23a..4dfe4796b4 100644 --- a/crates/verifier/src/groth16/mod.rs +++ b/crates/verifier/src/groth16/mod.rs @@ -2,19 +2,22 @@ mod converter; pub mod error; mod verify; +use bn::Fr; pub(crate) use converter::{load_groth16_proof_from_bytes, load_groth16_verifying_key_from_bytes}; -use sha2::{Digest, Sha256}; pub(crate) use verify::*; use error::Groth16Error; -use crate::{bn254_public_values, decode_sp1_vkey_hash, error::Error}; +use crate::{decode_sp1_vkey_hash, error::Error, hash_public_inputs}; + +use alloc::vec::Vec; +use sha2::{Digest, Sha256}; /// A verifier for Groth16 zero-knowledge proofs. #[derive(Debug)] pub struct Groth16Verifier; impl Groth16Verifier { - /// Verifies a Groth16 proof. + /// Verifies an SP1 Groth16 proof, as generated by the SP1 SDK. /// /// # Arguments /// @@ -57,11 +60,45 @@ impl Groth16Verifier { } let sp1_vkey_hash = decode_sp1_vkey_hash(sp1_vkey_hash)?; - let public_inputs = bn254_public_values(&sp1_vkey_hash, sp1_public_inputs); - let proof = load_groth16_proof_from_bytes(&proof[4..])?; - let groth16_vk = load_groth16_verifying_key_from_bytes(groth16_vk)?; + Self::verify_gnark_proof( + &proof[4..], + &[sp1_vkey_hash, hash_public_inputs(sp1_public_inputs)], + groth16_vk, + ) + } + + /// Verifies a Gnark Groth16 proof using raw byte inputs. + /// + /// WARNING: if you're verifying an SP1 proof, you should use [`verify`] instead. + /// This is a lower-level verification method that works directly with raw bytes rather than + /// the SP1 SDK's data structures. + /// + /// # Arguments + /// + /// * `proof` - The raw Groth16 proof bytes (without the 4-byte vkey hash prefix) + /// * `public_inputs` - The public inputs to the circuit + /// * `groth16_vk` - The Groth16 verifying key bytes + /// + /// # Returns + /// + /// A [`Result`] containing unit `()` if the proof is valid, + /// or a [`Groth16Error`] if verification fails. + /// + /// # Note + /// + /// This method expects the raw proof bytes without the 4-byte vkey hash prefix that + /// [`verify`] checks. If you have a complete proof with the prefix, use [`verify`] instead. + pub fn verify_gnark_proof( + proof: &[u8], + public_inputs: &[[u8; 32]], + groth16_vk: &[u8], + ) -> Result<(), Groth16Error> { + let proof = load_groth16_proof_from_bytes(proof).unwrap(); + let groth16_vk = load_groth16_verifying_key_from_bytes(groth16_vk).unwrap(); - verify_groth16_raw(&groth16_vk, &proof, &public_inputs) + let public_inputs = + public_inputs.iter().map(|input| Fr::from_slice(input).unwrap()).collect::>(); + verify_groth16_algebraic(&groth16_vk, &proof, &public_inputs) } } diff --git a/crates/verifier/src/groth16/verify.rs b/crates/verifier/src/groth16/verify.rs index 686e62ff61..636a7f66d6 100644 --- a/crates/verifier/src/groth16/verify.rs +++ b/crates/verifier/src/groth16/verify.rs @@ -46,11 +46,11 @@ fn prepare_inputs(vk: Groth16VerifyingKey, public_inputs: &[Fr]) -> Result Result<(), PlonkError> { + let plonk_vk = load_plonk_verifying_key_from_bytes(plonk_vk).unwrap(); + let proof = load_plonk_proof_from_bytes(proof, plonk_vk.qcp.len()).unwrap(); - verify_plonk_raw(&plonk_vk, &proof, &public_inputs) + let public_inputs = + public_inputs.iter().map(|input| Fr::from_slice(input).unwrap()).collect::>(); + verify_plonk_algebraic(&plonk_vk, &proof, &public_inputs) } } diff --git a/crates/verifier/src/plonk/verify.rs b/crates/verifier/src/plonk/verify.rs index 4cfbcc884f..6da0872d6e 100644 --- a/crates/verifier/src/plonk/verify.rs +++ b/crates/verifier/src/plonk/verify.rs @@ -33,7 +33,7 @@ pub(crate) struct PlonkVerifyingKey { pub(crate) commitment_constraint_indexes: Vec, } -/// Verifies a PLONK proof +/// Verifies a PLONK proof using algebraic inputs. /// /// # Arguments /// @@ -44,7 +44,7 @@ pub(crate) struct PlonkVerifyingKey { /// # Returns /// /// * `Result` - Returns true if the proof is valid, or an error if verification fails -pub(crate) fn verify_plonk_raw( +pub(crate) fn verify_plonk_algebraic( vk: &PlonkVerifyingKey, proof: &PlonkProof, public_inputs: &[Fr],