Skip to main content

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}