lib

Core libraries for Radroots
git clone https://radroots.dev/git/lib.git
Log | Files | Refs | README | LICENSE

commit 16469af9258c7fc294e57a2d51213c4c754ab087
parent 64985502122dcff4022eeb6681622d9aad23e2e2
Author: triesap <tyson@radroots.org>
Date:   Wed, 20 May 2026 18:58:16 +0000

verify validation receipt sp1 proofs

Diffstat:
Mcrates/sp1_host_trade/src/lib.rs | 189+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 189 insertions(+), 0 deletions(-)

diff --git a/crates/sp1_host_trade/src/lib.rs b/crates/sp1_host_trade/src/lib.rs @@ -6,6 +6,11 @@ use radroots_sp1_guest_trade::{ RadrootsSp1TradeGuestError, RadrootsSp1TradeOrderAcceptanceWitness, RadrootsSp1TradePublicValuesExecution, reduce_order_acceptance_public_values, }; +#[cfg(feature = "expensive_proofs")] +use radroots_sp1_guest_trade::{ + RadrootsSp1TradeProofPublicValues, RadrootsSp1TradeProofResult, + RadrootsSp1TradeProofStatementType, +}; use radroots_trade::validation_receipt::{ RadrootsTradeValidationReceipt, RadrootsValidationReceiptProof, RadrootsValidationReceiptProofSystem, RadrootsValidationReceiptResult, @@ -67,6 +72,16 @@ pub struct RadrootsSp1TradeProofBundle { pub proof: RadrootsSp1TradeProofArtifact, } +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct RadrootsSp1TradeValidationReceiptVerification { + pub canonical_public_values_len: usize, + pub proof_mode: RadrootsSp1TradeProofMode, + pub proof_system: RadrootsValidationReceiptProofSystem, + pub public_values_hash: String, + pub sp1_program_hash: String, + pub sp1_verifying_key_hash: String, +} + #[derive(Debug, Error, PartialEq, Eq)] pub enum RadrootsSp1TradeHostError { #[error("guest execution failed")] @@ -103,6 +118,8 @@ pub enum RadrootsSp1TradeHostError { Sp1ProofMaterialDecode(String), #[error("SP1 proof material is synthetic")] Sp1SyntheticProofMaterial, + #[error("SP1 proof reference is unresolved")] + Sp1ProofReferenceUnresolved, #[error("SP1 proof mode does not match the proof artifact")] Sp1ProofModeMismatch, #[error("SP1 verifying key hash mismatch")] @@ -111,6 +128,8 @@ pub enum RadrootsSp1TradeHostError { Sp1ProgramHashMismatch, #[error("SP1 program hash is missing")] MissingSp1ProgramHash, + #[error("validation receipt field {0} does not match SP1 public values")] + ValidationReceiptBindingMismatch(&'static str), #[error("proof artifact encoding failed")] ProofEncoding, } @@ -298,6 +317,79 @@ pub async fn verify_order_acceptance_sp1_proof_artifact( Ok(()) } +#[cfg(feature = "expensive_proofs")] +pub async fn verify_order_acceptance_validation_receipt_inline_sp1_proof( + receipt: &RadrootsTradeValidationReceipt, +) -> Result<RadrootsSp1TradeValidationReceiptVerification, RadrootsSp1TradeHostError> { + use sp1_sdk::{HashableKey, Prover, ProverClient, ProvingKey, StatusCode}; + + if receipt.proof.system == RadrootsValidationReceiptProofSystem::None { + return Err(RadrootsSp1TradeHostError::Sp1ProofModeRequired); + } + if receipt.proof.proof_reference.is_some() { + return Err(RadrootsSp1TradeHostError::Sp1ProofReferenceUnresolved); + } + + let artifact = RadrootsSp1TradeProofArtifact { + inline_proof_base64: Some( + receipt + .proof + .inline_proof_base64 + .clone() + .ok_or(RadrootsSp1TradeHostError::MissingProofMaterial)?, + ), + mode: receipt.proof.mode.clone(), + program_hash: receipt.proof.program_hash.clone(), + proof_digest: String::new(), + proof_reference: None, + public_values_hash: receipt.public_values_hash.clone(), + system: receipt.proof.system, + verifying_key_hash: receipt.proof.verifying_key_hash.clone(), + }; + let mode = artifact_proof_mode(&artifact)?; + let proof = decode_sp1_proof_artifact(&artifact)?; + if !sp1_proof_material_is_real(&proof.proof) { + return Err(RadrootsSp1TradeHostError::Sp1SyntheticProofMaterial); + } + if !sp1_proof_matches_mode(&proof.proof, mode) { + return Err(RadrootsSp1TradeHostError::Sp1ProofModeMismatch); + } + + let client = ProverClient::builder().cpu().build().await; + let pk = client + .setup(order_acceptance_guest_elf()) + .await + .map_err(|error| RadrootsSp1TradeHostError::Sp1SetupFailed(error.to_string()))?; + let verifying_key_hash = pk.verifying_key().bytes32(); + if artifact.verifying_key_hash.as_deref() != Some(verifying_key_hash.as_str()) { + return Err(RadrootsSp1TradeHostError::Sp1VerifyingKeyHashMismatch); + } + let sp1_program_hash = sp1_program_hash_for_order_acceptance_guest(); + if artifact.program_hash.as_deref() != Some(sp1_program_hash.as_str()) { + return Err(RadrootsSp1TradeHostError::Sp1ProgramHashMismatch); + } + + client + .verify(&proof, pk.verifying_key(), Some(StatusCode::SUCCESS)) + .map_err(|error| { + RadrootsSp1TradeHostError::Sp1ProofVerificationFailed(error.to_string()) + })?; + let (_, execution) = execution_from_sp1_public_values(proof.public_values)?; + verify_validation_receipt_matches_public_values(receipt, &execution.public_values)?; + if execution.public_values_hash != receipt.public_values_hash { + return Err(RadrootsSp1TradeHostError::PublicValuesHashMismatch); + } + + Ok(RadrootsSp1TradeValidationReceiptVerification { + canonical_public_values_len: execution.canonical_public_values.len(), + proof_mode: mode, + proof_system: receipt.proof.system, + public_values_hash: execution.public_values_hash, + sp1_program_hash, + sp1_verifying_key_hash: verifying_key_hash, + }) +} + pub fn generate_order_acceptance_proof( witness: &RadrootsSp1TradeOrderAcceptanceWitness, mode: RadrootsSp1TradeProofMode, @@ -308,6 +400,84 @@ pub fn generate_order_acceptance_proof( Ok(RadrootsSp1TradeProofBundle { execution, proof }) } +#[cfg(feature = "expensive_proofs")] +fn verify_validation_receipt_matches_public_values( + receipt: &RadrootsTradeValidationReceipt, + public_values: &RadrootsSp1TradeProofPublicValues, +) -> Result<(), RadrootsSp1TradeHostError> { + if public_values.statement_type != RadrootsSp1TradeProofStatementType::TradeTransition { + return Err(RadrootsSp1TradeHostError::ValidationReceiptBindingMismatch( + "statement_type", + )); + } + if receipt.receipt_type != RadrootsValidationReceiptType::TradeTransition + || receipt.statement.statement_type != RadrootsValidationReceiptType::TradeTransition + { + return Err(RadrootsSp1TradeHostError::ValidationReceiptBindingMismatch( + "receipt_type", + )); + } + if public_values.event_set_root != receipt.event_set_root { + return Err(RadrootsSp1TradeHostError::ValidationReceiptBindingMismatch( + "event_set_root", + )); + } + if public_values.previous_state_root != receipt.previous_state_root { + return Err(RadrootsSp1TradeHostError::ValidationReceiptBindingMismatch( + "previous_state_root", + )); + } + if public_values.new_state_root != receipt.new_state_root { + return Err(RadrootsSp1TradeHostError::ValidationReceiptBindingMismatch( + "new_state_root", + )); + } + if public_values.changed_records_root != receipt.changed_records_root { + return Err(RadrootsSp1TradeHostError::ValidationReceiptBindingMismatch( + "changed_records_root", + )); + } + if public_values.error_bitmap != receipt.error_bitmap { + return Err(RadrootsSp1TradeHostError::ValidationReceiptBindingMismatch( + "error_bitmap", + )); + } + if public_values.root_event_id.as_deref() != Some(receipt.statement.root_event_id.as_str()) { + return Err(RadrootsSp1TradeHostError::ValidationReceiptBindingMismatch( + "root_event_id", + )); + } + if public_values.target_event_id.as_deref() != Some(receipt.statement.target_event_id.as_str()) + { + return Err(RadrootsSp1TradeHostError::ValidationReceiptBindingMismatch( + "target_event_id", + )); + } + if !receipt_result_matches_public_values(receipt.result, public_values.result) { + return Err(RadrootsSp1TradeHostError::ValidationReceiptBindingMismatch( + "result", + )); + } + Ok(()) +} + +#[cfg(feature = "expensive_proofs")] +fn receipt_result_matches_public_values( + receipt_result: RadrootsValidationReceiptResult, + public_values_result: RadrootsSp1TradeProofResult, +) -> bool { + matches!( + (receipt_result, public_values_result), + ( + RadrootsValidationReceiptResult::Valid, + RadrootsSp1TradeProofResult::Valid + ) | ( + RadrootsValidationReceiptResult::Invalid, + RadrootsSp1TradeProofResult::Invalid + ) + ) +} + pub fn verify_order_acceptance_proof_artifact( execution: &RadrootsSp1TradePublicValuesExecution, artifact: &RadrootsSp1TradeProofArtifact, @@ -1002,11 +1172,30 @@ mod tests { super::verify_order_acceptance_sp1_proof_artifact(&bundle.execution, &bundle.proof) .await .expect("proof verifies"); + let receipt = + validation_receipt_for_order_acceptance_proof(&bundle).expect("validation receipt"); + let verification = + super::verify_order_acceptance_validation_receipt_inline_sp1_proof(&receipt) + .await + .expect("receipt proof verifies"); assert_eq!( bundle.proof.system, RadrootsValidationReceiptProofSystem::Sp1Core ); assert!(bundle.proof.inline_proof_base64.is_some()); + assert_eq!(verification.proof_mode, RadrootsSp1TradeProofMode::Core); + assert_eq!(verification.public_values_hash, receipt.public_values_hash); + assert_eq!( + verification.sp1_program_hash, + receipt.proof.program_hash.expect("receipt program hash") + ); + assert_eq!( + verification.sp1_verifying_key_hash, + receipt + .proof + .verifying_key_hash + .expect("receipt verifying key hash") + ); } #[cfg(feature = "expensive_proofs")]