sp1_verifier/groth16/
mod.rs

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