1#![allow(missing_docs)]
5#![allow(clippy::double_parens)] use 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
15pub use sp1_verifier::{ProofFromNetwork, SP1Proof, SP1ProofMode};
18
19pub(crate) fn verify_mock_public_inputs(
23 vkey: &SP1VerifyingKey,
24 public_values: &SP1PublicValues,
25 public_inputs: &[String; 5],
26) -> Result<()> {
27 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 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#[derive(Debug, Clone, Serialize, Deserialize)]
53pub struct SP1ProofWithPublicValues {
54 pub proof: SP1Proof,
56 pub public_values: SP1PublicValues,
58 pub sp1_version: String,
61 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 #[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 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 pub fn load(path: impl AsRef<Path>) -> Result<Self> {
98 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 match maybe_this {
107 Ok(this) => Ok(this),
108 Err(e) => {
109 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 Ok(proof_from_network.into())
123 } else {
124 Err(e)
126 }
127 }
128 }
129 }
130
131 #[must_use]
138 pub fn bytes(&self) -> Vec<u8> {
139 match &self.proof {
140 SP1Proof::Plonk(plonk_proof) => {
141 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 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 #[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 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 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(), "0".to_string(), "0".to_string(), ],
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 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(), "0".to_string(), "0".to_string(), ],
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 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 prover.verify(&proof_loaded, &pk.vk, None).unwrap();
454 }
455}