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