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}