Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(verifier): verify_bytes api GRO-304 #1784

Merged
merged 6 commits into from
Nov 12, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions book/verification/off-chain-verification.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion crates/verifier/src/groth16/converter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Groth16Proof, Groth16Error> {
let ar = uncompressed_bytes_to_g1_point(&buffer[..64])?;
let bs = uncompressed_bytes_to_g2_point(&buffer[64..192])?;
Expand Down
51 changes: 44 additions & 7 deletions crates/verifier/src/groth16/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
///
Expand Down Expand Up @@ -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(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: add #[must_use]

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::<Vec<_>>();
verify_groth16_algebraic(&groth16_vk, &proof, &public_inputs)
}
}
4 changes: 2 additions & 2 deletions crates/verifier/src/groth16/verify.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,11 @@ fn prepare_inputs(vk: Groth16VerifyingKey, public_inputs: &[Fr]) -> Result<G1, G
.into())
}

/// Verify the Groth16 proof
/// Verify the Groth16 proof using algebraic inputs.
///
/// First, prepare the public inputs by folding them with the verification key.
/// Then, verify the proof by checking the pairing equation.
pub(crate) fn verify_groth16_raw(
pub(crate) fn verify_groth16_algebraic(
vk: &Groth16VerifyingKey,
proof: &Groth16Proof,
public_inputs: &[Fr],
Expand Down
45 changes: 39 additions & 6 deletions crates/verifier/src/plonk/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,21 @@ pub(crate) mod error;

pub(crate) use converter::{load_plonk_proof_from_bytes, load_plonk_verifying_key_from_bytes};
pub(crate) use proof::PlonkProof;
pub(crate) use verify::verify_plonk_raw;
pub(crate) use verify::verify_plonk_algebraic;

use alloc::vec::Vec;
use bn::Fr;
use error::PlonkError;
use sha2::{Digest, Sha256};

use crate::{bn254_public_values, decode_sp1_vkey_hash, error::Error};
use crate::{decode_sp1_vkey_hash, error::Error, hash_public_inputs};
/// A verifier for Plonk zero-knowledge proofs.
#[derive(Debug)]
pub struct PlonkVerifier;

impl PlonkVerifier {
/// Verifies an SP1 PLONK proof, as generated by the SP1 SDK.
///
/// # Arguments
///
/// * `proof` - The proof bytes.
Expand Down Expand Up @@ -66,11 +70,40 @@ impl PlonkVerifier {
}

let sp1_vkey_hash = decode_sp1_vkey_hash(sp1_vkey_hash)?;
let public_inputs = bn254_public_values(&sp1_vkey_hash, sp1_public_inputs);

let plonk_vk = load_plonk_verifying_key_from_bytes(plonk_vk)?;
let proof = load_plonk_proof_from_bytes(&proof[4..], plonk_vk.qcp.len())?;
Self::verify_gnark_proof(
&proof[4..],
&[sp1_vkey_hash, hash_public_inputs(sp1_public_inputs)],
plonk_vk,
)
}

/// Verifies a Gnark PLONK 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 PLONK proof bytes (without the 4-byte vkey hash prefix)
/// * `public_inputs` - The public inputs to the circuit
/// * `plonk_vk` - The PLONK verifying key bytes
///
/// # Returns
///
/// A [`Result`] containing unit `()` if the proof is valid,
/// or a [`PlonkError`] if verification fails.
pub fn verify_gnark_proof(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: add #[must_use]

proof: &[u8],
public_inputs: &[[u8; 32]],
plonk_vk: &[u8],
) -> 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::<Vec<_>>();
verify_plonk_algebraic(&plonk_vk, &proof, &public_inputs)
}
}
4 changes: 2 additions & 2 deletions crates/verifier/src/plonk/verify.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ pub(crate) struct PlonkVerifyingKey {
pub(crate) commitment_constraint_indexes: Vec<usize>,
}

/// Verifies a PLONK proof
/// Verifies a PLONK proof using algebraic inputs.
///
/// # Arguments
///
Expand All @@ -44,7 +44,7 @@ pub(crate) struct PlonkVerifyingKey {
/// # Returns
///
/// * `Result<bool, PlonkError>` - 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(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add #[must_use] so people don't forget to unwrap

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

must_use is already on by default for Result. doing something like this gives me a warning

crate::PlonkVerifier::verify(...);

vk: &PlonkVerifyingKey,
proof: &PlonkProof,
public_inputs: &[Fr],
Expand Down
Loading