sp1_sdk/
proof.rs

1//! # SP1 Proof
2//!
3//! A library of types and functions for SP1 proofs.
4#![allow(missing_docs)]
5
6use std::{fmt::Debug, fs::File, path::Path};
7
8use anyhow::{Context, Result};
9use hashbrown::HashMap;
10use p3_baby_bear::BabyBear;
11use p3_field::{extension::BinomialExtensionField, AbstractField, PrimeField};
12use p3_fri::{FriProof, TwoAdicFriPcsProof};
13use serde::{Deserialize, Serialize};
14use sp1_primitives::io::SP1PublicValues;
15use sp1_prover::{Groth16Bn254Proof, HashableKey, PlonkBn254Proof, SP1ProvingKey};
16use sp1_stark::{
17    septic_digest::SepticDigest, SP1ReduceProof, ShardCommitment, ShardOpenedValues, ShardProof,
18    StarkVerifyingKey,
19};
20
21pub use sp1_stark::{SP1Proof, SP1ProofMode};
22
23/// A proof generated by the SP1 RISC-V zkVM bundled together with the public values and the
24/// version.
25#[derive(Debug, Clone, Serialize, Deserialize)]
26pub struct SP1ProofWithPublicValues {
27    /// The raw proof generated by the SP1 RISC-V zkVM.
28    pub proof: SP1Proof,
29    /// The public values generated by the SP1 RISC-V zkVM.
30    pub public_values: SP1PublicValues,
31    /// The version of the SP1 RISC-V zkVM (not necessary but useful for detecting version
32    /// mismatches).
33    pub sp1_version: String,
34    /// The integrity proof generated by the TEE server.
35    pub tee_proof: Option<Vec<u8>>,
36}
37
38/// The proof generated by the prover network.
39///
40/// Since [`bincode`] is not self describing, it cannot handle "nullable" optional values.
41#[derive(Debug, Clone, Serialize, Deserialize)]
42pub struct ProofFromNetwork {
43    pub proof: SP1Proof,
44    pub public_values: SP1PublicValues,
45    pub sp1_version: String,
46}
47
48impl From<ProofFromNetwork> for SP1ProofWithPublicValues {
49    fn from(value: ProofFromNetwork) -> Self {
50        Self {
51            proof: value.proof,
52            public_values: value.public_values,
53            sp1_version: value.sp1_version,
54            tee_proof: None,
55        }
56    }
57}
58
59impl SP1ProofWithPublicValues {
60    /// Creates a new [`SP1ProofWithPublicValues`] from the proof, public values, and SP1 version.
61    ///
62    /// If the [`tee`] feature is enabled, the proof field is set to none.
63    pub(crate) const fn new(
64        proof: SP1Proof,
65        public_values: SP1PublicValues,
66        sp1_version: String,
67    ) -> Self {
68        Self { proof, public_values, sp1_version, tee_proof: None }
69    }
70
71    /// Saves the proof to a path.
72    pub fn save(&self, path: impl AsRef<Path>) -> Result<()> {
73        bincode::serialize_into(
74            File::create(path.as_ref()).with_context(|| {
75                format!("failed to create file for saving proof: {}", path.as_ref().display())
76            })?,
77            self,
78        )
79        .map_err(Into::into)
80    }
81
82    /// Loads a proof from a path.
83    pub fn load(path: impl AsRef<Path>) -> Result<Self> {
84        // Try to load a [`Self`] from the file.
85        let maybe_this: Result<Self> =
86            bincode::deserialize_from(File::open(path.as_ref()).with_context(|| {
87                format!("failed to open file for loading proof: {}", path.as_ref().display())
88            })?)
89            .map_err(Into::into);
90
91        // This may be a proof from the prover network, which lacks the TEE proof field.
92        match maybe_this {
93            Ok(this) => Ok(this),
94            Err(e) => {
95                // If the file does not contain a [`Self`], try to load a [`ProofFromNetwork`]
96                // instead.
97                let maybe_proof_from_network: Result<ProofFromNetwork> =
98                    bincode::deserialize_from(File::open(path.as_ref()).with_context(|| {
99                        format!(
100                            "failed to open file for loading proof: {}",
101                            path.as_ref().display()
102                        )
103                    })?)
104                    .map_err(Into::into);
105
106                if let Ok(proof_from_network) = maybe_proof_from_network {
107                    // The file contains a [`ProofFromNetwork`], which lacks the TEE proof field.
108                    Ok(proof_from_network.into())
109                } else {
110                    // Return the original error from trying to load a [`Self`].
111                    Err(e)
112                }
113            }
114        }
115    }
116
117    /// The proof in the byte encoding the onchain verifiers accepts for [`SP1ProofMode::Groth16`]
118    /// and [`SP1ProofMode::Plonk`] proofs.
119    ///
120    /// # Details
121    /// The bytes consist of the first four bytes of Groth16/Plonk vkey hash followed by the encoded
122    /// proof, in a form optimized for onchain verification.
123    #[must_use]
124    pub fn bytes(&self) -> Vec<u8> {
125        match &self.proof {
126            SP1Proof::Plonk(plonk_proof) => {
127                // If the proof is empty, then this is a mock proof. The mock SP1 verifier
128                // expects an empty byte array for verification, so return an empty byte array.
129                if plonk_proof.encoded_proof.is_empty() {
130                    return Vec::new();
131                }
132
133                let proof_bytes =
134                    hex::decode(&plonk_proof.encoded_proof).expect("Invalid Plonk proof");
135
136                if let Some(tee_proof) = &self.tee_proof {
137                    return [
138                        tee_proof.clone(),
139                        plonk_proof.plonk_vkey_hash[..4].to_vec(),
140                        proof_bytes,
141                    ]
142                    .concat();
143                }
144
145                [plonk_proof.plonk_vkey_hash[..4].to_vec(), proof_bytes].concat()
146            }
147            SP1Proof::Groth16(groth16_proof) => {
148                // If the proof is empty, then this is a mock proof. The mock SP1 verifier
149                // expects an empty byte array for verification, so return an empty byte array.
150                if groth16_proof.encoded_proof.is_empty() {
151                    return Vec::new();
152                }
153
154                let proof_bytes =
155                    hex::decode(&groth16_proof.encoded_proof).expect("Invalid Groth16 proof");
156
157                if let Some(tee_proof) = &self.tee_proof {
158                    return [
159                        tee_proof.clone(),
160                        groth16_proof.groth16_vkey_hash[..4].to_vec(),
161                        proof_bytes,
162                    ]
163                    .concat();
164                }
165
166                [groth16_proof.groth16_vkey_hash[..4].to_vec(), proof_bytes].concat()
167            }
168            proof => panic!(
169                "Proof type {proof} is not supported for onchain verification. \
170                Only Plonk and Groth16 proofs are verifiable onchain"
171            ),
172        }
173    }
174
175    /// Creates a mock proof for the specified proof mode from the public values.
176    ///
177    /// # Example
178    /// ```rust,no_run
179    /// use sp1_sdk::{
180    ///     Prover, ProverClient, SP1ProofMode, SP1ProofWithPublicValues, SP1Stdin, SP1_CIRCUIT_VERSION,
181    /// };
182    ///
183    /// let elf = &[1, 2, 3];
184    /// let stdin = SP1Stdin::new();
185    ///
186    /// let client = ProverClient::builder().cpu().build();
187    /// let (pk, vk) = client.setup(elf);
188    /// let (public_values, _) = client.execute(&pk.elf, &stdin).run().unwrap();
189    ///
190    /// // Create a mock Plonk proof.
191    /// let mock_proof = SP1ProofWithPublicValues::create_mock_proof(
192    ///     &pk,
193    ///     public_values,
194    ///     SP1ProofMode::Plonk,
195    ///     SP1_CIRCUIT_VERSION,
196    /// );
197    /// ```
198    #[must_use]
199    pub fn create_mock_proof(
200        pk: &SP1ProvingKey,
201        public_values: SP1PublicValues,
202        mode: SP1ProofMode,
203        sp1_version: &str,
204    ) -> Self {
205        let sp1_version = sp1_version.to_string();
206        match mode {
207            SP1ProofMode::Core => SP1ProofWithPublicValues {
208                proof: SP1Proof::Core(vec![]),
209                public_values,
210                sp1_version,
211
212                tee_proof: None,
213            },
214            SP1ProofMode::Compressed => {
215                let shard_proof = ShardProof {
216                    commitment: ShardCommitment {
217                        main_commit: [BabyBear::zero(); 8].into(),
218                        permutation_commit: [BabyBear::zero(); 8].into(),
219                        quotient_commit: [BabyBear::zero(); 8].into(),
220                    },
221                    opened_values: ShardOpenedValues { chips: vec![] },
222                    opening_proof: TwoAdicFriPcsProof {
223                        fri_proof: FriProof {
224                            commit_phase_commits: vec![],
225                            query_proofs: vec![],
226                            final_poly: BinomialExtensionField::default(),
227                            pow_witness: BabyBear::zero(),
228                        },
229                        query_openings: vec![],
230                    },
231                    chip_ordering: HashMap::new(),
232                    public_values: vec![],
233                };
234
235                let reduce_vk = StarkVerifyingKey {
236                    commit: [BabyBear::zero(); 8].into(),
237                    pc_start: BabyBear::zero(),
238                    chip_information: vec![],
239                    chip_ordering: HashMap::new(),
240                    initial_global_cumulative_sum: SepticDigest::zero(),
241                };
242
243                let proof = SP1Proof::Compressed(Box::new(SP1ReduceProof {
244                    vk: reduce_vk,
245                    proof: shard_proof,
246                }));
247
248                SP1ProofWithPublicValues { proof, public_values, sp1_version, tee_proof: None }
249            }
250            SP1ProofMode::Plonk => SP1ProofWithPublicValues {
251                proof: SP1Proof::Plonk(PlonkBn254Proof {
252                    public_inputs: [
253                        pk.vk.hash_bn254().as_canonical_biguint().to_string(),
254                        public_values.hash_bn254().to_string(),
255                    ],
256                    encoded_proof: String::new(),
257                    raw_proof: String::new(),
258                    plonk_vkey_hash: [0; 32],
259                }),
260                public_values,
261                sp1_version,
262
263                tee_proof: None,
264            },
265            SP1ProofMode::Groth16 => SP1ProofWithPublicValues {
266                proof: SP1Proof::Groth16(Groth16Bn254Proof {
267                    public_inputs: [
268                        pk.vk.hash_bn254().as_canonical_biguint().to_string(),
269                        public_values.hash_bn254().to_string(),
270                    ],
271                    encoded_proof: String::new(),
272                    raw_proof: String::new(),
273                    groth16_vkey_hash: [0; 32],
274                }),
275                public_values,
276                sp1_version,
277
278                tee_proof: None,
279            },
280        }
281    }
282}
283
284#[cfg(test)]
285mod tests {
286    #![allow(clippy::print_stdout)]
287
288    use super::*;
289
290    #[test]
291    fn test_plonk_proof_bytes() {
292        let plonk_proof = SP1ProofWithPublicValues {
293            proof: SP1Proof::Plonk(PlonkBn254Proof {
294                encoded_proof: "ab".to_string(),
295                plonk_vkey_hash: [0; 32],
296                public_inputs: [String::new(), String::new()],
297                raw_proof: String::new(),
298            }),
299            public_values: SP1PublicValues::new(),
300            sp1_version: String::new(),
301            tee_proof: None,
302        };
303        let expected_bytes = [vec![0, 0, 0, 0], hex::decode("ab").unwrap()].concat();
304        assert_eq!(plonk_proof.bytes(), expected_bytes);
305    }
306
307    #[test]
308    fn test_groth16_proof_bytes() {
309        let groth16_proof = SP1ProofWithPublicValues {
310            proof: SP1Proof::Groth16(Groth16Bn254Proof {
311                encoded_proof: "ab".to_string(),
312                groth16_vkey_hash: [0; 32],
313                public_inputs: [String::new(), String::new()],
314                raw_proof: String::new(),
315            }),
316            public_values: SP1PublicValues::new(),
317            sp1_version: String::new(),
318            tee_proof: None,
319        };
320        let expected_bytes = [vec![0, 0, 0, 0], hex::decode("ab").unwrap()].concat();
321        assert_eq!(groth16_proof.bytes(), expected_bytes);
322    }
323
324    #[test]
325    fn test_mock_plonk_proof_bytes() {
326        let mock_plonk_proof = SP1ProofWithPublicValues {
327            proof: SP1Proof::Plonk(PlonkBn254Proof {
328                encoded_proof: String::new(),
329                plonk_vkey_hash: [0; 32],
330                public_inputs: [String::new(), String::new()],
331                raw_proof: String::new(),
332            }),
333            public_values: SP1PublicValues::new(),
334            sp1_version: String::new(),
335            tee_proof: None,
336        };
337        assert_eq!(mock_plonk_proof.bytes(), Vec::<u8>::new());
338    }
339
340    #[test]
341    fn test_mock_groth16_proof_bytes() {
342        let mock_groth16_proof = SP1ProofWithPublicValues {
343            proof: SP1Proof::Groth16(Groth16Bn254Proof {
344                encoded_proof: String::new(),
345                groth16_vkey_hash: [0; 32],
346                public_inputs: [String::new(), String::new()],
347                raw_proof: String::new(),
348            }),
349            public_values: SP1PublicValues::new(),
350            sp1_version: String::new(),
351            tee_proof: None,
352        };
353        assert_eq!(mock_groth16_proof.bytes(), Vec::<u8>::new());
354    }
355
356    #[test]
357    #[should_panic(
358        expected = "Proof type Core is not supported for onchain verification. Only Plonk and Groth16 proofs are verifiable onchain"
359    )]
360    fn test_core_proof_bytes_unimplemented() {
361        let core_proof = SP1ProofWithPublicValues {
362            proof: SP1Proof::Core(vec![]),
363            public_values: SP1PublicValues::new(),
364            sp1_version: String::new(),
365            tee_proof: None,
366        };
367        println!("{:?}", core_proof.bytes());
368    }
369
370    #[test]
371    fn test_deser_backwards_compat() {
372        let round_trip = SP1ProofWithPublicValues {
373            proof: SP1Proof::Core(vec![]),
374            public_values: SP1PublicValues::new(),
375            sp1_version: String::new(),
376            tee_proof: None,
377        };
378
379        let round_trip_bytes = bincode::serialize(&round_trip).unwrap();
380
381        bincode::deserialize::<SP1ProofWithPublicValues>(&round_trip_bytes).unwrap();
382
383        let _ = ProofFromNetwork {
384            proof: SP1Proof::Core(vec![]),
385            public_values: SP1PublicValues::new(),
386            sp1_version: String::new(),
387        };
388
389        let _ = bincode::deserialize::<ProofFromNetwork>(&round_trip_bytes).unwrap();
390    }
391
392    #[test]
393    fn test_round_trip_proof_save_load() {
394        use crate::Prover;
395
396        let prover = crate::CpuProver::new();
397        let (pk, _) = prover.setup(test_artifacts::FIBONACCI_BLAKE3_ELF);
398        let proof = prover.prove(&pk, &crate::SP1Stdin::new()).compressed().run().unwrap();
399
400        // Verify the original proof
401        prover.verify(&proof, &pk.vk).unwrap();
402
403        let temp_dir = tempfile::tempdir().unwrap();
404        let path = temp_dir.path().join("proof.bin");
405        std::fs::File::create(&path).unwrap();
406        proof.save(&path).unwrap();
407
408        let proof_loaded = SP1ProofWithPublicValues::load(&path).unwrap();
409
410        // Verify the loaded proof
411        prover.verify(&proof_loaded, &pk.vk).unwrap();
412    }
413}