Skip to main content

sp1_verifier/groth16/
mod.rs

1mod converter;
2pub mod error;
3mod verify;
4
5use bn::Fr;
6pub(crate) use converter::{load_groth16_proof_from_bytes, load_groth16_verifying_key_from_bytes};
7pub(crate) use verify::*;
8
9use error::Groth16Error;
10
11use crate::{
12    blake3_hash, constants::VK_HASH_PREFIX_LENGTH, decode_sp1_vkey_hash, error::Error,
13    hash_public_inputs, hash_public_inputs_with_fn, VK_ROOT_BYTES,
14};
15
16use alloc::vec::Vec;
17use sha2::{Digest, Sha256};
18
19#[cfg(feature = "ark")]
20pub mod ark_converter;
21
22/// A verifier for Groth16 zero-knowledge proofs.
23#[derive(Debug)]
24pub struct Groth16Verifier;
25impl Groth16Verifier {
26    /// Verifies an SP1 Groth16 proof, as generated by the SP1 SDK.
27    ///
28    /// # Arguments
29    ///
30    /// * `proof` - The proof bytes.
31    /// * `public_inputs` - The SP1 public inputs.
32    /// * `sp1_vkey_hash` - The SP1 vkey hash. This is generated in the following manner:
33    ///
34    /// ```ignore
35    /// use sp1_sdk::ProverClient;
36    /// let client = ProverClient::from_env();
37    /// let (pk, vk) = client.setup(ELF);
38    /// let sp1_vkey_hash = vk.bytes32();
39    /// ```
40    /// * `groth16_vk` - The Groth16 verifying key bytes. Usually this will be the
41    ///   [`static@crate::GROTH16_VK_BYTES`] constant, which is the Groth16 verifying key for the
42    ///   current SP1 version.
43    ///
44    /// # Returns
45    ///
46    /// A success [`Result`] if verification succeeds, or a [`Groth16Error`] if verification fails.
47    pub fn verify(
48        proof: &[u8],
49        sp1_public_inputs: &[u8],
50        sp1_vkey_hash: &str,
51        groth16_vk: &[u8],
52    ) -> Result<(), Groth16Error> {
53        Self::verify_with_exit_code(proof, sp1_public_inputs, sp1_vkey_hash, groth16_vk, [0u8; 32])
54    }
55
56    /// Verifies an SP1 Groth16 proof with an expected exit code. Only use this if you're trying to
57    /// verify a program that panics. Otherwise use [`verify`].
58    ///
59    /// # Arguments
60    ///
61    /// * `proof` - The proof bytes.
62    /// * `public_inputs` - The SP1 public inputs.
63    /// * `sp1_vkey_hash` - The SP1 vkey hash. This is generated in the following manner:
64    ///
65    /// ```ignore
66    /// use sp1_sdk::ProverClient;
67    /// let client = ProverClient::from_env();
68    /// let (pk, vk) = client.setup(ELF);
69    /// let sp1_vkey_hash = vk.bytes32();
70    /// ```
71    /// * `groth16_vk` - The Groth16 verifying key bytes. Usually this will be the
72    ///   [`static@crate::GROTH16_VK_BYTES`] constant, which is the Groth16 verifying key for the
73    ///   current SP1 version.
74    /// * `expected_exit_code` - The expected exit code to verify against.
75    ///
76    /// # Returns
77    ///
78    /// A success [`Result`] if verification succeeds, or a [`Groth16Error`] if verification fails.
79    pub fn verify_with_exit_code(
80        proof: &[u8],
81        sp1_public_inputs: &[u8],
82        sp1_vkey_hash: &str,
83        groth16_vk: &[u8],
84        expected_exit_code: [u8; 32],
85    ) -> Result<(), Groth16Error> {
86        if proof.len() < VK_HASH_PREFIX_LENGTH + 32 + 32 + 32 {
87            return Err(Groth16Error::GeneralError(Error::InvalidData));
88        }
89
90        // Hash the vk and get the first 4 bytes.
91        let groth16_vk_hash: [u8; 4] = Sha256::digest(groth16_vk)[..VK_HASH_PREFIX_LENGTH]
92            .try_into()
93            .map_err(|_| Groth16Error::GeneralError(Error::InvalidData))?;
94
95        // Check to make sure that this proof was generated by the groth16 proving key corresponding
96        // to the given groth16_vk.
97        //
98        // SP1 prepends the raw Groth16 proof with the first 4 bytes of the groth16 vkey to
99        // facilitate this check.
100        if groth16_vk_hash != proof[..VK_HASH_PREFIX_LENGTH] {
101            return Err(Groth16Error::Groth16VkeyHashMismatch);
102        }
103
104        let sp1_vkey_hash = decode_sp1_vkey_hash(sp1_vkey_hash)?;
105
106        let exit_code: [u8; 32] = proof[VK_HASH_PREFIX_LENGTH..VK_HASH_PREFIX_LENGTH + 32]
107            .try_into()
108            .map_err(|_| Groth16Error::GeneralError(Error::InvalidData))?;
109
110        let vk_root: [u8; 32] = proof[VK_HASH_PREFIX_LENGTH + 32..VK_HASH_PREFIX_LENGTH + 64]
111            .try_into()
112            .map_err(|_| Groth16Error::GeneralError(Error::InvalidData))?;
113
114        let proof_nonce: [u8; 32] = proof[VK_HASH_PREFIX_LENGTH + 64..VK_HASH_PREFIX_LENGTH + 96]
115            .try_into()
116            .map_err(|_| Groth16Error::GeneralError(Error::InvalidData))?;
117
118        if vk_root != *VK_ROOT_BYTES {
119            return Err(Groth16Error::VkeyRootMismatch);
120        }
121
122        if exit_code != expected_exit_code {
123            return Err(Groth16Error::ExitCodeMismatch);
124        }
125
126        // It is computationally infeasible to find two distinct inputs, one processed with
127        // SHA256 and the other with Blake3, that yield the same hash value.
128        if Self::verify_gnark_proof(
129            &proof[VK_HASH_PREFIX_LENGTH + 96..],
130            &[
131                sp1_vkey_hash,
132                hash_public_inputs(sp1_public_inputs),
133                exit_code,
134                vk_root,
135                proof_nonce,
136            ],
137            groth16_vk,
138        )
139        .is_ok()
140        {
141            return Ok(());
142        }
143
144        Self::verify_gnark_proof(
145            &proof[VK_HASH_PREFIX_LENGTH + 96..],
146            &[
147                sp1_vkey_hash,
148                hash_public_inputs_with_fn(sp1_public_inputs, blake3_hash),
149                exit_code,
150                vk_root,
151                proof_nonce,
152            ],
153            groth16_vk,
154        )
155    }
156
157    /// Verifies a Gnark Groth16 proof using raw byte inputs.
158    ///
159    /// WARNING: if you're verifying an SP1 proof, you should use [`verify`] instead.
160    /// This is a lower-level verification method that works directly with raw bytes rather than
161    /// the SP1 SDK's data structures.
162    ///
163    /// # Arguments
164    ///
165    /// * `proof` - The raw Groth16 proof bytes (without the 4-byte vkey hash prefix)
166    /// * `public_inputs` - The public inputs to the circuit
167    /// * `groth16_vk` - The Groth16 verifying key bytes
168    ///
169    /// # Returns
170    ///
171    /// A [`Result`] containing unit `()` if the proof is valid,
172    /// or a [`Groth16Error`] if verification fails.
173    ///
174    /// # Note
175    ///
176    /// This method expects the raw proof bytes without the 4-byte vkey hash prefix that
177    /// [`verify`] checks. If you have a complete proof with the prefix, use [`verify`] instead.
178    pub fn verify_gnark_proof(
179        proof: &[u8],
180        public_inputs: &[[u8; 32]],
181        groth16_vk: &[u8],
182    ) -> Result<(), Groth16Error> {
183        let proof = load_groth16_proof_from_bytes(proof)?;
184        let groth16_vk = load_groth16_verifying_key_from_bytes(groth16_vk)?;
185
186        let public_inputs = public_inputs
187            .iter()
188            .map(|input| Fr::from_slice(input))
189            .collect::<Result<Vec<_>, _>>()
190            .map_err(|e| Groth16Error::GeneralError(crate::groth16::Error::Field(e)))?;
191
192        verify_groth16_algebraic(&groth16_vk, &proof, &public_inputs)
193    }
194}