sp1_prover/
verify.rs

1use std::{borrow::Borrow, path::Path, str::FromStr};
2
3use anyhow::Result;
4use num_bigint::BigUint;
5use p3_baby_bear::BabyBear;
6use p3_field::{AbstractField, PrimeField};
7use sp1_core_executor::subproof::SubproofVerifier;
8use sp1_core_machine::cpu::MAX_CPU_LOG_DEGREE;
9use sp1_primitives::{
10    consts::WORD_SIZE,
11    io::{blake3_hash, SP1PublicValues},
12};
13
14use sp1_recursion_circuit::machine::RootPublicValues;
15use sp1_recursion_core::{air::RecursionPublicValues, stark::BabyBearPoseidon2Outer};
16use sp1_recursion_gnark_ffi::{
17    Groth16Bn254Proof, Groth16Bn254Prover, PlonkBn254Proof, PlonkBn254Prover,
18};
19use sp1_stark::{
20    air::{PublicValues, POSEIDON_NUM_WORDS, PV_DIGEST_NUM_WORDS},
21    baby_bear_poseidon2::BabyBearPoseidon2,
22    MachineProof, MachineProver, MachineVerificationError, SP1ReduceProof, StarkGenericConfig,
23    Word,
24};
25use thiserror::Error;
26
27use crate::{
28    components::SP1ProverComponents,
29    utils::{is_recursion_public_values_valid, is_root_public_values_valid},
30    CoreSC, HashableKey, OuterSC, SP1CoreProofData, SP1Prover, SP1VerifyingKey,
31};
32
33#[derive(Error, Debug)]
34pub enum PlonkVerificationError {
35    #[error(
36        "the verifying key does not match the inner plonk bn254 proof's committed verifying key"
37    )]
38    InvalidVerificationKey,
39    #[error(
40        "the public values in the sp1 proof do not match the public values in the inner plonk bn254 proof"
41    )]
42    InvalidPublicValues,
43}
44
45#[derive(Error, Debug)]
46pub enum Groth16VerificationError {
47    #[error(
48        "the verifying key does not match the inner groth16 bn254 proof's committed verifying key"
49    )]
50    InvalidVerificationKey,
51    #[error(
52        "the public values in the sp1 proof do not match the public values in the inner groth16 bn254 proof"
53    )]
54    InvalidPublicValues,
55}
56
57impl<C: SP1ProverComponents> SP1Prover<C> {
58    /// Verify a core proof by verifying the shards, verifying lookup bus, verifying that the
59    /// shards are contiguous and complete.
60    pub fn verify(
61        &self,
62        proof: &SP1CoreProofData,
63        vk: &SP1VerifyingKey,
64    ) -> Result<(), MachineVerificationError<CoreSC>> {
65        // The proof should not be empty.
66        if proof.0.is_empty() {
67            return Err(MachineVerificationError::EmptyProof);
68        }
69
70        // First shard has a "CPU" constraint.
71        //
72        // Check that the first shard has a "CPU".
73        // SAFETY: The proof is already checked to not be empty.
74        let first_shard = proof.0.first().unwrap();
75        if !first_shard.contains_cpu() {
76            return Err(MachineVerificationError::MissingCpuInFirstShard);
77        }
78
79        // CPU log degree bound constraints.
80        //
81        // Check that the CPU log degree does not exceed `MAX_CPU_LOG_DEGREE`. This is to ensure
82        // that the lookup argument's multiplicities do not overflow.
83        for shard_proof in proof.0.iter() {
84            if shard_proof.contains_cpu() {
85                let log_degree_cpu = shard_proof.log_degree_cpu();
86                if log_degree_cpu > MAX_CPU_LOG_DEGREE {
87                    return Err(MachineVerificationError::CpuLogDegreeTooLarge(log_degree_cpu));
88                }
89            }
90        }
91
92        // Shard constraints.
93        //
94        // Initialization:
95        // - Shard should start at one.
96        //
97        // Transition:
98        // - Shard should increment by one for each shard.
99        let mut current_shard = BabyBear::zero();
100        for shard_proof in proof.0.iter() {
101            let public_values: &PublicValues<Word<_>, _> =
102                shard_proof.public_values.as_slice().borrow();
103            current_shard += BabyBear::one();
104            if public_values.shard != current_shard {
105                return Err(MachineVerificationError::InvalidPublicValues(
106                    "shard index should be the previous shard index + 1 and start at 1",
107                ));
108            }
109        }
110
111        // Execution shard constraints.
112        //
113        // Initialization:
114        // - Execution shard should start at one.
115        //
116        // Transition:
117        // - Execution shard should increment by one for each shard with "CPU".
118        // - Execution shard should stay the same for non-CPU shards.
119        // - For the other shards, execution shard does not matter.
120        let mut current_execution_shard = BabyBear::zero();
121        for shard_proof in proof.0.iter() {
122            let public_values: &PublicValues<Word<_>, _> =
123                shard_proof.public_values.as_slice().borrow();
124            if shard_proof.contains_cpu() {
125                current_execution_shard += BabyBear::one();
126                if public_values.execution_shard != current_execution_shard {
127                    return Err(MachineVerificationError::InvalidPublicValues(
128                        "execution shard index should be the previous execution shard index + 1 if cpu exists and start at 1",
129                    ));
130                }
131            }
132        }
133
134        // Program counter constraints.
135        //
136        // Initialization:
137        // - `start_pc` should start as `vk.start_pc`.
138        //
139        // Transition:
140        // - `next_pc` of the previous shard should equal `start_pc`.
141        // - If it's not a shard with "CPU", then `start_pc` equals `next_pc`.
142        // - If it's a shard with "CPU", then `start_pc` should never equal zero.
143        //
144        // Finalization:
145        // - `next_pc` should equal zero.
146        let mut prev_next_pc = BabyBear::zero();
147        for (i, shard_proof) in proof.0.iter().enumerate() {
148            let public_values: &PublicValues<Word<_>, _> =
149                shard_proof.public_values.as_slice().borrow();
150            if i == 0 && public_values.start_pc != vk.vk.pc_start {
151                return Err(MachineVerificationError::InvalidPublicValues(
152                    "start_pc != vk.start_pc: program counter should start at vk.start_pc",
153                ));
154            } else if i != 0 && public_values.start_pc != prev_next_pc {
155                return Err(MachineVerificationError::InvalidPublicValues(
156                    "start_pc != next_pc_prev: start_pc should equal next_pc_prev for all shards",
157                ));
158            } else if !shard_proof.contains_cpu() && public_values.start_pc != public_values.next_pc
159            {
160                return Err(MachineVerificationError::InvalidPublicValues(
161                    "start_pc != next_pc: start_pc should equal next_pc for non-cpu shards",
162                ));
163            } else if shard_proof.contains_cpu() && public_values.start_pc == BabyBear::zero() {
164                return Err(MachineVerificationError::InvalidPublicValues(
165                    "start_pc == 0: execution should never start at halted state",
166                ));
167            } else if i == proof.0.len() - 1 && public_values.next_pc != BabyBear::zero() {
168                return Err(MachineVerificationError::InvalidPublicValues(
169                    "next_pc != 0: execution should have halted",
170                ));
171            }
172            prev_next_pc = public_values.next_pc;
173        }
174
175        // Exit code constraints.
176        //
177        // - In every shard, the exit code should be zero.
178        for shard_proof in proof.0.iter() {
179            let public_values: &PublicValues<Word<_>, _> =
180                shard_proof.public_values.as_slice().borrow();
181            if public_values.exit_code != BabyBear::zero() {
182                return Err(MachineVerificationError::InvalidPublicValues(
183                    "exit_code != 0: exit code should be zero for all shards",
184                ));
185            }
186        }
187
188        // Memory initialization & finalization constraints.
189        //
190        // Initialization:
191        // - `previous_init_addr_bits` should be zero.
192        // - `previous_finalize_addr_bits` should be zero.
193        //
194        // Transition:
195        // - For all shards, `previous_init_addr_bits` should equal `last_init_addr_bits` of the
196        //   previous shard.
197        // - For all shards, `previous_finalize_addr_bits` should equal `last_finalize_addr_bits` of
198        //   the previous shard.
199        // - For shards without "MemoryInit", `previous_init_addr_bits` should equal
200        //   `last_init_addr_bits`.
201        // - For shards without "MemoryFinalize", `previous_finalize_addr_bits` should equal
202        //   `last_finalize_addr_bits`.
203        let mut last_init_addr_bits_prev = [BabyBear::zero(); 32];
204        let mut last_finalize_addr_bits_prev = [BabyBear::zero(); 32];
205        for shard_proof in proof.0.iter() {
206            let public_values: &PublicValues<Word<_>, _> =
207                shard_proof.public_values.as_slice().borrow();
208            if public_values.previous_init_addr_bits != last_init_addr_bits_prev {
209                return Err(MachineVerificationError::InvalidPublicValues(
210                    "previous_init_addr_bits != last_init_addr_bits_prev",
211                ));
212            } else if public_values.previous_finalize_addr_bits != last_finalize_addr_bits_prev {
213                return Err(MachineVerificationError::InvalidPublicValues(
214                    "last_init_addr_bits != last_finalize_addr_bits_prev",
215                ));
216            } else if !shard_proof.contains_global_memory_init() &&
217                public_values.previous_init_addr_bits != public_values.last_init_addr_bits
218            {
219                return Err(MachineVerificationError::InvalidPublicValues(
220                    "previous_init_addr_bits != last_init_addr_bits",
221                ));
222            } else if !shard_proof.contains_global_memory_finalize() &&
223                public_values.previous_finalize_addr_bits !=
224                    public_values.last_finalize_addr_bits
225            {
226                return Err(MachineVerificationError::InvalidPublicValues(
227                    "previous_finalize_addr_bits != last_finalize_addr_bits",
228                ));
229            }
230            last_init_addr_bits_prev = public_values.last_init_addr_bits;
231            last_finalize_addr_bits_prev = public_values.last_finalize_addr_bits;
232        }
233
234        // Digest constraints.
235        //
236        // Initialization:
237        // - `committed_value_digest` should be zero.
238        // - `deferred_proofs_digest` should be zero.
239        //
240        // Transition:
241        // - If `committed_value_digest_prev` is not zero, then `committed_value_digest` should
242        //   equal
243        //  `committed_value_digest_prev`. Otherwise, `committed_value_digest` should equal zero.
244        // - If `deferred_proofs_digest_prev` is not zero, then `deferred_proofs_digest` should
245        //   equal
246        //  `deferred_proofs_digest_prev`. Otherwise, `deferred_proofs_digest` should equal zero.
247        // - If it's not a shard with "CPU", then `committed_value_digest` should not change from
248        //   the
249        //  previous shard.
250        // - If it's not a shard with "CPU", then `deferred_proofs_digest` should not change from
251        //   the
252        //  previous shard.
253        let zero_committed_value_digest =
254            [Word([BabyBear::zero(); WORD_SIZE]); PV_DIGEST_NUM_WORDS];
255        let zero_deferred_proofs_digest = [BabyBear::zero(); POSEIDON_NUM_WORDS];
256        let mut committed_value_digest_prev = zero_committed_value_digest;
257        let mut deferred_proofs_digest_prev = zero_deferred_proofs_digest;
258        for shard_proof in proof.0.iter() {
259            let public_values: &PublicValues<Word<_>, _> =
260                shard_proof.public_values.as_slice().borrow();
261            if committed_value_digest_prev != zero_committed_value_digest &&
262                public_values.committed_value_digest != committed_value_digest_prev
263            {
264                return Err(MachineVerificationError::InvalidPublicValues(
265                    "committed_value_digest != committed_value_digest_prev",
266                ));
267            } else if deferred_proofs_digest_prev != zero_deferred_proofs_digest &&
268                public_values.deferred_proofs_digest != deferred_proofs_digest_prev
269            {
270                return Err(MachineVerificationError::InvalidPublicValues(
271                    "deferred_proofs_digest != deferred_proofs_digest_prev",
272                ));
273            } else if !shard_proof.contains_cpu() &&
274                public_values.committed_value_digest != committed_value_digest_prev
275            {
276                return Err(MachineVerificationError::InvalidPublicValues(
277                    "committed_value_digest != committed_value_digest_prev",
278                ));
279            } else if !shard_proof.contains_cpu() &&
280                public_values.deferred_proofs_digest != deferred_proofs_digest_prev
281            {
282                return Err(MachineVerificationError::InvalidPublicValues(
283                    "deferred_proofs_digest != deferred_proofs_digest_prev",
284                ));
285            }
286            committed_value_digest_prev = public_values.committed_value_digest;
287            deferred_proofs_digest_prev = public_values.deferred_proofs_digest;
288        }
289
290        // Verify that the number of shards is not too large.
291        if proof.0.len() >= 1 << 16 {
292            return Err(MachineVerificationError::TooManyShards);
293        }
294
295        // Verify the shard proof.
296        let mut challenger = self.core_prover.config().challenger();
297        let machine_proof = MachineProof { shard_proofs: proof.0.to_vec() };
298        self.core_prover.machine().verify(&vk.vk, &machine_proof, &mut challenger)?;
299
300        Ok(())
301    }
302
303    /// Verify a compressed proof.
304    pub fn verify_compressed(
305        &self,
306        proof: &SP1ReduceProof<BabyBearPoseidon2>,
307        vk: &SP1VerifyingKey,
308    ) -> Result<(), MachineVerificationError<CoreSC>> {
309        let SP1ReduceProof { vk: compress_vk, proof } = proof;
310        let mut challenger = self.compress_prover.config().challenger();
311        let machine_proof = MachineProof { shard_proofs: vec![proof.clone()] };
312        self.compress_prover.machine().verify(compress_vk, &machine_proof, &mut challenger)?;
313
314        // Validate public values
315        let public_values: &RecursionPublicValues<_> = proof.public_values.as_slice().borrow();
316
317        if !is_recursion_public_values_valid(self.compress_prover.machine().config(), public_values)
318        {
319            return Err(MachineVerificationError::InvalidPublicValues(
320                "recursion public values are invalid",
321            ));
322        }
323
324        if public_values.vk_root != self.recursion_vk_root {
325            return Err(MachineVerificationError::InvalidPublicValues("vk_root mismatch"));
326        }
327
328        if self.vk_verification && !self.recursion_vk_map.contains_key(&compress_vk.hash_babybear())
329        {
330            return Err(MachineVerificationError::InvalidVerificationKey);
331        }
332
333        // `is_complete` should be 1. In the reduce program, this ensures that the proof is fully
334        // reduced.
335        if public_values.is_complete != BabyBear::one() {
336            return Err(MachineVerificationError::InvalidPublicValues("is_complete is not 1"));
337        }
338
339        // Verify that the proof is for the sp1 vkey we are expecting.
340        let vkey_hash = vk.hash_babybear();
341        if public_values.sp1_vk_digest != vkey_hash {
342            return Err(MachineVerificationError::InvalidPublicValues("sp1 vk hash mismatch"));
343        }
344
345        Ok(())
346    }
347
348    /// Verify a shrink proof.
349    pub fn verify_shrink(
350        &self,
351        proof: &SP1ReduceProof<BabyBearPoseidon2>,
352        vk: &SP1VerifyingKey,
353    ) -> Result<(), MachineVerificationError<CoreSC>> {
354        let mut challenger = self.shrink_prover.config().challenger();
355        let machine_proof = MachineProof { shard_proofs: vec![proof.proof.clone()] };
356        self.shrink_prover.machine().verify(&proof.vk, &machine_proof, &mut challenger)?;
357
358        // Validate public values
359        let public_values: &RecursionPublicValues<_> =
360            proof.proof.public_values.as_slice().borrow();
361        if !is_recursion_public_values_valid(self.compress_prover.machine().config(), public_values)
362        {
363            return Err(MachineVerificationError::InvalidPublicValues(
364                "recursion public values are invalid",
365            ));
366        }
367        if public_values.vk_root != self.recursion_vk_root {
368            return Err(MachineVerificationError::InvalidPublicValues("vk_root mismatch"));
369        }
370
371        if self.vk_verification && !self.recursion_vk_map.contains_key(&proof.vk.hash_babybear()) {
372            return Err(MachineVerificationError::InvalidVerificationKey);
373        }
374
375        // `is_complete` should be 1. In the reduce program, this ensures that the proof is fully
376        // reduced.
377        if public_values.is_complete != BabyBear::one() {
378            return Err(MachineVerificationError::InvalidPublicValues("is_complete is not 1"));
379        }
380
381        // Verify that the proof is for the sp1 vkey we are expecting.
382        let vkey_hash = vk.hash_babybear();
383        if public_values.sp1_vk_digest != vkey_hash {
384            return Err(MachineVerificationError::InvalidPublicValues("sp1 vk hash mismatch"));
385        }
386
387        Ok(())
388    }
389
390    /// Verify a wrap bn254 proof.
391    pub fn verify_wrap_bn254(
392        &self,
393        proof: &SP1ReduceProof<BabyBearPoseidon2Outer>,
394        vk: &SP1VerifyingKey,
395    ) -> Result<(), MachineVerificationError<OuterSC>> {
396        let mut challenger = self.wrap_prover.config().challenger();
397        let machine_proof = MachineProof { shard_proofs: vec![proof.proof.clone()] };
398
399        let wrap_vk = self.wrap_vk.get().expect("Wrap verifier key not set");
400        self.wrap_prover.machine().verify(wrap_vk, &machine_proof, &mut challenger)?;
401
402        // Validate public values
403        let public_values: &RootPublicValues<_> = proof.proof.public_values.as_slice().borrow();
404        if !is_root_public_values_valid(self.shrink_prover.machine().config(), public_values) {
405            return Err(MachineVerificationError::InvalidPublicValues(
406                "root public values are invalid",
407            ));
408        }
409
410        // Verify that the proof is for the sp1 vkey we are expecting.
411        let vkey_hash = vk.hash_babybear();
412        if *public_values.sp1_vk_digest() != vkey_hash {
413            return Err(MachineVerificationError::InvalidPublicValues("sp1 vk hash mismatch"));
414        }
415
416        Ok(())
417    }
418
419    /// Verifies a PLONK proof using the circuit artifacts in the build directory.
420    pub fn verify_plonk_bn254(
421        &self,
422        proof: &PlonkBn254Proof,
423        vk: &SP1VerifyingKey,
424        public_values: &SP1PublicValues,
425        build_dir: &Path,
426    ) -> Result<()> {
427        let prover = PlonkBn254Prover::new();
428
429        let vkey_hash = BigUint::from_str(&proof.public_inputs[0])?;
430        let committed_values_digest = BigUint::from_str(&proof.public_inputs[1])?;
431
432        // Verify the proof with the corresponding public inputs.
433        prover.verify(proof, &vkey_hash, &committed_values_digest, build_dir)?;
434
435        verify_plonk_bn254_public_inputs(vk, public_values, &proof.public_inputs)?;
436
437        Ok(())
438    }
439
440    /// Verifies a Groth16 proof using the circuit artifacts in the build directory.
441    pub fn verify_groth16_bn254(
442        &self,
443        proof: &Groth16Bn254Proof,
444        vk: &SP1VerifyingKey,
445        public_values: &SP1PublicValues,
446        build_dir: &Path,
447    ) -> Result<()> {
448        let prover = Groth16Bn254Prover::new();
449
450        let vkey_hash = BigUint::from_str(&proof.public_inputs[0])?;
451        let committed_values_digest = BigUint::from_str(&proof.public_inputs[1])?;
452
453        // Verify the proof with the corresponding public inputs.
454        prover.verify(proof, &vkey_hash, &committed_values_digest, build_dir)?;
455
456        verify_groth16_bn254_public_inputs(vk, public_values, &proof.public_inputs)?;
457
458        Ok(())
459    }
460}
461
462/// Verify the vk_hash and public_values_hash in the public inputs of the PlonkBn254Proof match the
463/// expected values.
464pub fn verify_plonk_bn254_public_inputs(
465    vk: &SP1VerifyingKey,
466    public_values: &SP1PublicValues,
467    plonk_bn254_public_inputs: &[String],
468) -> Result<()> {
469    let expected_vk_hash = BigUint::from_str(&plonk_bn254_public_inputs[0])?;
470    let expected_public_values_hash = BigUint::from_str(&plonk_bn254_public_inputs[1])?;
471
472    let vk_hash = vk.hash_bn254().as_canonical_biguint();
473    if vk_hash != expected_vk_hash {
474        return Err(PlonkVerificationError::InvalidVerificationKey.into());
475    }
476
477    verify_public_values(public_values, expected_public_values_hash)?;
478
479    Ok(())
480}
481
482/// Verify the vk_hash and public_values_hash in the public inputs of the Groth16Bn254Proof match
483/// the expected values.
484pub fn verify_groth16_bn254_public_inputs(
485    vk: &SP1VerifyingKey,
486    public_values: &SP1PublicValues,
487    groth16_bn254_public_inputs: &[String],
488) -> Result<()> {
489    let expected_vk_hash = BigUint::from_str(&groth16_bn254_public_inputs[0])?;
490    let expected_public_values_hash = BigUint::from_str(&groth16_bn254_public_inputs[1])?;
491
492    let vk_hash = vk.hash_bn254().as_canonical_biguint();
493    if vk_hash != expected_vk_hash {
494        return Err(Groth16VerificationError::InvalidVerificationKey.into());
495    }
496
497    verify_public_values(public_values, expected_public_values_hash)?;
498
499    Ok(())
500}
501
502/// In SP1, a proof's public values can either be hashed with SHA2 or Blake3. In SP1 V4, there is no
503/// metadata attached to the proof about which hasher function was used for public values hashing.
504/// Instead, when verifying the proof, the public values are hashed with SHA2 and Blake3, and
505/// if either matches the `expected_public_values_hash`, the verification is successful.
506///
507/// The security for this verification in SP1 V4 derives from the fact that both SHA2 and Blake3 are
508/// designed to be collision resistant. It is computationally infeasible to find an input i1 for
509/// SHA256 and an input i2 for Blake3 that the same hash value. Doing so would require breaking both
510/// algorithms simultaneously.
511fn verify_public_values(
512    public_values: &SP1PublicValues,
513    expected_public_values_hash: BigUint,
514) -> Result<()> {
515    // First, check if the public values are hashed with SHA256. If that fails, attempt hashing with
516    // Blake3. If neither match, return an error.
517    let sha256_public_values_hash = public_values.hash_bn254();
518    if sha256_public_values_hash != expected_public_values_hash {
519        let blake3_public_values_hash = public_values.hash_bn254_with_fn(blake3_hash);
520        if blake3_public_values_hash != expected_public_values_hash {
521            return Err(Groth16VerificationError::InvalidPublicValues.into());
522        }
523    }
524
525    Ok(())
526}
527
528impl<C: SP1ProverComponents> SubproofVerifier for SP1Prover<C> {
529    fn verify_deferred_proof(
530        &self,
531        proof: &sp1_core_machine::reduce::SP1ReduceProof<BabyBearPoseidon2>,
532        vk: &sp1_stark::StarkVerifyingKey<BabyBearPoseidon2>,
533        vk_hash: [u32; 8],
534        committed_value_digest: [u32; 8],
535    ) -> Result<(), MachineVerificationError<BabyBearPoseidon2>> {
536        // Check that the vk hash matches the vk hash from the input.
537        if vk.hash_u32() != vk_hash {
538            return Err(MachineVerificationError::InvalidPublicValues(
539                "vk hash from syscall does not match vkey from input",
540            ));
541        }
542        // Check that proof is valid.
543        self.verify_compressed(
544            &SP1ReduceProof { vk: proof.vk.clone(), proof: proof.proof.clone() },
545            &SP1VerifyingKey { vk: vk.clone() },
546        )?;
547        // Check that the committed value digest matches the one from syscall
548        let public_values: &RecursionPublicValues<_> =
549            proof.proof.public_values.as_slice().borrow();
550        if public_values.vk_root != self.recursion_vk_root {
551            return Err(MachineVerificationError::InvalidPublicValues("vk_root mismatch"));
552        }
553
554        for (i, word) in public_values.committed_value_digest.iter().enumerate() {
555            if *word != committed_value_digest[i].into() {
556                return Err(MachineVerificationError::InvalidPublicValues(
557                    "committed_value_digest does not match",
558                ));
559            }
560        }
561        Ok(())
562    }
563}