Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 83 additions & 5 deletions crates/sdk/src/blocking/mock/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ use crate::{
cpu::CPUProverError,
prover::{BaseProveRequest, ProveRequest, Prover},
},
proof::verify_mock_public_inputs,
proof::{mock_status_code, verify_mock_public_inputs},
SP1Proof, SP1ProofWithPublicValues, SP1ProvingKey, SP1VerificationError, StatusCode,
};

Expand Down Expand Up @@ -79,8 +79,19 @@ impl Prover for MockProver {
&self,
proof: &SP1ProofWithPublicValues,
vkey: &SP1VerifyingKey,
_status_code: Option<StatusCode>,
status_code: Option<StatusCode>,
) -> Result<(), SP1VerificationError> {
let status_code = status_code.unwrap_or(StatusCode::SUCCESS);
let actual_status_code = mock_status_code(proof).map_err(|err| match &proof.proof {
SP1Proof::Core(_) => SP1VerificationError::Core(err),
SP1Proof::Compressed(_) => SP1VerificationError::Recursion(err),
SP1Proof::Plonk(_) => SP1VerificationError::Plonk(err),
SP1Proof::Groth16(_) => SP1VerificationError::Groth16(err),
})?;
if !status_code.is_accepted_code(actual_status_code) {
return Err(SP1VerificationError::UnexpectedExitCode(actual_status_code));
}

match &proof.proof {
SP1Proof::Plonk(PlonkBn254Proof { public_inputs, .. }) => {
// Verify the mock Plonk proof by checking public inputs match.
Expand Down Expand Up @@ -112,12 +123,17 @@ impl<'a> ProveRequest<'a, MockProver> for MockProveRequest<'a> {
tracing::info!(mode = ?mode, "generating mock proof");
let mut req = prover.execute(pk.elf.clone(), stdin);
req.context_builder = context_builder;
let (public_values, _) = req.run()?;
Ok(SP1ProofWithPublicValues::create_mock_proof(
let (public_values, report) = req.run()?;
let status_code =
u32::try_from(report.exit_code).ok().and_then(StatusCode::new).ok_or_else(|| {
anyhow::anyhow!("unsupported mock execution exit code: {}", report.exit_code)
})?;
Ok(SP1ProofWithPublicValues::create_mock_proof_with_status(
&pk.vk,
public_values,
mode,
prover.version(),
status_code,
))
}
}
Expand All @@ -127,7 +143,7 @@ mod tests {
use crate::{
blocking::prover::{ProveRequest, Prover},
utils::setup_logger,
SP1Stdin,
SP1Stdin, StatusCode,
};

use super::MockProver;
Expand Down Expand Up @@ -281,4 +297,66 @@ mod tests {
let result = prover.verify(&proof, &pk.vk, None);
assert!(result.is_err(), "Verification should fail with tampered public values");
}

#[test]
fn test_mock_proof_panic_exit_status() {
setup_logger();
let prover = MockProver::new();
let pk = prover.setup(test_artifacts::PANIC_ELF).expect("failed to setup proving key");
let stdin = SP1Stdin::new();

let modes = [
crate::SP1ProofMode::Core,
crate::SP1ProofMode::Compressed,
crate::SP1ProofMode::Plonk,
crate::SP1ProofMode::Groth16,
];

for mode in modes {
let proof = prover
.prove(&pk, stdin.clone())
.mode(mode)
.expected_exit_code(StatusCode::PANIC)
.run()
.expect("failed to create panic mock proof");

prover
.verify(&proof, &pk.vk, StatusCode::new(1))
.expect("failed to verify mock proof with panic exit code");

let result = prover.verify(&proof, &pk.vk, None);
assert!(matches!(result, Err(crate::SP1VerificationError::UnexpectedExitCode(1))));

let result = prover.verify(&proof, &pk.vk, StatusCode::new(0));
assert!(matches!(result, Err(crate::SP1VerificationError::UnexpectedExitCode(1))));
}
}

#[test]
fn test_mock_proof_success_exit_status_rejects_panic_expectation() {
setup_logger();
let prover = MockProver::new();
let pk = prover.setup(test_artifacts::FIBONACCI_ELF).expect("failed to setup proving key");
let stdin = SP1Stdin::new();

let modes = [
crate::SP1ProofMode::Core,
crate::SP1ProofMode::Compressed,
crate::SP1ProofMode::Plonk,
crate::SP1ProofMode::Groth16,
];

for mode in modes {
let proof = prover
.prove(&pk, stdin.clone())
.mode(mode)
.run()
.expect("failed to create success mock proof");

prover.verify(&proof, &pk.vk, None).expect("failed to verify success mock proof");

let result = prover.verify(&proof, &pk.vk, StatusCode::new(1));
assert!(matches!(result, Err(crate::SP1VerificationError::UnexpectedExitCode(0))));
}
}
}
96 changes: 90 additions & 6 deletions crates/sdk/src/mock/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use sp1_prover::{
};

use crate::{
proof::verify_mock_public_inputs,
proof::{mock_status_code, verify_mock_public_inputs},
prover::{BaseProveRequest, ProveRequest},
Prover, SP1Proof, SP1ProofWithPublicValues, SP1ProvingKey, SP1VerificationError, StatusCode,
};
Expand Down Expand Up @@ -89,8 +89,19 @@ impl Prover for MockProver {
&self,
proof: &SP1ProofWithPublicValues,
vkey: &SP1VerifyingKey,
_status_code: Option<StatusCode>,
status_code: Option<StatusCode>,
) -> Result<(), SP1VerificationError> {
let status_code = status_code.unwrap_or(StatusCode::SUCCESS);
let actual_status_code = mock_status_code(proof).map_err(|err| match &proof.proof {
SP1Proof::Core(_) => SP1VerificationError::Core(err),
SP1Proof::Compressed(_) => SP1VerificationError::Recursion(err),
SP1Proof::Plonk(_) => SP1VerificationError::Plonk(err),
SP1Proof::Groth16(_) => SP1VerificationError::Groth16(err),
})?;
if !status_code.is_accepted_code(actual_status_code) {
return Err(SP1VerificationError::UnexpectedExitCode(actual_status_code));
}

match &proof.proof {
SP1Proof::Plonk(PlonkBn254Proof { public_inputs, .. }) => {
// Verify the mock Plonk proof by checking public inputs match.
Expand Down Expand Up @@ -134,21 +145,30 @@ impl<'a> IntoFuture for MockProveRequest<'a> {
req.context_builder = context_builder;

// Spawn blocking under the hood.
let (public_values, _) = req.await?;

Ok(SP1ProofWithPublicValues::create_mock_proof(
let (public_values, report) = req.await?;
let status_code = u32::try_from(report.exit_code)
.ok()
.and_then(StatusCode::new)
.ok_or_else(|| {
anyhow::anyhow!("unsupported mock execution exit code: {}", report.exit_code)
})?;

Ok(SP1ProofWithPublicValues::create_mock_proof_with_status(
&pk.vk,
public_values,
mode,
prover.version(),
status_code,
))
})
}
}

#[cfg(test)]
mod tests {
use crate::{prover::ProveRequest, utils::setup_logger, MockProver, Prover, SP1Stdin};
use crate::{
prover::ProveRequest, utils::setup_logger, MockProver, Prover, SP1Stdin, StatusCode,
};

/// Test mock proof creation and verification for all proof types.
#[tokio::test]
Expand Down Expand Up @@ -311,4 +331,68 @@ mod tests {
let result = prover.verify(&proof, &pk.vk, None);
assert!(result.is_err(), "Verification should fail with tampered public values");
}

#[tokio::test]
async fn test_mock_proof_panic_exit_status() {
setup_logger();
let prover = MockProver::new().await;
let pk =
prover.setup(test_artifacts::PANIC_ELF).await.expect("failed to setup proving key");
let stdin = SP1Stdin::new();

let modes = [
crate::SP1ProofMode::Core,
crate::SP1ProofMode::Compressed,
crate::SP1ProofMode::Plonk,
crate::SP1ProofMode::Groth16,
];

for mode in modes {
let proof = prover
.prove(&pk, stdin.clone())
.mode(mode)
.expected_exit_code(StatusCode::PANIC)
.await
.expect("failed to create panic mock proof");

prover
.verify(&proof, &pk.vk, StatusCode::new(1))
.expect("failed to verify mock proof with panic exit code");

let result = prover.verify(&proof, &pk.vk, None);
assert!(matches!(result, Err(crate::SP1VerificationError::UnexpectedExitCode(1))));

let result = prover.verify(&proof, &pk.vk, StatusCode::new(0));
assert!(matches!(result, Err(crate::SP1VerificationError::UnexpectedExitCode(1))));
}
}

#[tokio::test]
async fn test_mock_proof_success_exit_status_rejects_panic_expectation() {
setup_logger();
let prover = MockProver::new().await;
let pk =
prover.setup(test_artifacts::FIBONACCI_ELF).await.expect("failed to setup proving key");
let stdin = SP1Stdin::new();

let modes = [
crate::SP1ProofMode::Core,
crate::SP1ProofMode::Compressed,
crate::SP1ProofMode::Plonk,
crate::SP1ProofMode::Groth16,
];

for mode in modes {
let proof = prover
.prove(&pk, stdin.clone())
.mode(mode)
.await
.expect("failed to create success mock proof");

prover.verify(&proof, &pk.vk, None).expect("failed to verify success mock proof");

let result = prover.verify(&proof, &pk.vk, StatusCode::new(1));
assert!(matches!(result, Err(crate::SP1VerificationError::UnexpectedExitCode(0))));
}
}
}
64 changes: 58 additions & 6 deletions crates/sdk/src/proof.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,41 @@ use sp1_hypercube::create_dummy_recursion_proof;
use sp1_primitives::io::SP1PublicValues;
use sp1_prover::{Groth16Bn254Proof, HashableKey, PlonkBn254Proof, SP1VerifyingKey};

use crate::StatusCode;

// Re-export the types from the verifier crate in order to avoid importing the verifier crate
// for downstream dependencies.
pub use sp1_verifier::{ProofFromNetwork, SP1Proof, SP1ProofMode};

const MOCK_STATUS_TEE_PROOF_PREFIX: &[u8] = b"sp1-mock-status-v1:";

fn encode_mock_status_tee_proof(status_code: StatusCode) -> Vec<u8> {
let mut encoded = MOCK_STATUS_TEE_PROOF_PREFIX.to_vec();
encoded.extend_from_slice(&status_code.as_u32().to_le_bytes());
encoded
}

pub(crate) fn mock_status_code(proof: &SP1ProofWithPublicValues) -> Result<u32> {
if let Some(tee_proof) = &proof.tee_proof {
if let Some(encoded_status) = tee_proof.strip_prefix(MOCK_STATUS_TEE_PROOF_PREFIX) {
let bytes: [u8; 4] = encoded_status
.try_into()
.map_err(|_| anyhow::anyhow!("invalid mock status metadata"))?;
return Ok(u32::from_le_bytes(bytes));
}
}

match &proof.proof {
SP1Proof::Plonk(plonk_proof) => {
plonk_proof.public_inputs[2].parse::<u32>().context("invalid mock Plonk exit code")
}
SP1Proof::Groth16(groth16_proof) => {
groth16_proof.public_inputs[2].parse::<u32>().context("invalid mock Groth16 exit code")
}
SP1Proof::Core(_) | SP1Proof::Compressed(_) => Ok(StatusCode::SUCCESS.as_u32()),
}
}

/// Verify that the mock proof's public inputs match the expected values.
///
/// This is used by both the async and blocking mock provers to verify mock Plonk and Groth16 proofs.
Expand Down Expand Up @@ -219,14 +250,34 @@ impl SP1ProofWithPublicValues {
public_values: SP1PublicValues,
mode: SP1ProofMode,
sp1_version: &str,
) -> Self {
Self::create_mock_proof_with_status(
vk,
public_values,
mode,
sp1_version,
StatusCode::SUCCESS,
)
}

/// Creates a mock proof for the specified proof mode from the public values and exit status.
#[must_use]
#[allow(clippy::needless_pass_by_value)]
pub fn create_mock_proof_with_status(
vk: &SP1VerifyingKey,
public_values: SP1PublicValues,
mode: SP1ProofMode,
sp1_version: &str,
status_code: StatusCode,
) -> Self {
let sp1_version = sp1_version.to_string();
match mode {
SP1ProofMode::Core => SP1ProofWithPublicValues {
proof: SP1Proof::Core(vec![]),
public_values,
sp1_version,
tee_proof: None,
tee_proof: (status_code != StatusCode::SUCCESS)
.then(|| encode_mock_status_tee_proof(status_code)),
},
SP1ProofMode::Compressed => {
// Create a mock compressed proof with dummy values.
Expand All @@ -235,14 +286,15 @@ impl SP1ProofWithPublicValues {
proof: SP1Proof::Compressed(Box::new(dummy_proof)),
public_values,
sp1_version,
tee_proof: None,
tee_proof: (status_code != StatusCode::SUCCESS)
.then(|| encode_mock_status_tee_proof(status_code)),
}
}
SP1ProofMode::Plonk => {
// Create mock Plonk proof with correct public inputs.
// public_inputs[0]: vkey_hash
// public_inputs[1]: committed_values_digest (public_values hash)
// public_inputs[2]: exit_code (0 for success)
// public_inputs[2]: exit_code
// public_inputs[3]: vk_root (0 for mock)
// public_inputs[4]: proof_nonce (0 for mock)
let vkey_hash = vk.hash_bn254().to_string();
Expand All @@ -252,7 +304,7 @@ impl SP1ProofWithPublicValues {
public_inputs: [
vkey_hash,
committed_values_digest,
"0".to_string(), // exit_code
status_code.as_u32().to_string(),
"0".to_string(), // vk_root (mock)
"0".to_string(), // proof_nonce (mock)
],
Expand All @@ -269,7 +321,7 @@ impl SP1ProofWithPublicValues {
// Create mock Groth16 proof with correct public inputs.
// public_inputs[0]: vkey_hash
// public_inputs[1]: committed_values_digest (public_values hash)
// public_inputs[2]: exit_code (0 for success)
// public_inputs[2]: exit_code
// public_inputs[3]: vk_root (0 for mock)
// public_inputs[4]: proof_nonce (0 for mock)
let vkey_hash = vk.hash_bn254().to_string();
Expand All @@ -279,7 +331,7 @@ impl SP1ProofWithPublicValues {
public_inputs: [
vkey_hash,
committed_values_digest,
"0".to_string(), // exit_code
status_code.as_u32().to_string(),
"0".to_string(), // vk_root (mock)
"0".to_string(), // proof_nonce (mock)
],
Expand Down