Skip to main content

sp1_sdk/
proof.rs

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