From 47eac3393678b3ccc2fbd084e5d099c973946302 Mon Sep 17 00:00:00 2001 From: Yuwen Zhang Date: Tue, 12 Nov 2024 14:51:38 -0800 Subject: [PATCH 1/2] docs: docker on mac (#1771) --- .github/actions/setup/action.yml | 2 ++ audits/rkm0959.md | 2 +- book/generating-proofs/basics.md | 3 ++- book/getting-started/hardware-requirements.md | 4 +++- book/verification/onchain/getting-started.md | 3 ++- crates/recursion/gnark-ffi/src/ffi/docker.rs | 4 +++- 6 files changed, 13 insertions(+), 5 deletions(-) diff --git a/.github/actions/setup/action.yml b/.github/actions/setup/action.yml index d12a1d16a..d928b91df 100644 --- a/.github/actions/setup/action.yml +++ b/.github/actions/setup/action.yml @@ -1,3 +1,5 @@ +# Note: this is only compatible with Linux runners. + name: Test setup inputs: pull_token: diff --git a/audits/rkm0959.md b/audits/rkm0959.md index 3fb90318a..2c09f508d 100644 --- a/audits/rkm0959.md +++ b/audits/rkm0959.md @@ -393,7 +393,7 @@ this passes each core verification, and since the RecursionPublicValue of proof - shard 1, 2's committed_value_digest = `0` - shard 3, 4's committed_value_digest = `x` -this passes each core verification, as proof #2 thinks shard 3 is its "first" shard - so it actually thinks that the `committed_value_digest` didn't change. This means that the whole "no cpu chip means `commited_value_digest` equal" thing actually just passes. Then, in the compress verification, we'll just see the committed_value_digest go from `0` to `x`, which is also completely fine. However, the committed_value_digest will go `0, 0, x, x`, where the change occurs on a shard without cpu chip - which isn't supposed to happen. +this passes each core verification, as proof #2 thinks shard 3 is its "first" shard - so it actually thinks that the `committed_value_digest` didn't change. This means that the whole "no cpu chip means `committed_value_digest` equal" thing actually just passes. Then, in the compress verification, we'll just see the committed_value_digest go from `0` to `x`, which is also completely fine. However, the committed_value_digest will go `0, 0, x, x`, where the change occurs on a shard without cpu chip - which isn't supposed to happen. While this is a slight incompatibility, the main invariant (if nonzero, public digest can only be one non-zero value) is preserved. Therefore, we did not fix this observation. diff --git a/book/generating-proofs/basics.md b/book/generating-proofs/basics.md index 85273d96b..7bde99bbc 100644 --- a/book/generating-proofs/basics.md +++ b/book/generating-proofs/basics.md @@ -13,7 +13,8 @@ To make this more concrete, let's walk through a simple example of generating a You can run the above script in the `script` directory with `RUST_LOG=info cargo run --release`. Note that running the above script will generate a proof locally.
-WARNING: Local proving often is much slower than the prover network and for certain proof types (e.g. Groth16, PLONK) require a significant amount of RAM and will likely not work on a laptop. +WARNING: Local proving often is much slower than the prover network and for certain proof types (e.g. Groth16, PLONK) require a +significant amount of RAM. You might only be able to generate proofs for small inputs locally.
We recommend using the [prover network](./prover-network.md) to generate proofs. Read more about the [recommended workflow](./recommended-workflow.md) for developing with SP1. diff --git a/book/getting-started/hardware-requirements.md b/book/getting-started/hardware-requirements.md index 828ea9608..3877d22e8 100644 --- a/book/getting-started/hardware-requirements.md +++ b/book/getting-started/hardware-requirements.md @@ -31,7 +31,9 @@ which can be parallelized with multiple cores. Our prover requires keeping large matrices (i.e., traces) in memory to generate the proofs. Certain steps of the prover have a minimum memory requirement, meaning that if you have less than this amount of memory, the process will OOM. -This effect is most noticeable when using the Groth16 or PLONK provers. +This effect is most noticeable when using the Groth16 or PLONK provers. If you're running the Groth16 or Plonk provers locally +on Mac or Windows using docker, you might need to increase the memory limit for +[docker desktop](https://docs.docker.com/desktop/settings-and-maintenance/settings/#resources). ### Disk diff --git a/book/verification/onchain/getting-started.md b/book/verification/onchain/getting-started.md index 8519d620a..715b100d5 100644 --- a/book/verification/onchain/getting-started.md +++ b/book/verification/onchain/getting-started.md @@ -14,7 +14,8 @@ By default, the proofs generated by SP1 are not verifiable onchain, as they are > WARNING: The Groth16 and PLONK provers are only guaranteed to work on official releases of SP1. To > use Groth16 or PLONK proving & verification locally, ensure that you have Docker installed and have -> at least 128GB of RAM. +> at least 32GB of RAM. Note that you might need to increase the memory limit for +> [docker desktop](https://docs.docker.com/desktop/settings-and-maintenance/settings/#resources) if you're running on Mac. ### Example diff --git a/crates/recursion/gnark-ffi/src/ffi/docker.rs b/crates/recursion/gnark-ffi/src/ffi/docker.rs index 119253d49..bf0f2865b 100644 --- a/crates/recursion/gnark-ffi/src/ffi/docker.rs +++ b/crates/recursion/gnark-ffi/src/ffi/docker.rs @@ -47,8 +47,10 @@ fn call_docker(args: &[&str], mounts: &[(&str, &str)]) -> Result<()> { } cmd.arg(get_docker_image()); cmd.args(args); - if !cmd.status()?.success() { + let result = cmd.status()?; + if !result.success() { log::error!("Failed to run `docker run`: {:?}", cmd); + log::error!("Execution result: {:?}", result); return Err(anyhow!("docker command failed")); } Ok(()) From af1c35d590cd980af0c2cbf0e93428df0946b9ee Mon Sep 17 00:00:00 2001 From: Yuwen Zhang Date: Tue, 12 Nov 2024 15:19:38 -0800 Subject: [PATCH 2/2] feat(verifier): verify_bytes api GRO-304 (#1784) --- book/verification/off-chain-verification.md | 41 ++++++++++++++++- crates/verifier/README.md | 2 +- crates/verifier/src/groth16/converter.rs | 2 +- crates/verifier/src/groth16/mod.rs | 51 ++++++++++++++++++--- crates/verifier/src/groth16/verify.rs | 4 +- crates/verifier/src/lib.rs | 2 +- crates/verifier/src/plonk/mod.rs | 45 +++++++++++++++--- crates/verifier/src/plonk/verify.rs | 4 +- 8 files changed, 130 insertions(+), 21 deletions(-) diff --git a/book/verification/off-chain-verification.md b/book/verification/off-chain-verification.md index 3dd4bc601..100e554eb 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 6a4042e04..8755cfa7c 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 6c7a5e3b9..6648eb95c 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 c6cf98a23..4dfe4796b 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 686e62ff6..636a7f66d 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 4cfbcc884..6da0872d6 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],