lib

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

lib.rs (130157B)


      1 #![cfg_attr(coverage_nightly, feature(coverage_attribute))]
      2 #![forbid(unsafe_code)]
      3 
      4 use base64::Engine;
      5 use radroots_sp1_guest_trade::{
      6     RadrootsSp1TradeGuestError, RadrootsSp1TradeOrderAcceptanceWitness,
      7     RadrootsSp1TradeProofResult, RadrootsSp1TradePublicValuesExecution,
      8     reduce_order_acceptance_public_values,
      9 };
     10 #[cfg(feature = "sp1_verify")]
     11 use radroots_sp1_guest_trade::{
     12     RadrootsSp1TradeProofPublicValues, RadrootsSp1TradeProofStatementType,
     13 };
     14 use radroots_trade::validation_receipt::{
     15     RadrootsTradeValidationReceipt, RadrootsValidationReceiptProof,
     16     RadrootsValidationReceiptProofSystem, RadrootsValidationReceiptResult,
     17     RadrootsValidationReceiptStatement, RadrootsValidationReceiptType, VALIDATION_RECEIPT_DOMAIN,
     18     VALIDATION_RECEIPT_PROOF_REFERENCE_SHA256_PREFIX, VALIDATION_RECEIPT_VERSION,
     19 };
     20 use serde::{Deserialize, Serialize};
     21 use sha2::{Digest, Sha256};
     22 use thiserror::Error;
     23 
     24 #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
     25 #[serde(rename_all = "snake_case")]
     26 pub enum RadrootsSp1TradeProofMode {
     27     None,
     28     Core,
     29     Compressed,
     30     Groth16,
     31     Plonk,
     32 }
     33 
     34 impl RadrootsSp1TradeProofMode {
     35     pub const fn proof_system(self) -> RadrootsValidationReceiptProofSystem {
     36         match self {
     37             Self::None => RadrootsValidationReceiptProofSystem::None,
     38             Self::Core => RadrootsValidationReceiptProofSystem::Sp1Core,
     39             Self::Compressed => RadrootsValidationReceiptProofSystem::Sp1Compressed,
     40             Self::Groth16 => RadrootsValidationReceiptProofSystem::Sp1Groth16,
     41             Self::Plonk => RadrootsValidationReceiptProofSystem::Sp1Plonk,
     42         }
     43     }
     44 
     45     pub const fn mode_label(self) -> Option<&'static str> {
     46         match self {
     47             Self::None => None,
     48             Self::Core => Some("core"),
     49             Self::Compressed => Some("compressed"),
     50             Self::Groth16 => Some("groth16"),
     51             Self::Plonk => Some("plonk"),
     52         }
     53     }
     54 
     55     pub fn from_label(value: &str) -> Option<Self> {
     56         match value {
     57             "none" => Some(Self::None),
     58             "core" => Some(Self::Core),
     59             "compressed" => Some(Self::Compressed),
     60             "groth16" => Some(Self::Groth16),
     61             "plonk" => Some(Self::Plonk),
     62             _ => None,
     63         }
     64     }
     65 }
     66 
     67 #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
     68 #[serde(deny_unknown_fields)]
     69 pub struct RadrootsSp1TradeProofArtifact {
     70     pub inline_proof_base64: Option<String>,
     71     pub mode: Option<String>,
     72     pub program_hash: Option<String>,
     73     pub proof_digest: String,
     74     pub proof_reference: Option<String>,
     75     pub public_values_hash: String,
     76     pub system: RadrootsValidationReceiptProofSystem,
     77     pub verifying_key_hash: Option<String>,
     78 }
     79 
     80 pub const RADROOTS_SP1_TRADE_PROOF_ARTIFACT_SCHEMA_VERSION: u32 = 1;
     81 pub const RADROOTS_SP1_TRADE_REMOTE_PROVER_SCHEMA_VERSION: u32 = 1;
     82 pub const RADROOTS_SP1_TRADE_SP1_VERSION_LINE: &str = "sp1-sdk-6.2.1";
     83 pub const RADROOTS_SP1_TRADE_PROOF_CODEC: &str = "sp1-proof-with-public-values-bincode";
     84 pub const RADROOTS_SP1_TRADE_VERIFYING_KEY_CODEC: &str = "sp1-verifying-key-bincode";
     85 
     86 #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
     87 #[serde(deny_unknown_fields)]
     88 pub struct RadrootsSp1TradeProofEnvelope {
     89     pub schema_version: u32,
     90     pub sp1_version_line: String,
     91     pub proof_system: String,
     92     pub proof_mode: String,
     93     pub proof_codec: String,
     94     pub proof_content_hash: String,
     95     pub proof_digest: String,
     96     pub public_values_hash: String,
     97     pub canonical_public_values_hash: String,
     98     pub sp1_program_hash: String,
     99     pub sp1_verifying_key_hash: String,
    100     pub sp1_verifying_key_codec: String,
    101     pub sp1_verifying_key_base64: String,
    102     pub receipt_type: String,
    103     pub receipt_result: String,
    104     pub listing_event_id: String,
    105     pub root_event_id: String,
    106     pub target_event_id: String,
    107     pub event_set_root: String,
    108     pub previous_state_root: String,
    109     pub new_state_root: String,
    110     pub changed_records_root: String,
    111     pub error_bitmap: String,
    112     pub proof_content_base64: String,
    113 }
    114 
    115 #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
    116 #[serde(deny_unknown_fields)]
    117 pub struct RadrootsSp1TradeResolvedProofArtifact {
    118     pub artifact: RadrootsSp1TradeProofArtifact,
    119     #[serde(default)]
    120     pub resolved_proof_envelope_base64: Option<String>,
    121 }
    122 
    123 impl RadrootsSp1TradeResolvedProofArtifact {
    124     pub fn inline(artifact: RadrootsSp1TradeProofArtifact) -> Self {
    125         Self {
    126             artifact,
    127             resolved_proof_envelope_base64: None,
    128         }
    129     }
    130 }
    131 
    132 #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
    133 #[serde(rename_all = "snake_case")]
    134 pub enum RadrootsSp1TradeProverBackend {
    135     Disabled,
    136     DeterministicNone,
    137     LocalExecute,
    138     LocalCpuProve,
    139     LocalCudaProve,
    140     RemoteHttpProve,
    141 }
    142 
    143 impl RadrootsSp1TradeProverBackend {
    144     pub const fn as_str(self) -> &'static str {
    145         match self {
    146             Self::Disabled => "disabled",
    147             Self::DeterministicNone => "deterministic_none",
    148             Self::LocalExecute => "local_execute",
    149             Self::LocalCpuProve => "local_cpu_prove",
    150             Self::LocalCudaProve => "local_cuda_prove",
    151             Self::RemoteHttpProve => "remote_http_prove",
    152         }
    153     }
    154 
    155     pub fn from_label(value: &str) -> Option<Self> {
    156         match value {
    157             "disabled" => Some(Self::Disabled),
    158             "deterministic_none" => Some(Self::DeterministicNone),
    159             "local_execute" => Some(Self::LocalExecute),
    160             "local_cpu_prove" => Some(Self::LocalCpuProve),
    161             "local_cuda_prove" => Some(Self::LocalCudaProve),
    162             "remote_http_prove" => Some(Self::RemoteHttpProve),
    163             _ => None,
    164         }
    165     }
    166 }
    167 
    168 impl core::fmt::Display for RadrootsSp1TradeProverBackend {
    169     fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
    170         formatter.write_str(self.as_str())
    171     }
    172 }
    173 
    174 #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
    175 #[serde(rename_all = "snake_case")]
    176 pub enum RadrootsSp1TradeProofEngine {
    177     Cpu,
    178     Cuda,
    179 }
    180 
    181 impl RadrootsSp1TradeProofEngine {
    182     pub const fn as_str(self) -> &'static str {
    183         match self {
    184             Self::Cpu => "cpu",
    185             Self::Cuda => "cuda",
    186         }
    187     }
    188 
    189     pub fn from_label(value: &str) -> Option<Self> {
    190         match value {
    191             "cpu" => Some(Self::Cpu),
    192             "cuda" => Some(Self::Cuda),
    193             _ => None,
    194         }
    195     }
    196 }
    197 
    198 impl core::fmt::Display for RadrootsSp1TradeProofEngine {
    199     fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
    200         formatter.write_str(self.as_str())
    201     }
    202 }
    203 
    204 #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
    205 #[serde(rename_all = "snake_case")]
    206 pub enum RadrootsSp1TradeRemoteProverStatus {
    207     Accepted,
    208     Running,
    209     Completed,
    210     Failed,
    211     Rejected,
    212 }
    213 
    214 #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
    215 #[serde(rename_all = "snake_case")]
    216 pub enum RadrootsSp1TradeWorkerResultStatus {
    217     Succeeded,
    218 }
    219 
    220 impl RadrootsSp1TradeWorkerResultStatus {
    221     pub const fn as_str(self) -> &'static str {
    222         match self {
    223             Self::Succeeded => "succeeded",
    224         }
    225     }
    226 }
    227 
    228 impl core::fmt::Display for RadrootsSp1TradeWorkerResultStatus {
    229     fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
    230         formatter.write_str(self.as_str())
    231     }
    232 }
    233 
    234 #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
    235 #[serde(rename_all = "snake_case")]
    236 pub enum RadrootsSp1TradeWorkerRole {
    237     NonAuthoritativeProver,
    238 }
    239 
    240 impl RadrootsSp1TradeWorkerRole {
    241     pub const fn as_str(self) -> &'static str {
    242         match self {
    243             Self::NonAuthoritativeProver => "non_authoritative_prover",
    244         }
    245     }
    246 }
    247 
    248 impl core::fmt::Display for RadrootsSp1TradeWorkerRole {
    249     fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
    250         formatter.write_str(self.as_str())
    251     }
    252 }
    253 
    254 #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
    255 #[serde(deny_unknown_fields)]
    256 pub struct RadrootsSp1TradeRemoteProverRequest {
    257     pub schema_version: u32,
    258     pub request_id: String,
    259     pub proof_target: String,
    260     pub proof_mode: RadrootsSp1TradeProofMode,
    261     pub sp1_version_line: String,
    262     pub witness: RadrootsSp1TradeOrderAcceptanceWitness,
    263     pub expected_sp1_program_hash: String,
    264     pub expected_sp1_verifying_key_hash: String,
    265     pub expected_public_values_hash: String,
    266     pub expected_reducer_program_hash: String,
    267     pub expected_protocol_version: String,
    268     pub expected_witness_version: u32,
    269 }
    270 
    271 #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
    272 #[serde(deny_unknown_fields)]
    273 pub struct RadrootsSp1TradeRemoteProverResponse {
    274     pub schema_version: u32,
    275     pub request_id: String,
    276     pub status: RadrootsSp1TradeRemoteProverStatus,
    277     #[serde(default)]
    278     pub status_url: Option<String>,
    279     #[serde(default)]
    280     pub status_path: Option<String>,
    281     #[serde(default)]
    282     pub proof_system: Option<RadrootsValidationReceiptProofSystem>,
    283     #[serde(default)]
    284     pub proof_mode: Option<RadrootsSp1TradeProofMode>,
    285     #[serde(default)]
    286     pub public_values_hash: Option<String>,
    287     #[serde(default)]
    288     pub sp1_program_hash: Option<String>,
    289     #[serde(default)]
    290     pub sp1_verifying_key_hash: Option<String>,
    291     #[serde(default)]
    292     pub proof_artifact: Option<RadrootsSp1TradeProofArtifact>,
    293     #[serde(default)]
    294     pub resolved_proof_envelope_base64: Option<String>,
    295     #[serde(default)]
    296     pub reason_code: Option<String>,
    297     #[serde(default)]
    298     pub message: Option<String>,
    299     #[serde(default)]
    300     pub detail: Option<String>,
    301 }
    302 
    303 #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
    304 #[serde(deny_unknown_fields)]
    305 pub struct RadrootsSp1TradeWorkerResultPayload {
    306     pub cryptographic_proof_verified: bool,
    307     pub decision_event_id: Option<String>,
    308     pub event_set_root: Option<String>,
    309     pub listing_event_id: Option<String>,
    310     pub order_id: Option<String>,
    311     pub proof_generated: bool,
    312     pub proof_mode: RadrootsSp1TradeProofMode,
    313     pub proof_system: RadrootsValidationReceiptProofSystem,
    314     pub public_values_hash: String,
    315     pub prover_backend: RadrootsSp1TradeProverBackend,
    316     pub receipt_event_id: String,
    317     pub receipt_kind: Option<u32>,
    318     pub reducer_output_root: Option<String>,
    319     pub request_event_id: Option<String>,
    320     pub sp1_execute_checked: bool,
    321     pub sp1_execute_public_values_hash: Option<String>,
    322     pub status: RadrootsSp1TradeWorkerResultStatus,
    323     pub worker_role: Option<RadrootsSp1TradeWorkerRole>,
    324 }
    325 
    326 #[derive(Clone, Debug, PartialEq, Eq)]
    327 pub struct RadrootsSp1TradeProofBundle {
    328     pub execution: RadrootsSp1TradePublicValuesExecution,
    329     pub proof: RadrootsSp1TradeProofArtifact,
    330 }
    331 
    332 #[derive(Clone, Debug, PartialEq, Eq)]
    333 pub struct RadrootsSp1TradeValidationReceiptVerification {
    334     pub canonical_public_values_len: usize,
    335     pub proof_mode: RadrootsSp1TradeProofMode,
    336     pub proof_system: RadrootsValidationReceiptProofSystem,
    337     pub public_values_hash: String,
    338     pub sp1_program_hash: String,
    339     pub sp1_verifying_key_hash: String,
    340 }
    341 
    342 #[derive(Debug, Error, PartialEq, Eq)]
    343 pub enum RadrootsSp1TradeHostError {
    344     #[error("guest execution failed")]
    345     Guest,
    346     #[error("proof mode requires an SP1 verifying key hash")]
    347     MissingVerifyingKeyHash,
    348     #[error("proof public values hash does not match execution")]
    349     PublicValuesHashMismatch,
    350     #[error("proof digest does not match execution")]
    351     ProofDigestMismatch,
    352     #[error("proof material is missing")]
    353     MissingProofMaterial,
    354     #[error("proof material has conflicting inline and reference sources")]
    355     ProofMaterialConflict,
    356     #[error("receipt binding field {0} is missing")]
    357     MissingReceiptBinding(&'static str),
    358     #[error("SP1 execution failed: {0}")]
    359     Sp1ExecuteFailed(String),
    360     #[error("SP1 execution returned exit code {0}")]
    361     Sp1ExitCode(u64),
    362     #[error("SP1 public values failed to decode: {0}")]
    363     Sp1PublicValuesDecode(String),
    364     #[error("SP1 public values do not match deterministic reducer output")]
    365     Sp1PublicValuesMismatch,
    366     #[error("SP1 proof generation requires the sp1_proving feature")]
    367     Sp1ProofGenerationRequired,
    368     #[error("SP1 CUDA proof generation is unavailable: {0}")]
    369     Sp1CudaProofEngineUnavailable(String),
    370     #[error("SP1 proof mode is required")]
    371     Sp1ProofModeRequired,
    372     #[error("SP1 setup failed: {0}")]
    373     Sp1SetupFailed(String),
    374     #[error("SP1 proof generation failed: {0}")]
    375     Sp1ProofFailed(String),
    376     #[error("SP1 proof verifier is unavailable in this build")]
    377     Sp1ProofVerifierUnavailable,
    378     #[error("SP1 proof verification failed: {0}")]
    379     Sp1ProofVerificationFailed(String),
    380     #[error("SP1 proof material failed to decode: {0}")]
    381     Sp1ProofMaterialDecode(String),
    382     #[error("SP1 proof material is synthetic")]
    383     Sp1SyntheticProofMaterial,
    384     #[error("SP1 proof reference is unresolved")]
    385     Sp1ProofReferenceUnresolved,
    386     #[error("SP1 proof reference is invalid")]
    387     InvalidSp1ProofReference,
    388     #[error("SP1 proof reference digest does not match resolved envelope")]
    389     Sp1ProofReferenceDigestMismatch,
    390     #[error("SP1 proof mode does not match the proof artifact")]
    391     Sp1ProofModeMismatch,
    392     #[error("SP1 verifying key hash mismatch")]
    393     Sp1VerifyingKeyHashMismatch,
    394     #[error("SP1 program hash mismatch")]
    395     Sp1ProgramHashMismatch,
    396     #[error("SP1 program hash is missing")]
    397     MissingSp1ProgramHash,
    398     #[error("validation receipt field {0} does not match SP1 public values")]
    399     ValidationReceiptBindingMismatch(&'static str),
    400     #[error("proof artifact encoding failed")]
    401     ProofEncoding,
    402 }
    403 
    404 impl From<RadrootsSp1TradeGuestError> for RadrootsSp1TradeHostError {
    405     fn from(_: RadrootsSp1TradeGuestError) -> Self {
    406         Self::Guest
    407     }
    408 }
    409 
    410 pub fn execute_order_acceptance_public_values(
    411     witness: &RadrootsSp1TradeOrderAcceptanceWitness,
    412 ) -> Result<RadrootsSp1TradePublicValuesExecution, RadrootsSp1TradeHostError> {
    413     Ok(reduce_order_acceptance_public_values(witness)?)
    414 }
    415 
    416 #[cfg(feature = "sp1_proving")]
    417 #[derive(Clone, Debug, PartialEq, Eq)]
    418 pub struct RadrootsSp1TradeExecuteReport {
    419     pub exit_code: u64,
    420     pub gas: Option<u64>,
    421     pub total_instruction_count: u64,
    422     pub total_syscall_count: u64,
    423 }
    424 
    425 #[cfg(feature = "sp1_proving")]
    426 #[derive(Clone, Debug, PartialEq, Eq)]
    427 pub struct RadrootsSp1TradeExecuteBundle {
    428     pub committed_public_values: Vec<u8>,
    429     pub execution: RadrootsSp1TradePublicValuesExecution,
    430     pub report: RadrootsSp1TradeExecuteReport,
    431 }
    432 
    433 #[cfg(all(feature = "sp1_verify", radroots_sp1_guest_elf))]
    434 pub fn order_acceptance_guest_elf() -> sp1_sdk::Elf {
    435     sp1_sdk::include_elf!("radroots_sp1_trade_order_acceptance_guest")
    436 }
    437 
    438 #[cfg(all(feature = "sp1_verify", radroots_sp1_guest_elf))]
    439 pub fn sp1_program_hash_for_order_acceptance_guest() -> String {
    440     sp1_program_hash_for_elf(&order_acceptance_guest_elf())
    441 }
    442 
    443 #[cfg(all(feature = "sp1_proving", radroots_sp1_guest_elf))]
    444 pub async fn execute_order_acceptance_sp1_public_values(
    445     witness: &RadrootsSp1TradeOrderAcceptanceWitness,
    446 ) -> Result<RadrootsSp1TradeExecuteBundle, RadrootsSp1TradeHostError> {
    447     execute_order_acceptance_sp1_public_values_with_elf(order_acceptance_guest_elf(), witness).await
    448 }
    449 
    450 #[cfg(all(feature = "sp1_proving", not(radroots_sp1_guest_elf)))]
    451 pub async fn execute_order_acceptance_sp1_public_values(
    452     _witness: &RadrootsSp1TradeOrderAcceptanceWitness,
    453 ) -> Result<RadrootsSp1TradeExecuteBundle, RadrootsSp1TradeHostError> {
    454     Err(RadrootsSp1TradeHostError::Sp1SetupFailed(
    455         "SP1 guest ELF build is not enabled".to_owned(),
    456     ))
    457 }
    458 
    459 #[cfg(feature = "sp1_proving")]
    460 pub async fn execute_order_acceptance_sp1_public_values_with_elf(
    461     elf: sp1_sdk::Elf,
    462     witness: &RadrootsSp1TradeOrderAcceptanceWitness,
    463 ) -> Result<RadrootsSp1TradeExecuteBundle, RadrootsSp1TradeHostError> {
    464     use sp1_sdk::{HashableKey, Prover, ProverClient, ProvingKey, SP1Stdin, StatusCode};
    465 
    466     let client = ProverClient::builder().light().build().await;
    467     let pk = client
    468         .setup(elf.clone())
    469         .await
    470         .map_err(|error| RadrootsSp1TradeHostError::Sp1SetupFailed(error.to_string()))?;
    471     let verifying_key_hash = pk.verifying_key().bytes32();
    472     let witness = witness_with_sp1_identity(
    473         witness,
    474         Some(sp1_program_hash_for_elf(&elf)),
    475         Some(verifying_key_hash),
    476     )?;
    477     let expected = execute_order_acceptance_public_values(&witness)?;
    478     let mut stdin = SP1Stdin::new();
    479     stdin.write(&witness);
    480     let (public_values, report) = client
    481         .execute(elf, stdin)
    482         .calculate_gas(true)
    483         .expected_exit_code(StatusCode::SUCCESS)
    484         .await
    485         .map_err(|error| RadrootsSp1TradeHostError::Sp1ExecuteFailed(error.to_string()))?;
    486     if report.exit_code != 0 {
    487         return Err(RadrootsSp1TradeHostError::Sp1ExitCode(report.exit_code));
    488     }
    489 
    490     let (committed_public_values, execution) = execution_from_sp1_public_values(public_values)?;
    491     if execution != expected {
    492         return Err(RadrootsSp1TradeHostError::Sp1PublicValuesMismatch);
    493     }
    494 
    495     Ok(RadrootsSp1TradeExecuteBundle {
    496         committed_public_values,
    497         execution,
    498         report: RadrootsSp1TradeExecuteReport {
    499             exit_code: report.exit_code,
    500             gas: report.gas(),
    501             total_instruction_count: report.total_instruction_count(),
    502             total_syscall_count: report.total_syscall_count(),
    503         },
    504     })
    505 }
    506 
    507 #[cfg(all(feature = "sp1_proving", radroots_sp1_guest_elf))]
    508 pub async fn generate_order_acceptance_sp1_proof(
    509     witness: &RadrootsSp1TradeOrderAcceptanceWitness,
    510     mode: RadrootsSp1TradeProofMode,
    511 ) -> Result<RadrootsSp1TradeProofBundle, RadrootsSp1TradeHostError> {
    512     generate_order_acceptance_sp1_proof_with_engine(witness, mode, RadrootsSp1TradeProofEngine::Cpu)
    513         .await
    514 }
    515 
    516 #[cfg(all(feature = "sp1_proving", not(radroots_sp1_guest_elf)))]
    517 pub async fn generate_order_acceptance_sp1_proof(
    518     _witness: &RadrootsSp1TradeOrderAcceptanceWitness,
    519     _mode: RadrootsSp1TradeProofMode,
    520 ) -> Result<RadrootsSp1TradeProofBundle, RadrootsSp1TradeHostError> {
    521     Err(RadrootsSp1TradeHostError::Sp1SetupFailed(
    522         "SP1 guest ELF build is not enabled".to_owned(),
    523     ))
    524 }
    525 
    526 #[cfg(all(feature = "sp1_proving", radroots_sp1_guest_elf))]
    527 pub async fn generate_order_acceptance_sp1_proof_with_engine(
    528     witness: &RadrootsSp1TradeOrderAcceptanceWitness,
    529     mode: RadrootsSp1TradeProofMode,
    530     engine: RadrootsSp1TradeProofEngine,
    531 ) -> Result<RadrootsSp1TradeProofBundle, RadrootsSp1TradeHostError> {
    532     match engine {
    533         RadrootsSp1TradeProofEngine::Cpu => {
    534             let client = sp1_sdk::ProverClient::builder().cpu().build().await;
    535             generate_order_acceptance_sp1_proof_with_client(&client, witness, mode).await
    536         }
    537         RadrootsSp1TradeProofEngine::Cuda => {
    538             generate_order_acceptance_sp1_cuda_proof(witness, mode).await
    539         }
    540     }
    541 }
    542 
    543 #[cfg(all(feature = "sp1_proving", not(radroots_sp1_guest_elf)))]
    544 pub async fn generate_order_acceptance_sp1_proof_with_engine(
    545     _witness: &RadrootsSp1TradeOrderAcceptanceWitness,
    546     _mode: RadrootsSp1TradeProofMode,
    547     _engine: RadrootsSp1TradeProofEngine,
    548 ) -> Result<RadrootsSp1TradeProofBundle, RadrootsSp1TradeHostError> {
    549     Err(RadrootsSp1TradeHostError::Sp1SetupFailed(
    550         "SP1 guest ELF build is not enabled".to_owned(),
    551     ))
    552 }
    553 
    554 #[cfg(all(feature = "sp1_proving", feature = "sp1_cuda", radroots_sp1_guest_elf))]
    555 async fn generate_order_acceptance_sp1_cuda_proof(
    556     witness: &RadrootsSp1TradeOrderAcceptanceWitness,
    557     mode: RadrootsSp1TradeProofMode,
    558 ) -> Result<RadrootsSp1TradeProofBundle, RadrootsSp1TradeHostError> {
    559     use futures::FutureExt;
    560     let client = std::panic::AssertUnwindSafe(sp1_sdk::ProverClient::builder().cuda().build())
    561         .catch_unwind()
    562         .await
    563         .map_err(cuda_panic_to_host_error)?;
    564     generate_order_acceptance_sp1_proof_with_client(&client, witness, mode).await
    565 }
    566 
    567 #[cfg(all(feature = "sp1_proving", feature = "sp1_cuda", radroots_sp1_guest_elf))]
    568 fn cuda_panic_to_host_error(payload: Box<dyn std::any::Any + Send>) -> RadrootsSp1TradeHostError {
    569     let message = payload
    570         .downcast_ref::<&str>()
    571         .map(|value| (*value).to_owned())
    572         .or_else(|| payload.downcast_ref::<String>().cloned())
    573         .unwrap_or_else(|| "CUDA prover initialization failed".to_owned());
    574     RadrootsSp1TradeHostError::Sp1CudaProofEngineUnavailable(message)
    575 }
    576 
    577 #[cfg(all(
    578     feature = "sp1_proving",
    579     not(feature = "sp1_cuda"),
    580     radroots_sp1_guest_elf
    581 ))]
    582 async fn generate_order_acceptance_sp1_cuda_proof(
    583     _witness: &RadrootsSp1TradeOrderAcceptanceWitness,
    584     _mode: RadrootsSp1TradeProofMode,
    585 ) -> Result<RadrootsSp1TradeProofBundle, RadrootsSp1TradeHostError> {
    586     Err(RadrootsSp1TradeHostError::Sp1CudaProofEngineUnavailable(
    587         "build without sp1_cuda feature".to_owned(),
    588     ))
    589 }
    590 
    591 #[cfg(all(feature = "sp1_proving", radroots_sp1_guest_elf))]
    592 async fn generate_order_acceptance_sp1_proof_with_client<P>(
    593     client: &P,
    594     witness: &RadrootsSp1TradeOrderAcceptanceWitness,
    595     mode: RadrootsSp1TradeProofMode,
    596 ) -> Result<RadrootsSp1TradeProofBundle, RadrootsSp1TradeHostError>
    597 where
    598     P: sp1_sdk::Prover,
    599 {
    600     use sp1_sdk::{HashableKey, ProveRequest, ProvingKey, SP1Stdin, StatusCode};
    601 
    602     let sp1_mode = sp1_proof_mode(mode)?;
    603     let elf = order_acceptance_guest_elf();
    604     let sp1_program_hash = sp1_program_hash_for_elf(&elf);
    605     let pk = client
    606         .setup(elf)
    607         .await
    608         .map_err(|error| RadrootsSp1TradeHostError::Sp1SetupFailed(error.to_string()))?;
    609     let verifying_key_hash = pk.verifying_key().bytes32();
    610     let witness =
    611         witness_with_sp1_identity(witness, Some(sp1_program_hash), Some(verifying_key_hash))?;
    612     let expected = execute_order_acceptance_public_values(&witness)?;
    613     let mut stdin = SP1Stdin::new();
    614     stdin.write(&witness);
    615     let proof = client
    616         .prove(&pk, stdin)
    617         .mode(sp1_mode)
    618         .expected_exit_code(StatusCode::SUCCESS)
    619         .await
    620         .map_err(|error| RadrootsSp1TradeHostError::Sp1ProofFailed(error.to_string()))?;
    621     if !sp1_proof_material_is_real(&proof.proof) {
    622         return Err(RadrootsSp1TradeHostError::Sp1SyntheticProofMaterial);
    623     }
    624     client
    625         .verify(&proof, pk.verifying_key(), Some(StatusCode::SUCCESS))
    626         .map_err(|error| {
    627             RadrootsSp1TradeHostError::Sp1ProofVerificationFailed(error.to_string())
    628         })?;
    629     let (_, execution) = execution_from_sp1_public_values(proof.public_values.clone())?;
    630     if execution != expected {
    631         return Err(RadrootsSp1TradeHostError::Sp1PublicValuesMismatch);
    632     }
    633     let proof_bytes =
    634         bincode::serialize(&proof).map_err(|_| RadrootsSp1TradeHostError::ProofEncoding)?;
    635     let verifying_key_bytes = bincode::serialize(pk.verifying_key())
    636         .map_err(|_| RadrootsSp1TradeHostError::ProofEncoding)?;
    637     let proof = proof_artifact_for_real_sp1_execution(
    638         &execution,
    639         mode,
    640         &proof_bytes,
    641         &verifying_key_bytes,
    642     )?;
    643     verify_order_acceptance_proof_artifact_structure(&execution, &proof)?;
    644     Ok(RadrootsSp1TradeProofBundle { execution, proof })
    645 }
    646 
    647 #[cfg(feature = "sp1_verify")]
    648 pub async fn verify_order_acceptance_resolved_sp1_proof_artifact(
    649     execution: &RadrootsSp1TradePublicValuesExecution,
    650     resolved: &RadrootsSp1TradeResolvedProofArtifact,
    651 ) -> Result<(), RadrootsSp1TradeHostError> {
    652     use sp1_sdk::{HashableKey, Prover, ProverClient, StatusCode};
    653 
    654     let artifact = &resolved.artifact;
    655     verify_order_acceptance_proof_artifact_structure(execution, artifact)?;
    656     let envelope_base64 = resolved_proof_envelope_base64(resolved)?;
    657     let envelope = decode_proof_envelope_base64(envelope_base64)?;
    658     verify_proof_envelope(execution, artifact, &envelope)?;
    659     let mode = artifact_proof_mode(artifact)?;
    660     let proof = decode_sp1_proof_envelope(&envelope)?;
    661     if !sp1_proof_material_is_real(&proof.proof) {
    662         return Err(RadrootsSp1TradeHostError::Sp1SyntheticProofMaterial);
    663     }
    664     if !sp1_proof_matches_mode(&proof.proof, mode) {
    665         return Err(RadrootsSp1TradeHostError::Sp1ProofModeMismatch);
    666     }
    667     let client = ProverClient::builder().cpu().build().await;
    668     let verifying_key = decode_sp1_verifying_key_envelope(&envelope)?;
    669     let verifying_key_hash = verifying_key.bytes32();
    670     if artifact.verifying_key_hash.as_deref() != Some(verifying_key_hash.as_str()) {
    671         return Err(RadrootsSp1TradeHostError::Sp1VerifyingKeyHashMismatch);
    672     }
    673     let sp1_program_hash = artifact
    674         .program_hash
    675         .as_deref()
    676         .ok_or(RadrootsSp1TradeHostError::MissingSp1ProgramHash)?;
    677     client
    678         .verify(&proof, &verifying_key, Some(StatusCode::SUCCESS))
    679         .map_err(|error| {
    680             RadrootsSp1TradeHostError::Sp1ProofVerificationFailed(error.to_string())
    681         })?;
    682     let (_, proof_execution) = execution_from_sp1_public_values(proof.public_values)?;
    683     require_public_values_sp1_identity(
    684         &proof_execution.public_values,
    685         sp1_program_hash,
    686         verifying_key_hash.as_str(),
    687     )?;
    688     if &proof_execution != execution {
    689         return Err(RadrootsSp1TradeHostError::Sp1PublicValuesMismatch);
    690     }
    691     Ok(())
    692 }
    693 
    694 #[cfg(not(feature = "sp1_verify"))]
    695 pub async fn verify_order_acceptance_resolved_sp1_proof_artifact(
    696     _execution: &RadrootsSp1TradePublicValuesExecution,
    697     _resolved: &RadrootsSp1TradeResolvedProofArtifact,
    698 ) -> Result<(), RadrootsSp1TradeHostError> {
    699     Err(RadrootsSp1TradeHostError::Sp1ProofVerifierUnavailable)
    700 }
    701 
    702 #[cfg(feature = "sp1_verify")]
    703 pub async fn verify_order_acceptance_validation_receipt_inline_sp1_proof(
    704     receipt: &RadrootsTradeValidationReceipt,
    705 ) -> Result<RadrootsSp1TradeValidationReceiptVerification, RadrootsSp1TradeHostError> {
    706     use sp1_sdk::{HashableKey, Prover, ProverClient, StatusCode};
    707 
    708     if receipt.proof.system == RadrootsValidationReceiptProofSystem::None {
    709         return Err(RadrootsSp1TradeHostError::Sp1ProofModeRequired);
    710     }
    711     if receipt.proof.proof_reference.is_some() {
    712         return Err(RadrootsSp1TradeHostError::Sp1ProofReferenceUnresolved);
    713     }
    714 
    715     let artifact = RadrootsSp1TradeProofArtifact {
    716         inline_proof_base64: Some(
    717             receipt
    718                 .proof
    719                 .inline_proof_base64
    720                 .clone()
    721                 .ok_or(RadrootsSp1TradeHostError::MissingProofMaterial)?,
    722         ),
    723         mode: receipt.proof.mode.clone(),
    724         program_hash: receipt.proof.program_hash.clone(),
    725         proof_digest: String::new(),
    726         proof_reference: None,
    727         public_values_hash: receipt.public_values_hash.clone(),
    728         system: receipt.proof.system,
    729         verifying_key_hash: receipt.proof.verifying_key_hash.clone(),
    730     };
    731     let mode = artifact_proof_mode(&artifact)?;
    732     let proof = decode_sp1_proof_artifact(&artifact)?;
    733     if !sp1_proof_material_is_real(&proof.proof) {
    734         return Err(RadrootsSp1TradeHostError::Sp1SyntheticProofMaterial);
    735     }
    736     if !sp1_proof_matches_mode(&proof.proof, mode) {
    737         return Err(RadrootsSp1TradeHostError::Sp1ProofModeMismatch);
    738     }
    739 
    740     let envelope = decode_proof_envelope(&artifact)?;
    741     let verifying_key = decode_sp1_verifying_key_envelope(&envelope)?;
    742     let verifying_key_hash = verifying_key.bytes32();
    743     if artifact.verifying_key_hash.as_deref() != Some(verifying_key_hash.as_str()) {
    744         return Err(RadrootsSp1TradeHostError::Sp1VerifyingKeyHashMismatch);
    745     }
    746     let sp1_program_hash = artifact
    747         .program_hash
    748         .as_deref()
    749         .ok_or(RadrootsSp1TradeHostError::MissingSp1ProgramHash)?;
    750 
    751     let client = ProverClient::builder().cpu().build().await;
    752     client
    753         .verify(&proof, &verifying_key, Some(StatusCode::SUCCESS))
    754         .map_err(|error| {
    755             RadrootsSp1TradeHostError::Sp1ProofVerificationFailed(error.to_string())
    756         })?;
    757     let (_, execution) = execution_from_sp1_public_values(proof.public_values)?;
    758     require_public_values_sp1_identity(
    759         &execution.public_values,
    760         sp1_program_hash,
    761         verifying_key_hash.as_str(),
    762     )?;
    763     verify_proof_envelope(&execution, &artifact, &envelope)?;
    764     verify_validation_receipt_matches_public_values(receipt, &execution.public_values)?;
    765     if execution.public_values_hash != receipt.public_values_hash {
    766         return Err(RadrootsSp1TradeHostError::PublicValuesHashMismatch);
    767     }
    768 
    769     Ok(RadrootsSp1TradeValidationReceiptVerification {
    770         canonical_public_values_len: execution.canonical_public_values.len(),
    771         proof_mode: mode,
    772         proof_system: receipt.proof.system,
    773         public_values_hash: execution.public_values_hash,
    774         sp1_program_hash: sp1_program_hash.to_owned(),
    775         sp1_verifying_key_hash: verifying_key_hash,
    776     })
    777 }
    778 
    779 #[cfg(not(feature = "sp1_verify"))]
    780 pub async fn verify_order_acceptance_validation_receipt_inline_sp1_proof(
    781     _receipt: &RadrootsTradeValidationReceipt,
    782 ) -> Result<RadrootsSp1TradeValidationReceiptVerification, RadrootsSp1TradeHostError> {
    783     Err(RadrootsSp1TradeHostError::Sp1ProofVerifierUnavailable)
    784 }
    785 
    786 #[cfg(feature = "sp1_verify")]
    787 pub async fn verify_order_acceptance_validation_receipt_resolved_sp1_proof(
    788     receipt: &RadrootsTradeValidationReceipt,
    789     resolved: &RadrootsSp1TradeResolvedProofArtifact,
    790 ) -> Result<RadrootsSp1TradeValidationReceiptVerification, RadrootsSp1TradeHostError> {
    791     use sp1_sdk::{HashableKey, Prover, ProverClient, StatusCode};
    792 
    793     if receipt.proof.system == RadrootsValidationReceiptProofSystem::None {
    794         return Err(RadrootsSp1TradeHostError::Sp1ProofModeRequired);
    795     }
    796     verify_receipt_proof_matches_artifact(receipt, &resolved.artifact)?;
    797     let envelope_base64 = resolved_proof_envelope_base64(resolved)?;
    798     let envelope = decode_proof_envelope_base64(envelope_base64)?;
    799     let mode = artifact_proof_mode(&resolved.artifact)?;
    800     let proof = decode_sp1_proof_envelope(&envelope)?;
    801     if !sp1_proof_material_is_real(&proof.proof) {
    802         return Err(RadrootsSp1TradeHostError::Sp1SyntheticProofMaterial);
    803     }
    804     if !sp1_proof_matches_mode(&proof.proof, mode) {
    805         return Err(RadrootsSp1TradeHostError::Sp1ProofModeMismatch);
    806     }
    807 
    808     let verifying_key = decode_sp1_verifying_key_envelope(&envelope)?;
    809     let verifying_key_hash = verifying_key.bytes32();
    810     if resolved.artifact.verifying_key_hash.as_deref() != Some(verifying_key_hash.as_str()) {
    811         return Err(RadrootsSp1TradeHostError::Sp1VerifyingKeyHashMismatch);
    812     }
    813     let sp1_program_hash = resolved
    814         .artifact
    815         .program_hash
    816         .as_deref()
    817         .ok_or(RadrootsSp1TradeHostError::MissingSp1ProgramHash)?;
    818 
    819     let client = ProverClient::builder().cpu().build().await;
    820     client
    821         .verify(&proof, &verifying_key, Some(StatusCode::SUCCESS))
    822         .map_err(|error| {
    823             RadrootsSp1TradeHostError::Sp1ProofVerificationFailed(error.to_string())
    824         })?;
    825     let (_, execution) = execution_from_sp1_public_values(proof.public_values)?;
    826     require_public_values_sp1_identity(
    827         &execution.public_values,
    828         sp1_program_hash,
    829         verifying_key_hash.as_str(),
    830     )?;
    831     verify_order_acceptance_proof_artifact_structure(&execution, &resolved.artifact)?;
    832     verify_proof_envelope(&execution, &resolved.artifact, &envelope)?;
    833     verify_validation_receipt_matches_public_values(receipt, &execution.public_values)?;
    834     if execution.public_values_hash != receipt.public_values_hash {
    835         return Err(RadrootsSp1TradeHostError::PublicValuesHashMismatch);
    836     }
    837 
    838     Ok(RadrootsSp1TradeValidationReceiptVerification {
    839         canonical_public_values_len: execution.canonical_public_values.len(),
    840         proof_mode: mode,
    841         proof_system: receipt.proof.system,
    842         public_values_hash: execution.public_values_hash,
    843         sp1_program_hash: sp1_program_hash.to_owned(),
    844         sp1_verifying_key_hash: verifying_key_hash,
    845     })
    846 }
    847 
    848 #[cfg(not(feature = "sp1_verify"))]
    849 pub async fn verify_order_acceptance_validation_receipt_resolved_sp1_proof(
    850     _receipt: &RadrootsTradeValidationReceipt,
    851     _resolved: &RadrootsSp1TradeResolvedProofArtifact,
    852 ) -> Result<RadrootsSp1TradeValidationReceiptVerification, RadrootsSp1TradeHostError> {
    853     Err(RadrootsSp1TradeHostError::Sp1ProofVerifierUnavailable)
    854 }
    855 
    856 pub fn generate_order_acceptance_proof(
    857     witness: &RadrootsSp1TradeOrderAcceptanceWitness,
    858     mode: RadrootsSp1TradeProofMode,
    859 ) -> Result<RadrootsSp1TradeProofBundle, RadrootsSp1TradeHostError> {
    860     let execution = execute_order_acceptance_public_values(witness)?;
    861     let proof = proof_artifact_for_execution(&execution, mode)?;
    862     verify_order_acceptance_proof_artifact_structure(&execution, &proof)?;
    863     Ok(RadrootsSp1TradeProofBundle { execution, proof })
    864 }
    865 
    866 #[cfg(feature = "sp1_verify")]
    867 fn verify_validation_receipt_matches_public_values(
    868     receipt: &RadrootsTradeValidationReceipt,
    869     public_values: &RadrootsSp1TradeProofPublicValues,
    870 ) -> Result<(), RadrootsSp1TradeHostError> {
    871     let program_hash = public_values
    872         .sp1_program_hash
    873         .as_deref()
    874         .ok_or(RadrootsSp1TradeHostError::MissingSp1ProgramHash)?;
    875     let verifying_key_hash = public_values
    876         .sp1_verifying_key_hash
    877         .as_deref()
    878         .ok_or(RadrootsSp1TradeHostError::MissingVerifyingKeyHash)?;
    879     if receipt.proof.program_hash.as_deref() != Some(program_hash) {
    880         return Err(RadrootsSp1TradeHostError::Sp1ProgramHashMismatch);
    881     }
    882     if receipt.proof.verifying_key_hash.as_deref() != Some(verifying_key_hash) {
    883         return Err(RadrootsSp1TradeHostError::Sp1VerifyingKeyHashMismatch);
    884     }
    885     if public_values.statement_type != RadrootsSp1TradeProofStatementType::TradeTransition {
    886         return Err(RadrootsSp1TradeHostError::ValidationReceiptBindingMismatch(
    887             "statement_type",
    888         ));
    889     }
    890     if receipt.receipt_type != RadrootsValidationReceiptType::TradeTransition
    891         || receipt.statement.statement_type != RadrootsValidationReceiptType::TradeTransition
    892     {
    893         return Err(RadrootsSp1TradeHostError::ValidationReceiptBindingMismatch(
    894             "receipt_type",
    895         ));
    896     }
    897     if public_values.event_set_root != receipt.event_set_root {
    898         return Err(RadrootsSp1TradeHostError::ValidationReceiptBindingMismatch(
    899             "event_set_root",
    900         ));
    901     }
    902     if public_values.previous_state_root != receipt.previous_state_root {
    903         return Err(RadrootsSp1TradeHostError::ValidationReceiptBindingMismatch(
    904             "previous_state_root",
    905         ));
    906     }
    907     if public_values.new_state_root != receipt.new_state_root {
    908         return Err(RadrootsSp1TradeHostError::ValidationReceiptBindingMismatch(
    909             "new_state_root",
    910         ));
    911     }
    912     if public_values.changed_records_root != receipt.changed_records_root {
    913         return Err(RadrootsSp1TradeHostError::ValidationReceiptBindingMismatch(
    914             "changed_records_root",
    915         ));
    916     }
    917     if public_values.error_bitmap != receipt.error_bitmap {
    918         return Err(RadrootsSp1TradeHostError::ValidationReceiptBindingMismatch(
    919             "error_bitmap",
    920         ));
    921     }
    922     if public_values.listing_event_id.as_deref()
    923         != Some(receipt.statement.listing_event_id.as_str())
    924     {
    925         return Err(RadrootsSp1TradeHostError::ValidationReceiptBindingMismatch(
    926             "listing_event_id",
    927         ));
    928     }
    929     if public_values.root_event_id.as_deref() != Some(receipt.statement.root_event_id.as_str()) {
    930         return Err(RadrootsSp1TradeHostError::ValidationReceiptBindingMismatch(
    931             "root_event_id",
    932         ));
    933     }
    934     if public_values.target_event_id.as_deref() != Some(receipt.statement.target_event_id.as_str())
    935     {
    936         return Err(RadrootsSp1TradeHostError::ValidationReceiptBindingMismatch(
    937             "target_event_id",
    938         ));
    939     }
    940     if !receipt_result_matches_public_values(receipt.result, public_values.result) {
    941         return Err(RadrootsSp1TradeHostError::ValidationReceiptBindingMismatch(
    942             "result",
    943         ));
    944     }
    945     Ok(())
    946 }
    947 
    948 #[cfg(feature = "sp1_verify")]
    949 fn require_public_values_sp1_identity(
    950     public_values: &RadrootsSp1TradeProofPublicValues,
    951     expected_program_hash: &str,
    952     expected_verifying_key_hash: &str,
    953 ) -> Result<(), RadrootsSp1TradeHostError> {
    954     match public_values.sp1_program_hash.as_deref() {
    955         Some(value) if value == expected_program_hash => {}
    956         Some(_) => return Err(RadrootsSp1TradeHostError::Sp1ProgramHashMismatch),
    957         None => return Err(RadrootsSp1TradeHostError::MissingSp1ProgramHash),
    958     }
    959     match public_values.sp1_verifying_key_hash.as_deref() {
    960         Some(value) if value == expected_verifying_key_hash => {}
    961         Some(_) => return Err(RadrootsSp1TradeHostError::Sp1VerifyingKeyHashMismatch),
    962         None => return Err(RadrootsSp1TradeHostError::MissingVerifyingKeyHash),
    963     }
    964     Ok(())
    965 }
    966 
    967 #[cfg(feature = "sp1_verify")]
    968 fn receipt_result_matches_public_values(
    969     receipt_result: RadrootsValidationReceiptResult,
    970     public_values_result: RadrootsSp1TradeProofResult,
    971 ) -> bool {
    972     matches!(
    973         (receipt_result, public_values_result),
    974         (
    975             RadrootsValidationReceiptResult::Valid,
    976             RadrootsSp1TradeProofResult::Valid
    977         ) | (
    978             RadrootsValidationReceiptResult::Invalid,
    979             RadrootsSp1TradeProofResult::Invalid
    980         )
    981     )
    982 }
    983 
    984 pub fn verify_order_acceptance_proof_artifact_structure(
    985     execution: &RadrootsSp1TradePublicValuesExecution,
    986     artifact: &RadrootsSp1TradeProofArtifact,
    987 ) -> Result<(), RadrootsSp1TradeHostError> {
    988     if artifact.public_values_hash != execution.public_values_hash {
    989         return Err(RadrootsSp1TradeHostError::PublicValuesHashMismatch);
    990     }
    991     let expected = proof_digest_for_execution(execution, artifact)?;
    992     if artifact.proof_digest != expected {
    993         return Err(RadrootsSp1TradeHostError::ProofDigestMismatch);
    994     }
    995     match artifact.system {
    996         RadrootsValidationReceiptProofSystem::None => {
    997             if artifact.inline_proof_base64.is_some()
    998                 || artifact.mode.is_some()
    999                 || artifact.program_hash.is_some()
   1000                 || artifact.proof_reference.is_some()
   1001                 || artifact.verifying_key_hash.is_some()
   1002             {
   1003                 return Err(RadrootsSp1TradeHostError::MissingProofMaterial);
   1004             }
   1005         }
   1006         _ => {
   1007             match (&artifact.inline_proof_base64, &artifact.proof_reference) {
   1008                 (None, None) => return Err(RadrootsSp1TradeHostError::MissingProofMaterial),
   1009                 (Some(_), Some(_)) => {
   1010                     return Err(RadrootsSp1TradeHostError::ProofMaterialConflict);
   1011                 }
   1012                 (None, Some(reference)) => {
   1013                     proof_reference_digest(reference)?;
   1014                 }
   1015                 _ => {}
   1016             }
   1017             let public_values_program_hash = execution
   1018                 .public_values
   1019                 .sp1_program_hash
   1020                 .as_deref()
   1021                 .ok_or(RadrootsSp1TradeHostError::MissingSp1ProgramHash)?;
   1022             let public_values_verifying_key_hash = execution
   1023                 .public_values
   1024                 .sp1_verifying_key_hash
   1025                 .as_deref()
   1026                 .ok_or(RadrootsSp1TradeHostError::MissingVerifyingKeyHash)?;
   1027             if artifact.program_hash.as_deref() != Some(public_values_program_hash) {
   1028                 return Err(RadrootsSp1TradeHostError::Sp1ProgramHashMismatch);
   1029             }
   1030             if artifact.verifying_key_hash.as_deref() != Some(public_values_verifying_key_hash) {
   1031                 return Err(RadrootsSp1TradeHostError::Sp1VerifyingKeyHashMismatch);
   1032             }
   1033             if artifact.inline_proof_base64.is_some() {
   1034                 let envelope = decode_proof_envelope(artifact)?;
   1035                 verify_proof_envelope(execution, artifact, &envelope)?;
   1036             }
   1037         }
   1038     }
   1039     Ok(())
   1040 }
   1041 
   1042 pub fn validation_receipt_for_order_acceptance_proof(
   1043     bundle: &RadrootsSp1TradeProofBundle,
   1044 ) -> Result<RadrootsTradeValidationReceipt, RadrootsSp1TradeHostError> {
   1045     let public_values = &bundle.execution.public_values;
   1046     let listing_event_id = public_values.listing_event_id.clone().ok_or(
   1047         RadrootsSp1TradeHostError::MissingReceiptBinding("listing_event_id"),
   1048     )?;
   1049     let root_event_id = public_values.root_event_id.clone().ok_or(
   1050         RadrootsSp1TradeHostError::MissingReceiptBinding("root_event_id"),
   1051     )?;
   1052     let target_event_id = public_values.target_event_id.clone().ok_or(
   1053         RadrootsSp1TradeHostError::MissingReceiptBinding("target_event_id"),
   1054     )?;
   1055     Ok(RadrootsTradeValidationReceipt {
   1056         changed_records_root: public_values.changed_records_root.clone(),
   1057         domain: VALIDATION_RECEIPT_DOMAIN.to_string(),
   1058         error_bitmap: public_values.error_bitmap.clone(),
   1059         event_set_root: public_values.event_set_root.clone(),
   1060         new_state_root: public_values.new_state_root.clone(),
   1061         previous_state_root: public_values.previous_state_root.clone(),
   1062         proof: RadrootsValidationReceiptProof {
   1063             inline_proof_base64: bundle.proof.inline_proof_base64.clone(),
   1064             mode: bundle.proof.mode.clone(),
   1065             program_hash: bundle.proof.program_hash.clone(),
   1066             proof_reference: bundle.proof.proof_reference.clone(),
   1067             system: bundle.proof.system,
   1068             verifying_key_hash: bundle.proof.verifying_key_hash.clone(),
   1069         },
   1070         public_values_hash: bundle.execution.public_values_hash.clone(),
   1071         receipt_type: RadrootsValidationReceiptType::TradeTransition,
   1072         result: validation_receipt_result_from_public_values(public_values.result),
   1073         statement: RadrootsValidationReceiptStatement {
   1074             listing_event_id,
   1075             root_event_id,
   1076             target_event_id,
   1077             statement_type: RadrootsValidationReceiptType::TradeTransition,
   1078         },
   1079         version: VALIDATION_RECEIPT_VERSION,
   1080     })
   1081 }
   1082 
   1083 fn validation_receipt_result_from_public_values(
   1084     result: RadrootsSp1TradeProofResult,
   1085 ) -> RadrootsValidationReceiptResult {
   1086     match result {
   1087         RadrootsSp1TradeProofResult::Valid => RadrootsValidationReceiptResult::Valid,
   1088         RadrootsSp1TradeProofResult::Invalid => RadrootsValidationReceiptResult::Invalid,
   1089     }
   1090 }
   1091 
   1092 #[cfg(feature = "sp1_verify")]
   1093 fn verify_receipt_proof_matches_artifact(
   1094     receipt: &RadrootsTradeValidationReceipt,
   1095     artifact: &RadrootsSp1TradeProofArtifact,
   1096 ) -> Result<(), RadrootsSp1TradeHostError> {
   1097     if receipt.public_values_hash != artifact.public_values_hash {
   1098         return Err(RadrootsSp1TradeHostError::PublicValuesHashMismatch);
   1099     }
   1100     if receipt.proof.inline_proof_base64.as_deref() != artifact.inline_proof_base64.as_deref()
   1101         || receipt.proof.mode.as_deref() != artifact.mode.as_deref()
   1102         || receipt.proof.program_hash.as_deref() != artifact.program_hash.as_deref()
   1103         || receipt.proof.proof_reference.as_deref() != artifact.proof_reference.as_deref()
   1104         || receipt.proof.system != artifact.system
   1105         || receipt.proof.verifying_key_hash.as_deref() != artifact.verifying_key_hash.as_deref()
   1106     {
   1107         return Err(RadrootsSp1TradeHostError::ValidationReceiptBindingMismatch(
   1108             "proof",
   1109         ));
   1110     }
   1111     Ok(())
   1112 }
   1113 
   1114 fn validation_receipt_result_label(result: RadrootsValidationReceiptResult) -> &'static str {
   1115     match result {
   1116         RadrootsValidationReceiptResult::Valid => "valid",
   1117         RadrootsValidationReceiptResult::Invalid => "invalid",
   1118     }
   1119 }
   1120 
   1121 fn proof_artifact_for_execution(
   1122     execution: &RadrootsSp1TradePublicValuesExecution,
   1123     mode: RadrootsSp1TradeProofMode,
   1124 ) -> Result<RadrootsSp1TradeProofArtifact, RadrootsSp1TradeHostError> {
   1125     let system = mode.proof_system();
   1126     let mut artifact = RadrootsSp1TradeProofArtifact {
   1127         inline_proof_base64: None,
   1128         mode: mode.mode_label().map(str::to_string),
   1129         program_hash: None,
   1130         proof_digest: String::new(),
   1131         proof_reference: None,
   1132         public_values_hash: execution.public_values_hash.clone(),
   1133         system,
   1134         verifying_key_hash: None,
   1135     };
   1136     if system == RadrootsValidationReceiptProofSystem::None {
   1137         artifact.proof_digest = proof_digest_for_execution(execution, &artifact)?;
   1138         return Ok(artifact);
   1139     }
   1140 
   1141     Err(RadrootsSp1TradeHostError::Sp1ProofGenerationRequired)
   1142 }
   1143 
   1144 pub fn referenced_order_acceptance_proof_artifact_for_execution(
   1145     execution: &RadrootsSp1TradePublicValuesExecution,
   1146     mode: RadrootsSp1TradeProofMode,
   1147     proof_reference: String,
   1148 ) -> Result<RadrootsSp1TradeProofArtifact, RadrootsSp1TradeHostError> {
   1149     proof_reference_digest(proof_reference.as_str())?;
   1150     let system = mode.proof_system();
   1151     if system == RadrootsValidationReceiptProofSystem::None {
   1152         return Err(RadrootsSp1TradeHostError::Sp1ProofModeRequired);
   1153     }
   1154     let mut artifact = RadrootsSp1TradeProofArtifact {
   1155         inline_proof_base64: None,
   1156         mode: mode.mode_label().map(str::to_string),
   1157         program_hash: execution.public_values.sp1_program_hash.clone(),
   1158         proof_digest: String::new(),
   1159         proof_reference: Some(proof_reference),
   1160         public_values_hash: execution.public_values_hash.clone(),
   1161         system,
   1162         verifying_key_hash: execution.public_values.sp1_verifying_key_hash.clone(),
   1163     };
   1164     artifact.proof_digest = proof_digest_for_execution(execution, &artifact)?;
   1165     verify_order_acceptance_proof_artifact_structure(execution, &artifact)?;
   1166     Ok(artifact)
   1167 }
   1168 
   1169 #[cfg(all(feature = "sp1_proving", radroots_sp1_guest_elf))]
   1170 fn proof_artifact_for_real_sp1_execution(
   1171     execution: &RadrootsSp1TradePublicValuesExecution,
   1172     mode: RadrootsSp1TradeProofMode,
   1173     proof_bytes: &[u8],
   1174     verifying_key_bytes: &[u8],
   1175 ) -> Result<RadrootsSp1TradeProofArtifact, RadrootsSp1TradeHostError> {
   1176     let system = mode.proof_system();
   1177     if system == RadrootsValidationReceiptProofSystem::None {
   1178         return Err(RadrootsSp1TradeHostError::Sp1ProofModeRequired);
   1179     }
   1180     let program_hash = execution
   1181         .public_values
   1182         .sp1_program_hash
   1183         .clone()
   1184         .ok_or(RadrootsSp1TradeHostError::MissingSp1ProgramHash)?;
   1185     let verifying_key_hash = execution
   1186         .public_values
   1187         .sp1_verifying_key_hash
   1188         .clone()
   1189         .ok_or(RadrootsSp1TradeHostError::MissingVerifyingKeyHash)?;
   1190     let mut envelope = proof_envelope_for_real_sp1_execution(
   1191         execution,
   1192         system,
   1193         mode,
   1194         program_hash.as_str(),
   1195         verifying_key_hash.as_str(),
   1196         proof_bytes,
   1197         verifying_key_bytes,
   1198     )?;
   1199     envelope.proof_digest = proof_digest_for_envelope(&envelope)?;
   1200     let envelope_json =
   1201         serde_json::to_vec(&envelope).map_err(|_| RadrootsSp1TradeHostError::ProofEncoding)?;
   1202     Ok(RadrootsSp1TradeProofArtifact {
   1203         inline_proof_base64: Some(base64::engine::general_purpose::STANDARD.encode(envelope_json)),
   1204         mode: mode.mode_label().map(str::to_string),
   1205         program_hash: Some(program_hash),
   1206         proof_digest: envelope.proof_digest,
   1207         proof_reference: None,
   1208         public_values_hash: execution.public_values_hash.clone(),
   1209         system,
   1210         verifying_key_hash: Some(verifying_key_hash),
   1211     })
   1212 }
   1213 
   1214 fn proof_digest_for_execution(
   1215     execution: &RadrootsSp1TradePublicValuesExecution,
   1216     artifact: &RadrootsSp1TradeProofArtifact,
   1217 ) -> Result<String, RadrootsSp1TradeHostError> {
   1218     if artifact.inline_proof_base64.is_some() {
   1219         return proof_digest_for_envelope(&decode_proof_envelope(artifact)?);
   1220     }
   1221     let material = ProofDigestMaterial {
   1222         canonical_public_values: &execution.canonical_public_values,
   1223         mode: artifact.mode.as_deref(),
   1224         program_hash: artifact.program_hash.as_deref(),
   1225         proof_reference: artifact.proof_reference.as_deref(),
   1226         public_values_hash: &artifact.public_values_hash,
   1227         system: artifact.system.as_str(),
   1228         verifying_key_hash: artifact.verifying_key_hash.as_deref(),
   1229     };
   1230     let bytes =
   1231         serde_json::to_vec(&material).map_err(|_| RadrootsSp1TradeHostError::ProofEncoding)?;
   1232     let mut hasher = Sha256::new();
   1233     hasher.update(b"radroots:sp1-trade-proof-artifact:v1");
   1234     hasher.update(bytes);
   1235     Ok(format!("0x{}", hex_lower(hasher.finalize().as_slice())))
   1236 }
   1237 
   1238 #[cfg(all(feature = "sp1_proving", radroots_sp1_guest_elf))]
   1239 fn proof_envelope_for_real_sp1_execution(
   1240     execution: &RadrootsSp1TradePublicValuesExecution,
   1241     system: RadrootsValidationReceiptProofSystem,
   1242     mode: RadrootsSp1TradeProofMode,
   1243     program_hash: &str,
   1244     verifying_key_hash: &str,
   1245     proof_bytes: &[u8],
   1246     verifying_key_bytes: &[u8],
   1247 ) -> Result<RadrootsSp1TradeProofEnvelope, RadrootsSp1TradeHostError> {
   1248     Ok(RadrootsSp1TradeProofEnvelope {
   1249         schema_version: RADROOTS_SP1_TRADE_PROOF_ARTIFACT_SCHEMA_VERSION,
   1250         sp1_version_line: RADROOTS_SP1_TRADE_SP1_VERSION_LINE.to_owned(),
   1251         proof_system: system.as_str().to_owned(),
   1252         proof_mode: mode
   1253             .mode_label()
   1254             .ok_or(RadrootsSp1TradeHostError::Sp1ProofModeRequired)?
   1255             .to_owned(),
   1256         proof_codec: RADROOTS_SP1_TRADE_PROOF_CODEC.to_owned(),
   1257         proof_content_hash: hash_bytes("radroots:sp1-proof-content:v1", proof_bytes),
   1258         proof_digest: String::new(),
   1259         public_values_hash: execution.public_values_hash.clone(),
   1260         canonical_public_values_hash: hash_bytes(
   1261             "radroots:sp1-canonical-public-values:v1",
   1262             &execution.canonical_public_values,
   1263         ),
   1264         sp1_program_hash: program_hash.to_owned(),
   1265         sp1_verifying_key_hash: verifying_key_hash.to_owned(),
   1266         sp1_verifying_key_codec: RADROOTS_SP1_TRADE_VERIFYING_KEY_CODEC.to_owned(),
   1267         sp1_verifying_key_base64: base64::engine::general_purpose::STANDARD
   1268             .encode(verifying_key_bytes),
   1269         receipt_type: RadrootsValidationReceiptType::TradeTransition
   1270             .as_str()
   1271             .to_owned(),
   1272         receipt_result: validation_receipt_result_label(
   1273             validation_receipt_result_from_public_values(execution.public_values.result),
   1274         )
   1275         .to_owned(),
   1276         listing_event_id: execution.public_values.listing_event_id.clone().ok_or(
   1277             RadrootsSp1TradeHostError::MissingReceiptBinding("listing_event_id"),
   1278         )?,
   1279         root_event_id: execution.public_values.root_event_id.clone().ok_or(
   1280             RadrootsSp1TradeHostError::MissingReceiptBinding("root_event_id"),
   1281         )?,
   1282         target_event_id: execution.public_values.target_event_id.clone().ok_or(
   1283             RadrootsSp1TradeHostError::MissingReceiptBinding("target_event_id"),
   1284         )?,
   1285         event_set_root: execution.public_values.event_set_root.clone(),
   1286         previous_state_root: execution.public_values.previous_state_root.clone(),
   1287         new_state_root: execution.public_values.new_state_root.clone(),
   1288         changed_records_root: execution.public_values.changed_records_root.clone(),
   1289         error_bitmap: execution.public_values.error_bitmap.clone(),
   1290         proof_content_base64: base64::engine::general_purpose::STANDARD.encode(proof_bytes),
   1291     })
   1292 }
   1293 
   1294 fn decode_proof_envelope(
   1295     artifact: &RadrootsSp1TradeProofArtifact,
   1296 ) -> Result<RadrootsSp1TradeProofEnvelope, RadrootsSp1TradeHostError> {
   1297     let inline = artifact
   1298         .inline_proof_base64
   1299         .as_deref()
   1300         .ok_or(RadrootsSp1TradeHostError::MissingProofMaterial)?;
   1301     decode_proof_envelope_base64(inline)
   1302 }
   1303 
   1304 fn decode_proof_envelope_base64(
   1305     value: &str,
   1306 ) -> Result<RadrootsSp1TradeProofEnvelope, RadrootsSp1TradeHostError> {
   1307     let envelope_bytes = proof_envelope_bytes_from_base64(value)?;
   1308     serde_json::from_slice::<RadrootsSp1TradeProofEnvelope>(&envelope_bytes)
   1309         .map_err(|error| RadrootsSp1TradeHostError::Sp1ProofMaterialDecode(error.to_string()))
   1310 }
   1311 
   1312 fn proof_envelope_bytes_from_base64(value: &str) -> Result<Vec<u8>, RadrootsSp1TradeHostError> {
   1313     base64::engine::general_purpose::STANDARD
   1314         .decode(value)
   1315         .map_err(|error| RadrootsSp1TradeHostError::Sp1ProofMaterialDecode(error.to_string()))
   1316 }
   1317 
   1318 pub fn proof_reference_for_proof_envelope_base64(
   1319     value: &str,
   1320 ) -> Result<String, RadrootsSp1TradeHostError> {
   1321     let envelope_bytes = proof_envelope_bytes_from_base64(value)?;
   1322     let mut hasher = Sha256::new();
   1323     hasher.update(envelope_bytes);
   1324     Ok(format!(
   1325         "{VALIDATION_RECEIPT_PROOF_REFERENCE_SHA256_PREFIX}{}",
   1326         hex_lower(hasher.finalize().as_slice())
   1327     ))
   1328 }
   1329 
   1330 fn proof_reference_digest(value: &str) -> Result<&str, RadrootsSp1TradeHostError> {
   1331     let Some(digest) = value.strip_prefix(VALIDATION_RECEIPT_PROOF_REFERENCE_SHA256_PREFIX) else {
   1332         return Err(RadrootsSp1TradeHostError::InvalidSp1ProofReference);
   1333     };
   1334     if digest.len() != 64 || !is_lower_hex(digest) {
   1335         return Err(RadrootsSp1TradeHostError::InvalidSp1ProofReference);
   1336     }
   1337     Ok(digest)
   1338 }
   1339 
   1340 #[cfg(feature = "sp1_verify")]
   1341 fn resolved_proof_envelope_base64(
   1342     resolved: &RadrootsSp1TradeResolvedProofArtifact,
   1343 ) -> Result<&str, RadrootsSp1TradeHostError> {
   1344     match (
   1345         resolved.artifact.inline_proof_base64.as_deref(),
   1346         resolved.artifact.proof_reference.as_deref(),
   1347         resolved.resolved_proof_envelope_base64.as_deref(),
   1348     ) {
   1349         (Some(inline), None, None) => Ok(inline),
   1350         (Some(_), None, Some(_)) => Err(RadrootsSp1TradeHostError::ProofMaterialConflict),
   1351         (None, Some(reference), Some(envelope)) => {
   1352             let expected = proof_reference_digest(reference)?;
   1353             let actual = proof_reference_for_proof_envelope_base64(envelope)?;
   1354             let actual = proof_reference_digest(actual.as_str())?;
   1355             if actual != expected {
   1356                 return Err(RadrootsSp1TradeHostError::Sp1ProofReferenceDigestMismatch);
   1357             }
   1358             Ok(envelope)
   1359         }
   1360         (None, Some(_), None) => Err(RadrootsSp1TradeHostError::Sp1ProofReferenceUnresolved),
   1361         (Some(_), Some(_), _) => Err(RadrootsSp1TradeHostError::ProofMaterialConflict),
   1362         (None, None, _) => Err(RadrootsSp1TradeHostError::MissingProofMaterial),
   1363     }
   1364 }
   1365 
   1366 fn proof_content_bytes_from_envelope(
   1367     envelope: &RadrootsSp1TradeProofEnvelope,
   1368 ) -> Result<Vec<u8>, RadrootsSp1TradeHostError> {
   1369     base64::engine::general_purpose::STANDARD
   1370         .decode(envelope.proof_content_base64.as_str())
   1371         .map_err(|error| RadrootsSp1TradeHostError::Sp1ProofMaterialDecode(error.to_string()))
   1372 }
   1373 
   1374 fn proof_digest_for_envelope(
   1375     envelope: &RadrootsSp1TradeProofEnvelope,
   1376 ) -> Result<String, RadrootsSp1TradeHostError> {
   1377     let material = ProofEnvelopeDigestMaterial {
   1378         schema_version: envelope.schema_version,
   1379         sp1_version_line: envelope.sp1_version_line.as_str(),
   1380         proof_system: envelope.proof_system.as_str(),
   1381         proof_mode: envelope.proof_mode.as_str(),
   1382         proof_codec: envelope.proof_codec.as_str(),
   1383         proof_content_hash: envelope.proof_content_hash.as_str(),
   1384         public_values_hash: envelope.public_values_hash.as_str(),
   1385         canonical_public_values_hash: envelope.canonical_public_values_hash.as_str(),
   1386         sp1_program_hash: envelope.sp1_program_hash.as_str(),
   1387         sp1_verifying_key_hash: envelope.sp1_verifying_key_hash.as_str(),
   1388         sp1_verifying_key_codec: envelope.sp1_verifying_key_codec.as_str(),
   1389         sp1_verifying_key_base64: envelope.sp1_verifying_key_base64.as_str(),
   1390         receipt_type: envelope.receipt_type.as_str(),
   1391         receipt_result: envelope.receipt_result.as_str(),
   1392         listing_event_id: envelope.listing_event_id.as_str(),
   1393         root_event_id: envelope.root_event_id.as_str(),
   1394         target_event_id: envelope.target_event_id.as_str(),
   1395         event_set_root: envelope.event_set_root.as_str(),
   1396         previous_state_root: envelope.previous_state_root.as_str(),
   1397         new_state_root: envelope.new_state_root.as_str(),
   1398         changed_records_root: envelope.changed_records_root.as_str(),
   1399         error_bitmap: envelope.error_bitmap.as_str(),
   1400     };
   1401     let bytes =
   1402         serde_json::to_vec(&material).map_err(|_| RadrootsSp1TradeHostError::ProofEncoding)?;
   1403     Ok(hash_bytes(
   1404         "radroots:sp1-trade-proof-envelope-digest:v1",
   1405         &bytes,
   1406     ))
   1407 }
   1408 
   1409 fn verify_proof_envelope(
   1410     execution: &RadrootsSp1TradePublicValuesExecution,
   1411     artifact: &RadrootsSp1TradeProofArtifact,
   1412     envelope: &RadrootsSp1TradeProofEnvelope,
   1413 ) -> Result<(), RadrootsSp1TradeHostError> {
   1414     if envelope.schema_version != RADROOTS_SP1_TRADE_PROOF_ARTIFACT_SCHEMA_VERSION
   1415         || envelope.sp1_version_line != RADROOTS_SP1_TRADE_SP1_VERSION_LINE
   1416         || envelope.proof_codec != RADROOTS_SP1_TRADE_PROOF_CODEC
   1417         || envelope.proof_system != artifact.system.as_str()
   1418         || Some(envelope.proof_mode.as_str()) != artifact.mode.as_deref()
   1419         || envelope.public_values_hash != artifact.public_values_hash
   1420         || envelope.sp1_program_hash.as_str() != artifact.program_hash.as_deref().unwrap_or("")
   1421         || envelope.sp1_verifying_key_hash.as_str()
   1422             != artifact.verifying_key_hash.as_deref().unwrap_or("")
   1423         || envelope.sp1_verifying_key_codec != RADROOTS_SP1_TRADE_VERIFYING_KEY_CODEC
   1424         || envelope.sp1_verifying_key_base64.is_empty()
   1425     {
   1426         return Err(RadrootsSp1TradeHostError::Sp1ProofMaterialDecode(
   1427             "proof envelope metadata mismatch".to_owned(),
   1428         ));
   1429     }
   1430     if envelope.proof_digest != proof_digest_for_envelope(envelope)? {
   1431         return Err(RadrootsSp1TradeHostError::ProofDigestMismatch);
   1432     }
   1433     let proof_bytes = proof_content_bytes_from_envelope(envelope)?;
   1434     if envelope.proof_content_hash != hash_bytes("radroots:sp1-proof-content:v1", &proof_bytes) {
   1435         return Err(RadrootsSp1TradeHostError::Sp1ProofMaterialDecode(
   1436             "proof envelope content hash mismatch".to_owned(),
   1437         ));
   1438     }
   1439     let expected_canonical_public_values_hash = hash_bytes(
   1440         "radroots:sp1-canonical-public-values:v1",
   1441         &execution.canonical_public_values,
   1442     );
   1443     if envelope.canonical_public_values_hash != expected_canonical_public_values_hash {
   1444         return Err(RadrootsSp1TradeHostError::PublicValuesHashMismatch);
   1445     }
   1446     if envelope.receipt_type != RadrootsValidationReceiptType::TradeTransition.as_str() {
   1447         return Err(RadrootsSp1TradeHostError::ValidationReceiptBindingMismatch(
   1448             "receipt_type",
   1449         ));
   1450     }
   1451     if envelope.receipt_result
   1452         != validation_receipt_result_label(validation_receipt_result_from_public_values(
   1453             execution.public_values.result,
   1454         ))
   1455     {
   1456         return Err(RadrootsSp1TradeHostError::ValidationReceiptBindingMismatch(
   1457             "result",
   1458         ));
   1459     }
   1460     if envelope.listing_event_id.as_str()
   1461         != execution
   1462             .public_values
   1463             .listing_event_id
   1464             .as_deref()
   1465             .unwrap_or("")
   1466     {
   1467         return Err(RadrootsSp1TradeHostError::ValidationReceiptBindingMismatch(
   1468             "listing_event_id",
   1469         ));
   1470     }
   1471     if envelope.root_event_id.as_str()
   1472         != execution
   1473             .public_values
   1474             .root_event_id
   1475             .as_deref()
   1476             .unwrap_or("")
   1477     {
   1478         return Err(RadrootsSp1TradeHostError::ValidationReceiptBindingMismatch(
   1479             "root_event_id",
   1480         ));
   1481     }
   1482     if envelope.target_event_id.as_str()
   1483         != execution
   1484             .public_values
   1485             .target_event_id
   1486             .as_deref()
   1487             .unwrap_or("")
   1488     {
   1489         return Err(RadrootsSp1TradeHostError::ValidationReceiptBindingMismatch(
   1490             "target_event_id",
   1491         ));
   1492     }
   1493     if envelope.event_set_root != execution.public_values.event_set_root
   1494         || envelope.previous_state_root != execution.public_values.previous_state_root
   1495         || envelope.new_state_root != execution.public_values.new_state_root
   1496         || envelope.changed_records_root != execution.public_values.changed_records_root
   1497         || envelope.error_bitmap != execution.public_values.error_bitmap
   1498     {
   1499         return Err(RadrootsSp1TradeHostError::ValidationReceiptBindingMismatch(
   1500             "state_roots",
   1501         ));
   1502     }
   1503     Ok(())
   1504 }
   1505 
   1506 fn hex_lower(bytes: &[u8]) -> String {
   1507     const HEX: &[u8; 16] = b"0123456789abcdef";
   1508     let mut out = String::with_capacity(bytes.len() * 2);
   1509     for byte in bytes {
   1510         out.push(HEX[(byte >> 4) as usize] as char);
   1511         out.push(HEX[(byte & 0x0f) as usize] as char);
   1512     }
   1513     out
   1514 }
   1515 
   1516 fn is_lower_hex(value: &str) -> bool {
   1517     value
   1518         .bytes()
   1519         .all(|byte| byte.is_ascii_digit() || (b'a'..=b'f').contains(&byte))
   1520 }
   1521 
   1522 #[cfg(any(
   1523     feature = "sp1_proving",
   1524     all(feature = "sp1_verify", radroots_sp1_guest_elf)
   1525 ))]
   1526 fn sp1_program_hash_for_elf(elf: &sp1_sdk::Elf) -> String {
   1527     let bytes: &[u8] = match elf {
   1528         sp1_sdk::Elf::Static(bytes) => bytes,
   1529         sp1_sdk::Elf::Dynamic(bytes) => bytes,
   1530     };
   1531     hash_bytes("radroots:sp1-guest-elf:v1", bytes)
   1532 }
   1533 
   1534 fn hash_bytes(domain: &'static str, bytes: &[u8]) -> String {
   1535     let mut hasher = Sha256::new();
   1536     hasher.update(domain.as_bytes());
   1537     hasher.update(bytes);
   1538     format!("0x{}", hex_lower(hasher.finalize().as_slice()))
   1539 }
   1540 
   1541 #[cfg(feature = "sp1_proving")]
   1542 fn witness_with_sp1_identity(
   1543     witness: &RadrootsSp1TradeOrderAcceptanceWitness,
   1544     sp1_program_hash: Option<String>,
   1545     sp1_verifying_key_hash: Option<String>,
   1546 ) -> Result<RadrootsSp1TradeOrderAcceptanceWitness, RadrootsSp1TradeHostError> {
   1547     if let (Some(existing), Some(actual)) = (
   1548         witness.sp1_program_hash.as_deref(),
   1549         sp1_program_hash.as_deref(),
   1550     ) && existing != actual
   1551     {
   1552         return Err(RadrootsSp1TradeHostError::Sp1ProgramHashMismatch);
   1553     }
   1554     if let (Some(existing), Some(actual)) = (
   1555         witness.sp1_verifying_key_hash.as_deref(),
   1556         sp1_verifying_key_hash.as_deref(),
   1557     ) && existing != actual
   1558     {
   1559         return Err(RadrootsSp1TradeHostError::Sp1VerifyingKeyHashMismatch);
   1560     }
   1561 
   1562     let mut bound = witness.clone();
   1563     if let Some(hash) = sp1_program_hash {
   1564         bound.sp1_program_hash = Some(hash);
   1565     }
   1566     if let Some(hash) = sp1_verifying_key_hash {
   1567         bound.sp1_verifying_key_hash = Some(hash);
   1568     }
   1569     Ok(bound)
   1570 }
   1571 
   1572 #[cfg(feature = "sp1_verify")]
   1573 fn public_values_prefix(bytes: &[u8]) -> String {
   1574     const PREFIX_LEN: usize = 32;
   1575     hex_lower(&bytes[..bytes.len().min(PREFIX_LEN)])
   1576 }
   1577 
   1578 #[cfg(feature = "sp1_verify")]
   1579 fn execution_from_sp1_public_values(
   1580     public_values: sp1_sdk::SP1PublicValues,
   1581 ) -> Result<(Vec<u8>, RadrootsSp1TradePublicValuesExecution), RadrootsSp1TradeHostError> {
   1582     let committed_public_values = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
   1583         let mut public_values = public_values;
   1584         public_values.read::<Vec<u8>>()
   1585     }))
   1586     .map_err(|_| {
   1587         RadrootsSp1TradeHostError::Sp1PublicValuesDecode(
   1588             "SP1 public values stream did not contain canonical bytes".to_string(),
   1589         )
   1590     })?;
   1591     let decoded: radroots_sp1_guest_trade::RadrootsSp1TradeProofPublicValues =
   1592         serde_json::from_slice(&committed_public_values).map_err(|error| {
   1593             RadrootsSp1TradeHostError::Sp1PublicValuesDecode(format!(
   1594                 "{error}; public values bytes={}; prefix={}",
   1595                 committed_public_values.len(),
   1596                 public_values_prefix(&committed_public_values)
   1597             ))
   1598         })?;
   1599     let canonical_public_values = radroots_sp1_guest_trade::canonical_public_values_bytes(&decoded)
   1600         .map_err(|error| RadrootsSp1TradeHostError::Sp1PublicValuesDecode(error.to_string()))?;
   1601     let public_values_hash = radroots_sp1_guest_trade::public_values_hash_hex(&decoded)?;
   1602     Ok((
   1603         committed_public_values,
   1604         RadrootsSp1TradePublicValuesExecution {
   1605             public_values: decoded,
   1606             public_values_hash,
   1607             canonical_public_values,
   1608         },
   1609     ))
   1610 }
   1611 
   1612 #[cfg(all(feature = "sp1_proving", radroots_sp1_guest_elf))]
   1613 fn sp1_proof_mode(
   1614     mode: RadrootsSp1TradeProofMode,
   1615 ) -> Result<sp1_sdk::SP1ProofMode, RadrootsSp1TradeHostError> {
   1616     match mode {
   1617         RadrootsSp1TradeProofMode::None => Err(RadrootsSp1TradeHostError::Sp1ProofModeRequired),
   1618         RadrootsSp1TradeProofMode::Core => Ok(sp1_sdk::SP1ProofMode::Core),
   1619         RadrootsSp1TradeProofMode::Compressed => Ok(sp1_sdk::SP1ProofMode::Compressed),
   1620         RadrootsSp1TradeProofMode::Groth16 => Ok(sp1_sdk::SP1ProofMode::Groth16),
   1621         RadrootsSp1TradeProofMode::Plonk => Ok(sp1_sdk::SP1ProofMode::Plonk),
   1622     }
   1623 }
   1624 
   1625 #[cfg(feature = "sp1_verify")]
   1626 fn artifact_proof_mode(
   1627     artifact: &RadrootsSp1TradeProofArtifact,
   1628 ) -> Result<RadrootsSp1TradeProofMode, RadrootsSp1TradeHostError> {
   1629     match artifact.system {
   1630         RadrootsValidationReceiptProofSystem::None => {
   1631             Err(RadrootsSp1TradeHostError::Sp1ProofModeRequired)
   1632         }
   1633         RadrootsValidationReceiptProofSystem::Sp1Core => Ok(RadrootsSp1TradeProofMode::Core),
   1634         RadrootsValidationReceiptProofSystem::Sp1Compressed => {
   1635             Ok(RadrootsSp1TradeProofMode::Compressed)
   1636         }
   1637         RadrootsValidationReceiptProofSystem::Sp1Groth16 => Ok(RadrootsSp1TradeProofMode::Groth16),
   1638         RadrootsValidationReceiptProofSystem::Sp1Plonk => Ok(RadrootsSp1TradeProofMode::Plonk),
   1639     }
   1640 }
   1641 
   1642 #[cfg(feature = "sp1_verify")]
   1643 fn decode_sp1_proof_artifact(
   1644     artifact: &RadrootsSp1TradeProofArtifact,
   1645 ) -> Result<sp1_sdk::SP1ProofWithPublicValues, RadrootsSp1TradeHostError> {
   1646     let envelope = decode_proof_envelope(artifact)?;
   1647     decode_sp1_proof_envelope(&envelope)
   1648 }
   1649 
   1650 #[cfg(feature = "sp1_verify")]
   1651 fn decode_sp1_proof_envelope(
   1652     envelope: &RadrootsSp1TradeProofEnvelope,
   1653 ) -> Result<sp1_sdk::SP1ProofWithPublicValues, RadrootsSp1TradeHostError> {
   1654     let proof_bytes = proof_content_bytes_from_envelope(envelope)?;
   1655     bincode::deserialize::<sp1_sdk::SP1ProofWithPublicValues>(&proof_bytes)
   1656         .map_err(|error| RadrootsSp1TradeHostError::Sp1ProofMaterialDecode(error.to_string()))
   1657 }
   1658 
   1659 #[cfg(feature = "sp1_verify")]
   1660 fn decode_sp1_verifying_key_envelope(
   1661     envelope: &RadrootsSp1TradeProofEnvelope,
   1662 ) -> Result<sp1_sdk::SP1VerifyingKey, RadrootsSp1TradeHostError> {
   1663     if envelope.sp1_verifying_key_codec != RADROOTS_SP1_TRADE_VERIFYING_KEY_CODEC {
   1664         return Err(RadrootsSp1TradeHostError::Sp1ProofMaterialDecode(
   1665             "proof envelope verifying key codec mismatch".to_owned(),
   1666         ));
   1667     }
   1668     let verifying_key_bytes = base64::engine::general_purpose::STANDARD
   1669         .decode(envelope.sp1_verifying_key_base64.as_str())
   1670         .map_err(|error| RadrootsSp1TradeHostError::Sp1ProofMaterialDecode(error.to_string()))?;
   1671     bincode::deserialize::<sp1_sdk::SP1VerifyingKey>(&verifying_key_bytes)
   1672         .map_err(|error| RadrootsSp1TradeHostError::Sp1ProofMaterialDecode(error.to_string()))
   1673 }
   1674 
   1675 #[cfg(feature = "sp1_verify")]
   1676 fn sp1_proof_material_is_real(proof: &sp1_sdk::SP1Proof) -> bool {
   1677     match proof {
   1678         sp1_sdk::SP1Proof::Core(chunks) => !chunks.is_empty(),
   1679         sp1_sdk::SP1Proof::Compressed(_) => true,
   1680         sp1_sdk::SP1Proof::Groth16(proof) => !proof.encoded_proof.is_empty(),
   1681         sp1_sdk::SP1Proof::Plonk(proof) => !proof.encoded_proof.is_empty(),
   1682     }
   1683 }
   1684 
   1685 #[cfg(feature = "sp1_verify")]
   1686 fn sp1_proof_matches_mode(proof: &sp1_sdk::SP1Proof, mode: RadrootsSp1TradeProofMode) -> bool {
   1687     matches!(
   1688         (proof, mode),
   1689         (sp1_sdk::SP1Proof::Core(_), RadrootsSp1TradeProofMode::Core)
   1690             | (
   1691                 sp1_sdk::SP1Proof::Compressed(_),
   1692                 RadrootsSp1TradeProofMode::Compressed
   1693             )
   1694             | (
   1695                 sp1_sdk::SP1Proof::Groth16(_),
   1696                 RadrootsSp1TradeProofMode::Groth16
   1697             )
   1698             | (
   1699                 sp1_sdk::SP1Proof::Plonk(_),
   1700                 RadrootsSp1TradeProofMode::Plonk
   1701             )
   1702     )
   1703 }
   1704 
   1705 #[derive(Serialize)]
   1706 struct ProofDigestMaterial<'a> {
   1707     canonical_public_values: &'a [u8],
   1708     mode: Option<&'a str>,
   1709     program_hash: Option<&'a str>,
   1710     proof_reference: Option<&'a str>,
   1711     public_values_hash: &'a str,
   1712     system: &'a str,
   1713     verifying_key_hash: Option<&'a str>,
   1714 }
   1715 
   1716 #[derive(Serialize)]
   1717 struct ProofEnvelopeDigestMaterial<'a> {
   1718     schema_version: u32,
   1719     sp1_version_line: &'a str,
   1720     proof_system: &'a str,
   1721     proof_mode: &'a str,
   1722     proof_codec: &'a str,
   1723     proof_content_hash: &'a str,
   1724     public_values_hash: &'a str,
   1725     canonical_public_values_hash: &'a str,
   1726     sp1_program_hash: &'a str,
   1727     sp1_verifying_key_hash: &'a str,
   1728     sp1_verifying_key_codec: &'a str,
   1729     sp1_verifying_key_base64: &'a str,
   1730     receipt_type: &'a str,
   1731     receipt_result: &'a str,
   1732     listing_event_id: &'a str,
   1733     root_event_id: &'a str,
   1734     target_event_id: &'a str,
   1735     event_set_root: &'a str,
   1736     previous_state_root: &'a str,
   1737     new_state_root: &'a str,
   1738     changed_records_root: &'a str,
   1739     error_bitmap: &'a str,
   1740 }
   1741 
   1742 #[cfg(test)]
   1743 #[cfg_attr(coverage_nightly, coverage(off))]
   1744 mod tests {
   1745     use super::{
   1746         RadrootsSp1TradeHostError, RadrootsSp1TradeProofMode, generate_order_acceptance_proof,
   1747         validation_receipt_for_order_acceptance_proof, validation_receipt_result_label,
   1748         verify_order_acceptance_proof_artifact_structure,
   1749     };
   1750     use base64::Engine;
   1751     use radroots_events::{RadrootsNostrEvent, kinds::KIND_TRADE_VALIDATION_RECEIPT};
   1752     use radroots_sp1_guest_trade::{
   1753         RADROOTS_SP1_TRADE_KIND_LISTING, RADROOTS_SP1_TRADE_KIND_ORDER_DECISION,
   1754         RADROOTS_SP1_TRADE_KIND_ORDER_REQUEST, RADROOTS_SP1_TRADE_ORDER_ACCEPTANCE_PROOF_TARGET,
   1755         RADROOTS_SP1_TRADE_PROTOCOL_VERSION, RADROOTS_SP1_TRADE_REDUCER_PROGRAM_HASH,
   1756         RADROOTS_SP1_TRADE_WITNESS_VERSION, RadrootsSp1TradeCanonicalEventEvidence,
   1757         RadrootsSp1TradeEventEvidenceRole, RadrootsSp1TradeEventWorkflowPosition,
   1758         RadrootsSp1TradeInventoryBinWitness, RadrootsSp1TradeInventoryCommitmentWitness,
   1759         RadrootsSp1TradeOrderAcceptanceWitness, RadrootsSp1TradeOrderDecisionEventWitness,
   1760         RadrootsSp1TradeOrderDecisionWitness, RadrootsSp1TradeOrderItemWitness,
   1761         RadrootsSp1TradeOrderRequestWitness, RadrootsSp1TradeProofResult,
   1762         RadrootsSp1TradePublicValuesExecution,
   1763     };
   1764     use radroots_trade::validation_receipt::{
   1765         RadrootsValidationReceiptExpectedBinding, RadrootsValidationReceiptProof,
   1766         RadrootsValidationReceiptProofSystem, RadrootsValidationReceiptResult,
   1767         RadrootsValidationReceiptType, validation_receipt_event_build,
   1768         verify_validation_receipt_event,
   1769     };
   1770     #[cfg(feature = "sp1_verify")]
   1771     use serde::Deserialize;
   1772 
   1773     fn witness() -> RadrootsSp1TradeOrderAcceptanceWitness {
   1774         RadrootsSp1TradeOrderAcceptanceWitness {
   1775             witness_version: RADROOTS_SP1_TRADE_WITNESS_VERSION,
   1776             proof_target: RADROOTS_SP1_TRADE_ORDER_ACCEPTANCE_PROOF_TARGET.to_string(),
   1777             listing_event_id: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
   1778                 .to_string(),
   1779             request_event_id: "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
   1780                 .to_string(),
   1781             decision_event_id: "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"
   1782                 .to_string(),
   1783             event_evidence: event_evidence(),
   1784             request: request(2),
   1785             decision: decision(2),
   1786             inventory_bins: vec![RadrootsSp1TradeInventoryBinWitness {
   1787                 bin_id: "bin-1".to_string(),
   1788                 listing_capacity: 5,
   1789                 previous_reserved: 1,
   1790             }],
   1791             inventory_sequence: 7,
   1792             previous_state_root: None,
   1793             reducer_program_hash: RADROOTS_SP1_TRADE_REDUCER_PROGRAM_HASH.to_string(),
   1794             radroots_protocol_version: RADROOTS_SP1_TRADE_PROTOCOL_VERSION.to_string(),
   1795             sp1_program_hash: Some(
   1796                 "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".to_string(),
   1797             ),
   1798             sp1_verifying_key_hash: Some(
   1799                 "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb".to_string(),
   1800             ),
   1801         }
   1802     }
   1803 
   1804     #[cfg(all(feature = "sp1_proving", radroots_sp1_guest_elf))]
   1805     fn witness_without_sp1_identity() -> RadrootsSp1TradeOrderAcceptanceWitness {
   1806         let mut input = witness();
   1807         input.sp1_program_hash = None;
   1808         input.sp1_verifying_key_hash = None;
   1809         input
   1810     }
   1811 
   1812     fn event_evidence() -> Vec<RadrootsSp1TradeCanonicalEventEvidence> {
   1813         vec![
   1814             RadrootsSp1TradeCanonicalEventEvidence {
   1815                 event_id: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
   1816                     .to_string(),
   1817                 signer_pubkey: "1111111111111111111111111111111111111111111111111111111111111111"
   1818                     .to_string(),
   1819                 kind: RADROOTS_SP1_TRADE_KIND_LISTING,
   1820                 canonical_event_hash:
   1821                     "0x1010101010101010101010101010101010101010101010101010101010101010".to_string(),
   1822                 signature_hash:
   1823                     "0x1111111111111111111111111111111111111111111111111111111111111111".to_string(),
   1824                 preverified_signature: true,
   1825                 role: RadrootsSp1TradeEventEvidenceRole::Seller,
   1826                 workflow_position: RadrootsSp1TradeEventWorkflowPosition::Listing,
   1827                 content_hash: "0x1212121212121212121212121212121212121212121212121212121212121212"
   1828                     .to_string(),
   1829                 tags_hash: "0x1313131313131313131313131313131313131313131313131313131313131313"
   1830                     .to_string(),
   1831                 ordering_key: "001:listing".to_string(),
   1832             },
   1833             RadrootsSp1TradeCanonicalEventEvidence {
   1834                 event_id: "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
   1835                     .to_string(),
   1836                 signer_pubkey: "2222222222222222222222222222222222222222222222222222222222222222"
   1837                     .to_string(),
   1838                 kind: RADROOTS_SP1_TRADE_KIND_ORDER_REQUEST,
   1839                 canonical_event_hash:
   1840                     "0x2020202020202020202020202020202020202020202020202020202020202020".to_string(),
   1841                 signature_hash:
   1842                     "0x2121212121212121212121212121212121212121212121212121212121212121".to_string(),
   1843                 preverified_signature: true,
   1844                 role: RadrootsSp1TradeEventEvidenceRole::Buyer,
   1845                 workflow_position: RadrootsSp1TradeEventWorkflowPosition::OrderRequest,
   1846                 content_hash: "0x2222222222222222222222222222222222222222222222222222222222222222"
   1847                     .to_string(),
   1848                 tags_hash: "0x2323232323232323232323232323232323232323232323232323232323232323"
   1849                     .to_string(),
   1850                 ordering_key: "002:order_request".to_string(),
   1851             },
   1852             RadrootsSp1TradeCanonicalEventEvidence {
   1853                 event_id: "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"
   1854                     .to_string(),
   1855                 signer_pubkey: "1111111111111111111111111111111111111111111111111111111111111111"
   1856                     .to_string(),
   1857                 kind: RADROOTS_SP1_TRADE_KIND_ORDER_DECISION,
   1858                 canonical_event_hash:
   1859                     "0x3030303030303030303030303030303030303030303030303030303030303030".to_string(),
   1860                 signature_hash:
   1861                     "0x3131313131313131313131313131313131313131313131313131313131313131".to_string(),
   1862                 preverified_signature: true,
   1863                 role: RadrootsSp1TradeEventEvidenceRole::Seller,
   1864                 workflow_position: RadrootsSp1TradeEventWorkflowPosition::OrderDecision,
   1865                 content_hash: "0x3232323232323232323232323232323232323232323232323232323232323232"
   1866                     .to_string(),
   1867                 tags_hash: "0x3333333333333333333333333333333333333333333333333333333333333333"
   1868                     .to_string(),
   1869                 ordering_key: "003:order_decision".to_string(),
   1870             },
   1871         ]
   1872     }
   1873 
   1874     fn request(bin_count: u32) -> RadrootsSp1TradeOrderRequestWitness {
   1875         RadrootsSp1TradeOrderRequestWitness {
   1876             order_id: "order-1".to_string(),
   1877             listing_addr:
   1878                 "30402:1111111111111111111111111111111111111111111111111111111111111111:listing-1"
   1879                     .to_string(),
   1880             buyer_pubkey: "2222222222222222222222222222222222222222222222222222222222222222"
   1881                 .to_string(),
   1882             seller_pubkey: "1111111111111111111111111111111111111111111111111111111111111111"
   1883                 .to_string(),
   1884             items: vec![RadrootsSp1TradeOrderItemWitness {
   1885                 bin_id: "bin-1".to_string(),
   1886                 bin_count,
   1887             }],
   1888         }
   1889     }
   1890 
   1891     fn decision(bin_count: u32) -> RadrootsSp1TradeOrderDecisionEventWitness {
   1892         RadrootsSp1TradeOrderDecisionEventWitness {
   1893             order_id: "order-1".to_string(),
   1894             listing_addr:
   1895                 "30402:1111111111111111111111111111111111111111111111111111111111111111:listing-1"
   1896                     .to_string(),
   1897             buyer_pubkey: "2222222222222222222222222222222222222222222222222222222222222222"
   1898                 .to_string(),
   1899             seller_pubkey: "1111111111111111111111111111111111111111111111111111111111111111"
   1900                 .to_string(),
   1901             decision: RadrootsSp1TradeOrderDecisionWitness::Accepted {
   1902                 inventory_commitments: vec![RadrootsSp1TradeInventoryCommitmentWitness {
   1903                     bin_id: "bin-1".to_string(),
   1904                     bin_count,
   1905                 }],
   1906             },
   1907         }
   1908     }
   1909 
   1910     fn inline_sp1_artifact_fixture() -> (
   1911         super::RadrootsSp1TradeProofBundle,
   1912         super::RadrootsSp1TradeProofEnvelope,
   1913     ) {
   1914         let bundle = generate_order_acceptance_proof(&witness(), RadrootsSp1TradeProofMode::None)
   1915             .expect("proof bundle");
   1916         let execution = &bundle.execution;
   1917         let proof_bytes = b"deterministic proof bytes";
   1918         let verifying_key_bytes = b"deterministic verifying key bytes";
   1919         let public_values_hash = execution.public_values_hash.clone();
   1920         let canonical_public_values = execution.canonical_public_values.clone();
   1921         let result = execution.public_values.result;
   1922         let program_hash = execution
   1923             .public_values
   1924             .sp1_program_hash
   1925             .clone()
   1926             .expect("program hash");
   1927         let verifying_key_hash = execution
   1928             .public_values
   1929             .sp1_verifying_key_hash
   1930             .clone()
   1931             .expect("verifying key hash");
   1932         let listing_event_id = execution
   1933             .public_values
   1934             .listing_event_id
   1935             .clone()
   1936             .expect("listing event id");
   1937         let root_event_id = execution
   1938             .public_values
   1939             .root_event_id
   1940             .clone()
   1941             .expect("root event id");
   1942         let target_event_id = execution
   1943             .public_values
   1944             .target_event_id
   1945             .clone()
   1946             .expect("target event id");
   1947         let event_set_root = execution.public_values.event_set_root.clone();
   1948         let previous_state_root = execution.public_values.previous_state_root.clone();
   1949         let new_state_root = execution.public_values.new_state_root.clone();
   1950         let changed_records_root = execution.public_values.changed_records_root.clone();
   1951         let error_bitmap = execution.public_values.error_bitmap.clone();
   1952         let mut envelope = super::RadrootsSp1TradeProofEnvelope {
   1953             schema_version: super::RADROOTS_SP1_TRADE_PROOF_ARTIFACT_SCHEMA_VERSION,
   1954             sp1_version_line: super::RADROOTS_SP1_TRADE_SP1_VERSION_LINE.to_string(),
   1955             proof_system: RadrootsValidationReceiptProofSystem::Sp1Core
   1956                 .as_str()
   1957                 .to_string(),
   1958             proof_mode: "core".to_string(),
   1959             proof_codec: super::RADROOTS_SP1_TRADE_PROOF_CODEC.to_string(),
   1960             proof_content_hash: super::hash_bytes("radroots:sp1-proof-content:v1", proof_bytes),
   1961             proof_digest: String::new(),
   1962             public_values_hash: public_values_hash.clone(),
   1963             canonical_public_values_hash: super::hash_bytes(
   1964                 "radroots:sp1-canonical-public-values:v1",
   1965                 &canonical_public_values,
   1966             ),
   1967             sp1_program_hash: program_hash.clone(),
   1968             sp1_verifying_key_hash: verifying_key_hash.clone(),
   1969             sp1_verifying_key_codec: super::RADROOTS_SP1_TRADE_VERIFYING_KEY_CODEC.to_string(),
   1970             sp1_verifying_key_base64: base64::engine::general_purpose::STANDARD
   1971                 .encode(verifying_key_bytes),
   1972             receipt_type: RadrootsValidationReceiptType::TradeTransition
   1973                 .as_str()
   1974                 .to_string(),
   1975             receipt_result: super::validation_receipt_result_label(
   1976                 super::validation_receipt_result_from_public_values(result),
   1977             )
   1978             .to_string(),
   1979             listing_event_id,
   1980             root_event_id,
   1981             target_event_id,
   1982             event_set_root,
   1983             previous_state_root,
   1984             new_state_root,
   1985             changed_records_root,
   1986             error_bitmap,
   1987             proof_content_base64: base64::engine::general_purpose::STANDARD.encode(proof_bytes),
   1988         };
   1989         envelope.proof_digest = super::proof_digest_for_envelope(&envelope).expect("digest");
   1990         let envelope_json = serde_json::to_vec(&envelope).expect("envelope json");
   1991         let envelope_base64 = base64::engine::general_purpose::STANDARD.encode(envelope_json);
   1992         let mut bundle = bundle;
   1993         bundle.proof = super::RadrootsSp1TradeProofArtifact {
   1994             inline_proof_base64: Some(envelope_base64),
   1995             mode: Some("core".to_string()),
   1996             program_hash: Some(program_hash),
   1997             proof_digest: envelope.proof_digest.clone(),
   1998             proof_reference: None,
   1999             public_values_hash,
   2000             system: RadrootsValidationReceiptProofSystem::Sp1Core,
   2001             verifying_key_hash: Some(verifying_key_hash),
   2002         };
   2003         (bundle, envelope)
   2004     }
   2005 
   2006     fn refresh_proof_envelope_digest(envelope: &mut super::RadrootsSp1TradeProofEnvelope) {
   2007         envelope.proof_digest = super::proof_digest_for_envelope(envelope).expect("digest");
   2008     }
   2009 
   2010     fn assert_missing_none_proof_material(
   2011         execution: &RadrootsSp1TradePublicValuesExecution,
   2012         mut artifact: super::RadrootsSp1TradeProofArtifact,
   2013     ) {
   2014         artifact.proof_digest =
   2015             super::proof_digest_for_execution(execution, &artifact).expect("digest");
   2016         assert_eq!(
   2017             verify_order_acceptance_proof_artifact_structure(execution, &artifact)
   2018                 .expect_err("none material"),
   2019             RadrootsSp1TradeHostError::MissingProofMaterial
   2020         );
   2021     }
   2022 
   2023     #[test]
   2024     fn enum_labels_and_display_surfaces_are_complete() {
   2025         let modes = [
   2026             (
   2027                 RadrootsSp1TradeProofMode::None,
   2028                 None,
   2029                 RadrootsValidationReceiptProofSystem::None,
   2030             ),
   2031             (
   2032                 RadrootsSp1TradeProofMode::Core,
   2033                 Some("core"),
   2034                 RadrootsValidationReceiptProofSystem::Sp1Core,
   2035             ),
   2036             (
   2037                 RadrootsSp1TradeProofMode::Compressed,
   2038                 Some("compressed"),
   2039                 RadrootsValidationReceiptProofSystem::Sp1Compressed,
   2040             ),
   2041             (
   2042                 RadrootsSp1TradeProofMode::Groth16,
   2043                 Some("groth16"),
   2044                 RadrootsValidationReceiptProofSystem::Sp1Groth16,
   2045             ),
   2046             (
   2047                 RadrootsSp1TradeProofMode::Plonk,
   2048                 Some("plonk"),
   2049                 RadrootsValidationReceiptProofSystem::Sp1Plonk,
   2050             ),
   2051         ];
   2052         for (mode, label, system) in modes {
   2053             assert_eq!(mode.proof_system(), system);
   2054             assert_eq!(mode.mode_label(), label);
   2055             if let Some(label) = label {
   2056                 assert_eq!(RadrootsSp1TradeProofMode::from_label(label), Some(mode));
   2057             }
   2058         }
   2059         assert_eq!(
   2060             RadrootsSp1TradeProofMode::from_label("none"),
   2061             Some(RadrootsSp1TradeProofMode::None)
   2062         );
   2063         assert_eq!(RadrootsSp1TradeProofMode::from_label("legacy"), None);
   2064 
   2065         for backend in [
   2066             super::RadrootsSp1TradeProverBackend::Disabled,
   2067             super::RadrootsSp1TradeProverBackend::DeterministicNone,
   2068             super::RadrootsSp1TradeProverBackend::LocalExecute,
   2069             super::RadrootsSp1TradeProverBackend::LocalCpuProve,
   2070             super::RadrootsSp1TradeProverBackend::LocalCudaProve,
   2071             super::RadrootsSp1TradeProverBackend::RemoteHttpProve,
   2072         ] {
   2073             assert_eq!(
   2074                 super::RadrootsSp1TradeProverBackend::from_label(backend.as_str()),
   2075                 Some(backend)
   2076             );
   2077             assert_eq!(backend.to_string(), backend.as_str());
   2078         }
   2079         assert_eq!(
   2080             super::RadrootsSp1TradeProverBackend::from_label("legacy"),
   2081             None
   2082         );
   2083 
   2084         for engine in [
   2085             super::RadrootsSp1TradeProofEngine::Cpu,
   2086             super::RadrootsSp1TradeProofEngine::Cuda,
   2087         ] {
   2088             assert_eq!(
   2089                 super::RadrootsSp1TradeProofEngine::from_label(engine.as_str()),
   2090                 Some(engine)
   2091             );
   2092             assert_eq!(engine.to_string(), engine.as_str());
   2093         }
   2094         assert_eq!(super::RadrootsSp1TradeProofEngine::from_label("tpu"), None);
   2095         assert_eq!(
   2096             super::RadrootsSp1TradeWorkerResultStatus::Succeeded.to_string(),
   2097             "succeeded"
   2098         );
   2099         assert_eq!(
   2100             super::RadrootsSp1TradeWorkerRole::NonAuthoritativeProver.to_string(),
   2101             "non_authoritative_prover"
   2102         );
   2103         let artifact = generate_order_acceptance_proof(&witness(), RadrootsSp1TradeProofMode::None)
   2104             .expect("proof bundle")
   2105             .proof;
   2106         assert_eq!(
   2107             super::RadrootsSp1TradeResolvedProofArtifact::inline(artifact.clone()).artifact,
   2108             artifact
   2109         );
   2110         assert_eq!(
   2111             RadrootsSp1TradeHostError::from(
   2112                 radroots_sp1_guest_trade::RadrootsSp1TradeGuestError::UnsupportedProofTarget
   2113             ),
   2114             RadrootsSp1TradeHostError::Guest
   2115         );
   2116     }
   2117 
   2118     #[test]
   2119     fn inline_proof_envelope_helpers_validate_digest_and_references() {
   2120         let (bundle, envelope) = inline_sp1_artifact_fixture();
   2121         verify_order_acceptance_proof_artifact_structure(&bundle.execution, &bundle.proof)
   2122             .expect("inline artifact structure");
   2123         let envelope_base64 = bundle
   2124             .proof
   2125             .inline_proof_base64
   2126             .as_deref()
   2127             .expect("inline envelope");
   2128         let decoded = super::decode_proof_envelope_base64(envelope_base64).expect("decode");
   2129         assert_eq!(decoded, envelope);
   2130         assert_eq!(
   2131             super::proof_content_bytes_from_envelope(&decoded).expect("content"),
   2132             b"deterministic proof bytes"
   2133         );
   2134         let reference =
   2135             super::proof_reference_for_proof_envelope_base64(envelope_base64).expect("reference");
   2136         assert_eq!(
   2137             super::proof_reference_digest(reference.as_str())
   2138                 .expect("digest")
   2139                 .len(),
   2140             64
   2141         );
   2142         assert!(matches!(
   2143             super::proof_reference_for_proof_envelope_base64("not-base64"),
   2144             Err(RadrootsSp1TradeHostError::Sp1ProofMaterialDecode(_))
   2145         ));
   2146         assert!(matches!(
   2147             super::decode_proof_envelope_base64(
   2148                 base64::engine::general_purpose::STANDARD
   2149                     .encode(b"not json")
   2150                     .as_str()
   2151             ),
   2152             Err(RadrootsSp1TradeHostError::Sp1ProofMaterialDecode(_))
   2153         ));
   2154         assert_eq!(
   2155             super::proof_reference_digest("legacy-reference").expect_err("prefix"),
   2156             RadrootsSp1TradeHostError::InvalidSp1ProofReference
   2157         );
   2158     }
   2159 
   2160     #[test]
   2161     fn proof_envelope_metadata_mismatches_are_reported_by_field_family() {
   2162         let (bundle, envelope) = inline_sp1_artifact_fixture();
   2163         let execution = &bundle.execution;
   2164         let artifact = &bundle.proof;
   2165 
   2166         let mut bad = envelope.clone();
   2167         bad.schema_version += 1;
   2168         assert!(matches!(
   2169             super::verify_proof_envelope(execution, artifact, &bad),
   2170             Err(RadrootsSp1TradeHostError::Sp1ProofMaterialDecode(_))
   2171         ));
   2172 
   2173         let metadata_cases: [fn(&mut super::RadrootsSp1TradeProofEnvelope); 9] = [
   2174             |envelope: &mut super::RadrootsSp1TradeProofEnvelope| {
   2175                 envelope.sp1_version_line = "legacy".to_string();
   2176             },
   2177             |envelope: &mut super::RadrootsSp1TradeProofEnvelope| {
   2178                 envelope.proof_codec = "legacy".to_string();
   2179             },
   2180             |envelope: &mut super::RadrootsSp1TradeProofEnvelope| {
   2181                 envelope.proof_system = "legacy".to_string();
   2182             },
   2183             |envelope: &mut super::RadrootsSp1TradeProofEnvelope| {
   2184                 envelope.proof_mode = "compressed".to_string();
   2185             },
   2186             |envelope: &mut super::RadrootsSp1TradeProofEnvelope| {
   2187                 envelope.public_values_hash =
   2188                     "0xdddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd"
   2189                         .to_string();
   2190             },
   2191             |envelope: &mut super::RadrootsSp1TradeProofEnvelope| {
   2192                 envelope.sp1_program_hash =
   2193                     "0xdddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd"
   2194                         .to_string();
   2195             },
   2196             |envelope: &mut super::RadrootsSp1TradeProofEnvelope| {
   2197                 envelope.sp1_verifying_key_hash =
   2198                     "0xdddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd"
   2199                         .to_string();
   2200             },
   2201             |envelope: &mut super::RadrootsSp1TradeProofEnvelope| {
   2202                 envelope.sp1_verifying_key_codec = "legacy".to_string();
   2203             },
   2204             |envelope: &mut super::RadrootsSp1TradeProofEnvelope| {
   2205                 envelope.sp1_verifying_key_base64.clear();
   2206             },
   2207         ];
   2208         for apply in metadata_cases {
   2209             let mut bad = envelope.clone();
   2210             apply(&mut bad);
   2211             assert!(matches!(
   2212                 super::verify_proof_envelope(execution, artifact, &bad),
   2213                 Err(RadrootsSp1TradeHostError::Sp1ProofMaterialDecode(_))
   2214             ));
   2215         }
   2216 
   2217         let mut bad = envelope.clone();
   2218         bad.proof_digest =
   2219             "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".to_string();
   2220         assert_eq!(
   2221             super::verify_proof_envelope(execution, artifact, &bad).expect_err("digest"),
   2222             RadrootsSp1TradeHostError::ProofDigestMismatch
   2223         );
   2224 
   2225         let mut bad = envelope.clone();
   2226         bad.proof_content_hash =
   2227             "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".to_string();
   2228         refresh_proof_envelope_digest(&mut bad);
   2229         assert!(matches!(
   2230             super::verify_proof_envelope(execution, artifact, &bad),
   2231             Err(RadrootsSp1TradeHostError::Sp1ProofMaterialDecode(_))
   2232         ));
   2233 
   2234         let mut bad = envelope.clone();
   2235         bad.proof_content_base64 = "not-base64".to_string();
   2236         refresh_proof_envelope_digest(&mut bad);
   2237         assert!(matches!(
   2238             super::verify_proof_envelope(execution, artifact, &bad),
   2239             Err(RadrootsSp1TradeHostError::Sp1ProofMaterialDecode(_))
   2240         ));
   2241 
   2242         let mut bad = envelope.clone();
   2243         bad.canonical_public_values_hash =
   2244             "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".to_string();
   2245         refresh_proof_envelope_digest(&mut bad);
   2246         assert_eq!(
   2247             super::verify_proof_envelope(execution, artifact, &bad).expect_err("public values"),
   2248             RadrootsSp1TradeHostError::PublicValuesHashMismatch
   2249         );
   2250 
   2251         let envelope_cases: [(&str, fn(&mut super::RadrootsSp1TradeProofEnvelope)); 10] = [
   2252             (
   2253                 "receipt_type",
   2254                 |envelope: &mut super::RadrootsSp1TradeProofEnvelope| {
   2255                     envelope.receipt_type = "legacy".to_string();
   2256                 },
   2257             ),
   2258             (
   2259                 "result",
   2260                 |envelope: &mut super::RadrootsSp1TradeProofEnvelope| {
   2261                     envelope.receipt_result = "invalid".to_string();
   2262                 },
   2263             ),
   2264             (
   2265                 "listing_event_id",
   2266                 |envelope: &mut super::RadrootsSp1TradeProofEnvelope| {
   2267                     envelope.listing_event_id =
   2268                         "dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd"
   2269                             .to_string();
   2270                 },
   2271             ),
   2272             (
   2273                 "root_event_id",
   2274                 |envelope: &mut super::RadrootsSp1TradeProofEnvelope| {
   2275                     envelope.root_event_id =
   2276                         "dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd"
   2277                             .to_string();
   2278                 },
   2279             ),
   2280             (
   2281                 "target_event_id",
   2282                 |envelope: &mut super::RadrootsSp1TradeProofEnvelope| {
   2283                     envelope.target_event_id =
   2284                         "dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd"
   2285                             .to_string();
   2286                 },
   2287             ),
   2288             (
   2289                 "state_roots",
   2290                 |envelope: &mut super::RadrootsSp1TradeProofEnvelope| {
   2291                     envelope.event_set_root =
   2292                         "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
   2293                             .to_string();
   2294                 },
   2295             ),
   2296             (
   2297                 "state_roots",
   2298                 |envelope: &mut super::RadrootsSp1TradeProofEnvelope| {
   2299                     envelope.previous_state_root =
   2300                         "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
   2301                             .to_string();
   2302                 },
   2303             ),
   2304             (
   2305                 "state_roots",
   2306                 |envelope: &mut super::RadrootsSp1TradeProofEnvelope| {
   2307                     envelope.new_state_root =
   2308                         "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
   2309                             .to_string();
   2310                 },
   2311             ),
   2312             (
   2313                 "state_roots",
   2314                 |envelope: &mut super::RadrootsSp1TradeProofEnvelope| {
   2315                     envelope.changed_records_root =
   2316                         "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
   2317                             .to_string();
   2318                 },
   2319             ),
   2320             (
   2321                 "state_roots",
   2322                 |envelope: &mut super::RadrootsSp1TradeProofEnvelope| {
   2323                     envelope.error_bitmap =
   2324                         "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
   2325                             .to_string();
   2326                 },
   2327             ),
   2328         ];
   2329         for (field, apply) in envelope_cases {
   2330             let mut bad = envelope.clone();
   2331             apply(&mut bad);
   2332             refresh_proof_envelope_digest(&mut bad);
   2333             assert_eq!(
   2334                 super::verify_proof_envelope(execution, artifact, &bad)
   2335                     .expect_err("binding mismatch"),
   2336                 RadrootsSp1TradeHostError::ValidationReceiptBindingMismatch(field)
   2337             );
   2338         }
   2339     }
   2340 
   2341     #[test]
   2342     fn proof_artifact_structure_rejects_material_edge_cases() {
   2343         let (bundle, _) = inline_sp1_artifact_fixture();
   2344         let execution = &bundle.execution;
   2345         let inline_proof_base64 = bundle
   2346             .proof
   2347             .inline_proof_base64
   2348             .clone()
   2349             .expect("inline envelope");
   2350 
   2351         let base_none_artifact =
   2352             generate_order_acceptance_proof(&witness(), RadrootsSp1TradeProofMode::None)
   2353                 .expect("proof bundle")
   2354                 .proof;
   2355 
   2356         let mut artifact = base_none_artifact.clone();
   2357         artifact.proof_digest =
   2358             "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".to_string();
   2359         assert_eq!(
   2360             verify_order_acceptance_proof_artifact_structure(execution, &artifact)
   2361                 .expect_err("proof digest"),
   2362             RadrootsSp1TradeHostError::ProofDigestMismatch
   2363         );
   2364 
   2365         let mut artifact = base_none_artifact.clone();
   2366         artifact.inline_proof_base64 = Some(inline_proof_base64);
   2367         assert_missing_none_proof_material(execution, artifact);
   2368 
   2369         let mut artifact = base_none_artifact.clone();
   2370         artifact.mode = Some("core".to_string());
   2371         assert_missing_none_proof_material(execution, artifact);
   2372 
   2373         let mut artifact = base_none_artifact.clone();
   2374         artifact.program_hash =
   2375             Some("0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".to_string());
   2376         assert_missing_none_proof_material(execution, artifact);
   2377 
   2378         let mut artifact = base_none_artifact.clone();
   2379         artifact.proof_reference = Some(format!("radroots-proof://sha256/{}", "1".repeat(64)));
   2380         assert_missing_none_proof_material(execution, artifact);
   2381 
   2382         let mut artifact = base_none_artifact;
   2383         artifact.verifying_key_hash =
   2384             Some("0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".to_string());
   2385         assert_missing_none_proof_material(execution, artifact);
   2386 
   2387         let mut artifact = bundle.proof.clone();
   2388         artifact.inline_proof_base64 = None;
   2389         artifact.proof_reference = None;
   2390         artifact.proof_digest =
   2391             super::proof_digest_for_execution(execution, &artifact).expect("digest");
   2392         assert_eq!(
   2393             verify_order_acceptance_proof_artifact_structure(execution, &artifact)
   2394                 .expect_err("missing sp1 material"),
   2395             RadrootsSp1TradeHostError::MissingProofMaterial
   2396         );
   2397 
   2398         let mut artifact = bundle.proof.clone();
   2399         artifact.proof_reference = Some(format!("radroots-proof://sha256/{}", "1".repeat(64)));
   2400         artifact.proof_digest =
   2401             super::proof_digest_for_execution(execution, &artifact).expect("digest");
   2402         assert_eq!(
   2403             verify_order_acceptance_proof_artifact_structure(execution, &artifact)
   2404                 .expect_err("conflict"),
   2405             RadrootsSp1TradeHostError::ProofMaterialConflict
   2406         );
   2407 
   2408         let mut artifact = bundle.proof.clone();
   2409         artifact.verifying_key_hash =
   2410             Some("0xdddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd".to_string());
   2411         artifact.proof_digest =
   2412             super::proof_digest_for_execution(execution, &artifact).expect("digest");
   2413         assert_eq!(
   2414             verify_order_acceptance_proof_artifact_structure(execution, &artifact)
   2415                 .expect_err("verifying key mismatch"),
   2416             RadrootsSp1TradeHostError::Sp1VerifyingKeyHashMismatch
   2417         );
   2418 
   2419         let invalid_reference = format!("radroots-proof://sha256/{}", "A".repeat(64));
   2420         assert_eq!(
   2421             super::proof_reference_digest(invalid_reference.as_str())
   2422                 .expect_err("uppercase digest"),
   2423             RadrootsSp1TradeHostError::InvalidSp1ProofReference
   2424         );
   2425         let err = super::referenced_order_acceptance_proof_artifact_for_execution(
   2426             execution,
   2427             RadrootsSp1TradeProofMode::None,
   2428             format!("radroots-proof://sha256/{}", "1".repeat(64)),
   2429         )
   2430         .expect_err("none reference");
   2431         assert_eq!(err, RadrootsSp1TradeHostError::Sp1ProofModeRequired);
   2432     }
   2433 
   2434     #[test]
   2435     fn validation_receipt_requires_event_bindings() {
   2436         let bundle = generate_order_acceptance_proof(&witness(), RadrootsSp1TradeProofMode::None)
   2437             .expect("proof bundle");
   2438         let cases: [(&str, fn(&mut RadrootsSp1TradePublicValuesExecution)); 3] = [
   2439             ("listing_event_id", |execution| {
   2440                 execution.public_values.listing_event_id = None;
   2441             }),
   2442             ("root_event_id", |execution| {
   2443                 execution.public_values.root_event_id = None;
   2444             }),
   2445             ("target_event_id", |execution| {
   2446                 execution.public_values.target_event_id = None;
   2447             }),
   2448         ];
   2449         for (field, clear) in cases {
   2450             let mut bundle = bundle.clone();
   2451             clear(&mut bundle.execution);
   2452             assert_eq!(
   2453                 validation_receipt_for_order_acceptance_proof(&bundle)
   2454                     .expect_err("missing binding"),
   2455                 RadrootsSp1TradeHostError::MissingReceiptBinding(field)
   2456             );
   2457         }
   2458     }
   2459 
   2460     #[test]
   2461     fn execute_public_values_and_bind_validation_receipt() {
   2462         let bundle = generate_order_acceptance_proof(&witness(), RadrootsSp1TradeProofMode::None)
   2463             .expect("proof bundle");
   2464         assert_eq!(
   2465             bundle.proof.system,
   2466             RadrootsValidationReceiptProofSystem::None
   2467         );
   2468         verify_order_acceptance_proof_artifact_structure(&bundle.execution, &bundle.proof)
   2469             .expect("proof verifies");
   2470 
   2471         let receipt =
   2472             validation_receipt_for_order_acceptance_proof(&bundle).expect("validation receipt");
   2473         assert_eq!(
   2474             receipt.public_values_hash,
   2475             bundle.execution.public_values_hash
   2476         );
   2477         assert_eq!(
   2478             receipt.event_set_root,
   2479             bundle.execution.public_values.event_set_root
   2480         );
   2481         assert_eq!(
   2482             receipt.new_state_root,
   2483             bundle.execution.public_values.new_state_root
   2484         );
   2485 
   2486         let parts = validation_receipt_event_build("order-1", &receipt).expect("event parts");
   2487         let event = RadrootsNostrEvent {
   2488             id: "dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd".to_string(),
   2489             author: "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"
   2490                 .to_string(),
   2491             created_at: 1,
   2492             kind: KIND_TRADE_VALIDATION_RECEIPT,
   2493             tags: parts.tags,
   2494             content: parts.content,
   2495             sig: "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff".to_string(),
   2496         };
   2497         verify_validation_receipt_event(
   2498             &event,
   2499             RadrootsValidationReceiptExpectedBinding {
   2500                 event_set_root: Some(&receipt.event_set_root),
   2501                 order_id: Some("order-1"),
   2502                 proof_system: Some(RadrootsValidationReceiptProofSystem::None),
   2503                 public_values_hash: Some(&receipt.public_values_hash),
   2504                 reducer_output_root: Some(&receipt.new_state_root),
   2505                 ..RadrootsValidationReceiptExpectedBinding::default()
   2506             },
   2507         )
   2508         .expect("receipt verifies");
   2509     }
   2510 
   2511     #[test]
   2512     fn proof_verifier_rejects_tampered_public_values_hash() {
   2513         let mut bundle =
   2514             generate_order_acceptance_proof(&witness(), RadrootsSp1TradeProofMode::None)
   2515                 .expect("proof bundle");
   2516         bundle.proof.public_values_hash =
   2517             "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".to_string();
   2518         let err =
   2519             verify_order_acceptance_proof_artifact_structure(&bundle.execution, &bundle.proof)
   2520                 .expect_err("tamper");
   2521         assert_eq!(err, RadrootsSp1TradeHostError::PublicValuesHashMismatch);
   2522     }
   2523 
   2524     #[test]
   2525     fn validation_receipt_result_tracks_public_values_result() {
   2526         let mut bundle =
   2527             generate_order_acceptance_proof(&witness(), RadrootsSp1TradeProofMode::None)
   2528                 .expect("proof bundle");
   2529         bundle.execution.public_values.result = RadrootsSp1TradeProofResult::Invalid;
   2530         let receipt =
   2531             validation_receipt_for_order_acceptance_proof(&bundle).expect("validation receipt");
   2532         assert_eq!(receipt.result, RadrootsValidationReceiptResult::Invalid);
   2533         assert_eq!(validation_receipt_result_label(receipt.result), "invalid");
   2534     }
   2535 
   2536     #[test]
   2537     fn sp1_modes_require_the_sp1_proving_lane() {
   2538         let err =
   2539             generate_order_acceptance_proof(&witness(), RadrootsSp1TradeProofMode::Compressed)
   2540                 .expect_err("synthetic sp1 proof");
   2541         assert_eq!(err, RadrootsSp1TradeHostError::Sp1ProofGenerationRequired);
   2542     }
   2543 
   2544     #[test]
   2545     fn sp1_artifact_program_hash_must_match_public_values_identity() {
   2546         let mut input = witness();
   2547         input.sp1_program_hash =
   2548             Some("0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".to_string());
   2549         let execution =
   2550             super::execute_order_acceptance_public_values(&input).expect("deterministic execution");
   2551         let mut artifact = super::RadrootsSp1TradeProofArtifact {
   2552             inline_proof_base64: None,
   2553             mode: Some("core".to_string()),
   2554             program_hash: Some(
   2555                 "0xdddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd".to_string(),
   2556             ),
   2557             proof_digest: String::new(),
   2558             proof_reference: Some(format!("radroots-proof://sha256/{}", "1".repeat(64))),
   2559             public_values_hash: execution.public_values_hash.clone(),
   2560             system: RadrootsValidationReceiptProofSystem::Sp1Core,
   2561             verifying_key_hash: execution.public_values.sp1_verifying_key_hash.clone(),
   2562         };
   2563         artifact.proof_digest =
   2564             super::proof_digest_for_execution(&execution, &artifact).expect("proof digest");
   2565         let err = verify_order_acceptance_proof_artifact_structure(&execution, &artifact)
   2566             .expect_err("program hash mismatch");
   2567         assert_eq!(err, RadrootsSp1TradeHostError::Sp1ProgramHashMismatch);
   2568     }
   2569 
   2570     #[test]
   2571     fn sp1_artifact_requires_public_values_sp1_identity() {
   2572         let mut input = witness();
   2573         input.sp1_program_hash = None;
   2574         let execution =
   2575             super::execute_order_acceptance_public_values(&input).expect("deterministic execution");
   2576         let mut artifact = super::RadrootsSp1TradeProofArtifact {
   2577             inline_proof_base64: None,
   2578             mode: Some("core".to_string()),
   2579             program_hash: Some(
   2580                 "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".to_string(),
   2581             ),
   2582             proof_digest: String::new(),
   2583             proof_reference: Some(format!("radroots-proof://sha256/{}", "1".repeat(64))),
   2584             public_values_hash: execution.public_values_hash.clone(),
   2585             system: RadrootsValidationReceiptProofSystem::Sp1Core,
   2586             verifying_key_hash: execution.public_values.sp1_verifying_key_hash.clone(),
   2587         };
   2588         artifact.proof_digest =
   2589             super::proof_digest_for_execution(&execution, &artifact).expect("proof digest");
   2590         let err = verify_order_acceptance_proof_artifact_structure(&execution, &artifact)
   2591             .expect_err("missing program hash");
   2592         assert_eq!(err, RadrootsSp1TradeHostError::MissingSp1ProgramHash);
   2593     }
   2594 
   2595     #[cfg(feature = "sp1_verify")]
   2596     #[tokio::test]
   2597     async fn verify_order_acceptance_validation_receipt_inline_sp1_proof_rejects_raw_material() {
   2598         let bundle = generate_order_acceptance_proof(&witness(), RadrootsSp1TradeProofMode::None)
   2599             .expect("proof bundle");
   2600         let mut receipt =
   2601             validation_receipt_for_order_acceptance_proof(&bundle).expect("validation receipt");
   2602         receipt.proof = RadrootsValidationReceiptProof {
   2603             inline_proof_base64: Some("cHJvb2Y=".to_string()),
   2604             mode: Some("core".to_string()),
   2605             program_hash: Some(
   2606                 "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".to_string(),
   2607             ),
   2608             proof_reference: None,
   2609             system: RadrootsValidationReceiptProofSystem::Sp1Core,
   2610             verifying_key_hash: Some(
   2611                 "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb".to_string(),
   2612             ),
   2613         };
   2614         let error = super::verify_order_acceptance_validation_receipt_inline_sp1_proof(&receipt)
   2615             .await
   2616             .expect_err("raw material");
   2617         assert!(matches!(
   2618             error,
   2619             RadrootsSp1TradeHostError::Sp1ProofMaterialDecode(_)
   2620         ));
   2621     }
   2622 
   2623     #[test]
   2624     fn sp1_artifact_program_hash_is_distinct_from_reducer_hash() {
   2625         let mut input = witness();
   2626         input.sp1_program_hash =
   2627             Some("0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".to_string());
   2628         let execution =
   2629             super::execute_order_acceptance_public_values(&input).expect("deterministic execution");
   2630         let mut artifact = super::RadrootsSp1TradeProofArtifact {
   2631             inline_proof_base64: None,
   2632             mode: Some("core".to_string()),
   2633             program_hash: execution.public_values.sp1_program_hash.clone(),
   2634             proof_digest: String::new(),
   2635             proof_reference: Some(format!("radroots-proof://sha256/{}", "1".repeat(64))),
   2636             public_values_hash: execution.public_values_hash.clone(),
   2637             system: RadrootsValidationReceiptProofSystem::Sp1Core,
   2638             verifying_key_hash: execution.public_values.sp1_verifying_key_hash.clone(),
   2639         };
   2640         artifact.proof_digest =
   2641             super::proof_digest_for_execution(&execution, &artifact).expect("proof digest");
   2642         verify_order_acceptance_proof_artifact_structure(&execution, &artifact)
   2643             .expect("artifact verifies");
   2644         assert_ne!(
   2645             artifact.program_hash.as_deref(),
   2646             Some(execution.public_values.reducer_program_hash.as_str())
   2647         );
   2648     }
   2649 
   2650     #[test]
   2651     fn proof_reference_requires_canonical_sha256_uri() {
   2652         let execution =
   2653             super::execute_order_acceptance_public_values(&witness()).expect("execution");
   2654         let err = super::referenced_order_acceptance_proof_artifact_for_execution(
   2655             &execution,
   2656             RadrootsSp1TradeProofMode::Core,
   2657             "radroots-proof://sha256/xyz".to_string(),
   2658         )
   2659         .expect_err("invalid reference");
   2660         assert_eq!(err, RadrootsSp1TradeHostError::InvalidSp1ProofReference);
   2661 
   2662         let artifact = super::referenced_order_acceptance_proof_artifact_for_execution(
   2663             &execution,
   2664             RadrootsSp1TradeProofMode::Core,
   2665             format!("radroots-proof://sha256/{}", "1".repeat(64)),
   2666         )
   2667         .expect("referenced artifact");
   2668         verify_order_acceptance_proof_artifact_structure(&execution, &artifact)
   2669             .expect("referenced artifact is structurally valid");
   2670     }
   2671 
   2672     #[cfg(not(feature = "sp1_verify"))]
   2673     #[test]
   2674     fn sp1_verification_apis_report_unavailable_without_sp1_verify_feature() {
   2675         let runtime = tokio::runtime::Runtime::new().expect("runtime");
   2676         runtime.block_on(async {
   2677             let bundle =
   2678                 generate_order_acceptance_proof(&witness(), RadrootsSp1TradeProofMode::None)
   2679                     .expect("proof bundle");
   2680             let mut inline_receipt =
   2681                 validation_receipt_for_order_acceptance_proof(&bundle).expect("validation receipt");
   2682             inline_receipt.proof = RadrootsValidationReceiptProof {
   2683                 inline_proof_base64: Some("cHJvb2Y=".to_string()),
   2684                 mode: Some("core".to_string()),
   2685                 program_hash: Some(
   2686                     "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
   2687                         .to_string(),
   2688                 ),
   2689                 proof_reference: None,
   2690                 system: RadrootsValidationReceiptProofSystem::Sp1Core,
   2691                 verifying_key_hash: Some(
   2692                     "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
   2693                         .to_string(),
   2694                 ),
   2695             };
   2696             let err =
   2697                 super::verify_order_acceptance_validation_receipt_inline_sp1_proof(&inline_receipt)
   2698                     .await
   2699                     .expect_err("verifier unavailable");
   2700             assert_eq!(err, RadrootsSp1TradeHostError::Sp1ProofVerifierUnavailable);
   2701 
   2702             let execution =
   2703                 super::execute_order_acceptance_public_values(&witness()).expect("execution");
   2704             let artifact = super::referenced_order_acceptance_proof_artifact_for_execution(
   2705                 &execution,
   2706                 RadrootsSp1TradeProofMode::Core,
   2707                 format!("radroots-proof://sha256/{}", "1".repeat(64)),
   2708             )
   2709             .expect("referenced artifact");
   2710             let resolved = super::RadrootsSp1TradeResolvedProofArtifact {
   2711                 artifact: artifact.clone(),
   2712                 resolved_proof_envelope_base64: None,
   2713             };
   2714             let err =
   2715                 super::verify_order_acceptance_resolved_sp1_proof_artifact(&execution, &resolved)
   2716                     .await
   2717                     .expect_err("verifier unavailable");
   2718             assert_eq!(err, RadrootsSp1TradeHostError::Sp1ProofVerifierUnavailable);
   2719 
   2720             let mut referenced_receipt =
   2721                 validation_receipt_for_order_acceptance_proof(&bundle).expect("validation receipt");
   2722             referenced_receipt.proof = RadrootsValidationReceiptProof {
   2723                 inline_proof_base64: artifact.inline_proof_base64.clone(),
   2724                 mode: artifact.mode.clone(),
   2725                 program_hash: artifact.program_hash.clone(),
   2726                 proof_reference: artifact.proof_reference.clone(),
   2727                 system: artifact.system,
   2728                 verifying_key_hash: artifact.verifying_key_hash.clone(),
   2729             };
   2730             let err = super::verify_order_acceptance_validation_receipt_resolved_sp1_proof(
   2731                 &referenced_receipt,
   2732                 &resolved,
   2733             )
   2734             .await
   2735             .expect_err("verifier unavailable");
   2736             assert_eq!(err, RadrootsSp1TradeHostError::Sp1ProofVerifierUnavailable);
   2737         });
   2738     }
   2739 
   2740     #[test]
   2741     fn remote_prover_contract_round_trips_provider_neutral_payloads() {
   2742         let request = super::RadrootsSp1TradeRemoteProverRequest {
   2743             schema_version: super::RADROOTS_SP1_TRADE_REMOTE_PROVER_SCHEMA_VERSION,
   2744             request_id: "request-1".to_string(),
   2745             proof_target: RADROOTS_SP1_TRADE_ORDER_ACCEPTANCE_PROOF_TARGET.to_string(),
   2746             proof_mode: RadrootsSp1TradeProofMode::Core,
   2747             sp1_version_line: super::RADROOTS_SP1_TRADE_SP1_VERSION_LINE.to_string(),
   2748             witness: witness(),
   2749             expected_sp1_program_hash:
   2750                 "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".to_string(),
   2751             expected_sp1_verifying_key_hash:
   2752                 "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb".to_string(),
   2753             expected_public_values_hash:
   2754                 "0xcccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc".to_string(),
   2755             expected_reducer_program_hash: RADROOTS_SP1_TRADE_REDUCER_PROGRAM_HASH.to_string(),
   2756             expected_protocol_version: RADROOTS_SP1_TRADE_PROTOCOL_VERSION.to_string(),
   2757             expected_witness_version: RADROOTS_SP1_TRADE_WITNESS_VERSION,
   2758         };
   2759         let request_json = serde_json::to_string(&request).expect("request json");
   2760         let decoded_request: super::RadrootsSp1TradeRemoteProverRequest =
   2761             serde_json::from_str(&request_json).expect("decoded request");
   2762         assert_eq!(decoded_request, request);
   2763 
   2764         let response = super::RadrootsSp1TradeRemoteProverResponse {
   2765             schema_version: super::RADROOTS_SP1_TRADE_REMOTE_PROVER_SCHEMA_VERSION,
   2766             request_id: "request-1".to_string(),
   2767             status: super::RadrootsSp1TradeRemoteProverStatus::Accepted,
   2768             status_url: None,
   2769             status_path: Some("/proofs/request-1".to_string()),
   2770             proof_system: None,
   2771             proof_mode: None,
   2772             public_values_hash: None,
   2773             sp1_program_hash: None,
   2774             sp1_verifying_key_hash: None,
   2775             proof_artifact: None,
   2776             resolved_proof_envelope_base64: None,
   2777             reason_code: None,
   2778             message: None,
   2779             detail: None,
   2780         };
   2781         let response_json = serde_json::to_string(&response).expect("response json");
   2782         let decoded_response: super::RadrootsSp1TradeRemoteProverResponse =
   2783             serde_json::from_str(&response_json).expect("decoded response");
   2784         assert_eq!(decoded_response, response);
   2785         assert_eq!(
   2786             super::RadrootsSp1TradeProverBackend::from_label("remote_http_prove"),
   2787             Some(super::RadrootsSp1TradeProverBackend::RemoteHttpProve)
   2788         );
   2789     }
   2790 
   2791     #[cfg(feature = "sp1_verify")]
   2792     #[derive(Deserialize)]
   2793     #[serde(deny_unknown_fields)]
   2794     struct RemoteReturnedProofFixture {
   2795         schema_version: u32,
   2796         fixture_id: String,
   2797         proof_target: String,
   2798         proof_mode: RadrootsSp1TradeProofMode,
   2799         proof_system: RadrootsValidationReceiptProofSystem,
   2800         sp1_version_line: String,
   2801         remote_prover_request: super::RadrootsSp1TradeRemoteProverRequest,
   2802         remote_prover_response: super::RadrootsSp1TradeRemoteProverResponse,
   2803     }
   2804 
   2805     #[cfg(feature = "sp1_verify")]
   2806     fn remote_returned_proof_fixture() -> RemoteReturnedProofFixture {
   2807         serde_json::from_str(include_str!(
   2808             "../tests/fixtures/remote_returned_proof_order_acceptance_core_v1.json"
   2809         ))
   2810         .expect("remote returned proof fixture")
   2811     }
   2812 
   2813     #[cfg(feature = "sp1_verify")]
   2814     fn verified_remote_returned_proof_fixture() -> (
   2815         RadrootsSp1TradePublicValuesExecution,
   2816         super::RadrootsSp1TradeProofArtifact,
   2817     ) {
   2818         let fixture = remote_returned_proof_fixture();
   2819         assert_eq!(fixture.schema_version, 1);
   2820         assert_eq!(
   2821             fixture.fixture_id,
   2822             "remote_returned_proof_order_acceptance_core_v1"
   2823         );
   2824         assert_eq!(
   2825             fixture.proof_target,
   2826             RADROOTS_SP1_TRADE_ORDER_ACCEPTANCE_PROOF_TARGET
   2827         );
   2828         assert_eq!(fixture.proof_mode, RadrootsSp1TradeProofMode::Core);
   2829         assert_eq!(
   2830             fixture.proof_system,
   2831             RadrootsValidationReceiptProofSystem::Sp1Core
   2832         );
   2833         assert_eq!(
   2834             fixture.sp1_version_line,
   2835             super::RADROOTS_SP1_TRADE_SP1_VERSION_LINE
   2836         );
   2837 
   2838         let request = fixture.remote_prover_request;
   2839         let response = fixture.remote_prover_response;
   2840         let execution = super::execute_order_acceptance_public_values(&request.witness)
   2841             .expect("deterministic execution");
   2842         assert_eq!(
   2843             execution.public_values_hash,
   2844             request.expected_public_values_hash
   2845         );
   2846         assert_eq!(
   2847             execution.public_values.sp1_program_hash.as_deref(),
   2848             Some(request.expected_sp1_program_hash.as_str())
   2849         );
   2850         assert_eq!(
   2851             execution.public_values.sp1_verifying_key_hash.as_deref(),
   2852             Some(request.expected_sp1_verifying_key_hash.as_str())
   2853         );
   2854         assert_eq!(
   2855             response.status,
   2856             super::RadrootsSp1TradeRemoteProverStatus::Completed
   2857         );
   2858         assert_eq!(response.request_id, request.request_id);
   2859         assert_eq!(
   2860             response.public_values_hash.as_deref(),
   2861             Some(request.expected_public_values_hash.as_str())
   2862         );
   2863         assert_eq!(
   2864             response.sp1_program_hash.as_deref(),
   2865             Some(request.expected_sp1_program_hash.as_str())
   2866         );
   2867         assert_eq!(
   2868             response.sp1_verifying_key_hash.as_deref(),
   2869             Some(request.expected_sp1_verifying_key_hash.as_str())
   2870         );
   2871 
   2872         let artifact = response.proof_artifact.expect("proof artifact");
   2873         verify_order_acceptance_proof_artifact_structure(&execution, &artifact)
   2874             .expect("artifact structure");
   2875         (execution, artifact)
   2876     }
   2877 
   2878     #[cfg(feature = "sp1_verify")]
   2879     #[test]
   2880     fn remote_returned_proof_artifact_fixture_is_structurally_valid() {
   2881         verified_remote_returned_proof_fixture();
   2882     }
   2883 
   2884     #[cfg(all(feature = "sp1_verify", radroots_sp1_real_proof_tests))]
   2885     #[tokio::test]
   2886     async fn remote_returned_proof_artifact_real_sp1_verifies() {
   2887         let (execution, artifact) = verified_remote_returned_proof_fixture();
   2888         super::verify_order_acceptance_resolved_sp1_proof_artifact(
   2889             &execution,
   2890             &super::RadrootsSp1TradeResolvedProofArtifact::inline(artifact.clone()),
   2891         )
   2892         .await
   2893         .expect("remote proof verifies");
   2894 
   2895         let mut digest_mismatch = artifact.clone();
   2896         digest_mismatch.proof_digest =
   2897             "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".to_string();
   2898         let err = verify_order_acceptance_proof_artifact_structure(&execution, &digest_mismatch)
   2899             .expect_err("digest mismatch");
   2900         assert_eq!(err, RadrootsSp1TradeHostError::ProofDigestMismatch);
   2901 
   2902         let mut identity_mismatch = execution.clone();
   2903         identity_mismatch.public_values.sp1_program_hash =
   2904             Some("0xdddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd".to_string());
   2905         let err = verify_order_acceptance_proof_artifact_structure(&identity_mismatch, &artifact)
   2906             .expect_err("identity mismatch");
   2907         assert_eq!(err, RadrootsSp1TradeHostError::Sp1ProgramHashMismatch);
   2908 
   2909         let mut public_values_mismatch = request.witness;
   2910         public_values_mismatch.inventory_sequence += 1;
   2911         let mismatch_execution =
   2912             super::execute_order_acceptance_public_values(&public_values_mismatch)
   2913                 .expect("mismatch execution");
   2914         let err = verify_order_acceptance_proof_artifact_structure(&mismatch_execution, &artifact)
   2915             .expect_err("public values mismatch");
   2916         assert_eq!(err, RadrootsSp1TradeHostError::PublicValuesHashMismatch);
   2917     }
   2918 
   2919     #[cfg(feature = "sp1_verify")]
   2920     #[tokio::test]
   2921     async fn resolved_reference_rejects_unresolved_and_digest_mismatch() {
   2922         let execution =
   2923             super::execute_order_acceptance_public_values(&witness()).expect("execution");
   2924         let artifact = super::referenced_order_acceptance_proof_artifact_for_execution(
   2925             &execution,
   2926             RadrootsSp1TradeProofMode::Core,
   2927             format!("radroots-proof://sha256/{}", "1".repeat(64)),
   2928         )
   2929         .expect("referenced artifact");
   2930 
   2931         let err = super::verify_order_acceptance_resolved_sp1_proof_artifact(
   2932             &execution,
   2933             &super::RadrootsSp1TradeResolvedProofArtifact {
   2934                 artifact: artifact.clone(),
   2935                 resolved_proof_envelope_base64: None,
   2936             },
   2937         )
   2938         .await
   2939         .expect_err("unresolved reference");
   2940         assert_eq!(err, RadrootsSp1TradeHostError::Sp1ProofReferenceUnresolved);
   2941 
   2942         let err = super::verify_order_acceptance_resolved_sp1_proof_artifact(
   2943             &execution,
   2944             &super::RadrootsSp1TradeResolvedProofArtifact {
   2945                 artifact,
   2946                 resolved_proof_envelope_base64: Some("cHJvb2Y=".to_string()),
   2947             },
   2948         )
   2949         .await
   2950         .expect_err("digest mismatch");
   2951         assert_eq!(
   2952             err,
   2953             RadrootsSp1TradeHostError::Sp1ProofReferenceDigestMismatch
   2954         );
   2955     }
   2956 
   2957     #[test]
   2958     fn none_proof_mode_builds_deterministic_reducer_receipt() {
   2959         let mut input = witness();
   2960         input.sp1_verifying_key_hash = None;
   2961         let bundle = generate_order_acceptance_proof(&input, RadrootsSp1TradeProofMode::None)
   2962             .expect("none proof");
   2963         assert_eq!(
   2964             bundle.proof.system,
   2965             RadrootsValidationReceiptProofSystem::None
   2966         );
   2967         let receipt =
   2968             validation_receipt_for_order_acceptance_proof(&bundle).expect("validation receipt");
   2969         assert_eq!(
   2970             receipt.proof.system,
   2971             RadrootsValidationReceiptProofSystem::None
   2972         );
   2973         assert!(receipt.proof.inline_proof_base64.is_none());
   2974     }
   2975 
   2976     #[test]
   2977     fn deterministic_crates_do_not_depend_on_sp1_sdk() {
   2978         let manifest_dir = std::path::Path::new(env!("CARGO_MANIFEST_DIR"));
   2979         let crates_dir = manifest_dir.parent().expect("crates dir");
   2980         for crate_dir in ["events", "trade", "sp1_guest_trade"] {
   2981             let manifest = std::fs::read_to_string(crates_dir.join(crate_dir).join("Cargo.toml"))
   2982                 .expect("manifest");
   2983             assert!(!manifest.contains("sp1-sdk"));
   2984             assert!(!manifest.contains("sp1_sdk"));
   2985         }
   2986     }
   2987 
   2988     #[cfg(all(feature = "sp1_proving", radroots_sp1_guest_elf))]
   2989     #[tokio::test]
   2990     async fn sp1_execute_public_values_match_deterministic_reducer() {
   2991         let input = witness_without_sp1_identity();
   2992         let execution = super::execute_order_acceptance_sp1_public_values(&input)
   2993             .await
   2994             .expect("sp1 execute");
   2995         let mut expected_input = input;
   2996         expected_input.sp1_program_hash =
   2997             execution.execution.public_values.sp1_program_hash.clone();
   2998         expected_input.sp1_verifying_key_hash = execution
   2999             .execution
   3000             .public_values
   3001             .sp1_verifying_key_hash
   3002             .clone();
   3003         let expected = super::execute_order_acceptance_public_values(&expected_input)
   3004             .expect("deterministic execution");
   3005         assert_eq!(execution.execution, expected);
   3006         assert_eq!(
   3007             execution.committed_public_values,
   3008             expected.canonical_public_values
   3009         );
   3010         assert_eq!(execution.report.exit_code, 0);
   3011         assert!(execution.report.total_instruction_count > 0);
   3012     }
   3013 
   3014     #[cfg(all(feature = "sp1_proving", radroots_sp1_guest_elf))]
   3015     #[tokio::test]
   3016     async fn expensive_proof_generation_and_verification_is_runnable() {
   3017         let bundle = super::generate_order_acceptance_sp1_proof(
   3018             &witness_without_sp1_identity(),
   3019             RadrootsSp1TradeProofMode::Core,
   3020         )
   3021         .await
   3022         .expect("proof bundle");
   3023         super::verify_order_acceptance_resolved_sp1_proof_artifact(
   3024             &bundle.execution,
   3025             &super::RadrootsSp1TradeResolvedProofArtifact::inline(bundle.proof.clone()),
   3026         )
   3027         .await
   3028         .expect("proof verifies");
   3029         let receipt =
   3030             validation_receipt_for_order_acceptance_proof(&bundle).expect("validation receipt");
   3031         let verification =
   3032             super::verify_order_acceptance_validation_receipt_inline_sp1_proof(&receipt)
   3033                 .await
   3034                 .expect("receipt proof verifies");
   3035         assert_eq!(
   3036             bundle.proof.system,
   3037             RadrootsValidationReceiptProofSystem::Sp1Core
   3038         );
   3039         assert!(bundle.proof.inline_proof_base64.is_some());
   3040         assert_eq!(verification.proof_mode, RadrootsSp1TradeProofMode::Core);
   3041         assert_eq!(verification.public_values_hash, receipt.public_values_hash);
   3042         assert_eq!(
   3043             verification.sp1_program_hash,
   3044             receipt.proof.program_hash.expect("receipt program hash")
   3045         );
   3046         assert_eq!(
   3047             verification.sp1_verifying_key_hash,
   3048             receipt
   3049                 .proof
   3050                 .verifying_key_hash
   3051                 .expect("receipt verifying key hash")
   3052         );
   3053     }
   3054 
   3055     #[cfg(all(feature = "sp1_proving", radroots_sp1_guest_elf))]
   3056     #[tokio::test]
   3057     async fn real_sp1_verifier_rejects_missing_and_synthetic_material() {
   3058         let execution =
   3059             super::execute_order_acceptance_sp1_public_values(&witness_without_sp1_identity())
   3060                 .await
   3061                 .expect("sp1 execute")
   3062                 .execution;
   3063         let mut missing = super::RadrootsSp1TradeProofArtifact {
   3064             inline_proof_base64: None,
   3065             mode: Some("core".to_string()),
   3066             program_hash: execution.public_values.sp1_program_hash.clone(),
   3067             proof_digest: "0x00".to_string(),
   3068             proof_reference: None,
   3069             public_values_hash: execution.public_values_hash.clone(),
   3070             system: RadrootsValidationReceiptProofSystem::Sp1Core,
   3071             verifying_key_hash: execution.public_values.sp1_verifying_key_hash.clone(),
   3072         };
   3073         let err = super::verify_order_acceptance_resolved_sp1_proof_artifact(
   3074             &execution,
   3075             &super::RadrootsSp1TradeResolvedProofArtifact::inline(missing.clone()),
   3076         )
   3077         .await
   3078         .expect_err("missing proof material");
   3079         assert_eq!(err, RadrootsSp1TradeHostError::ProofDigestMismatch);
   3080 
   3081         missing.proof_digest =
   3082             super::proof_digest_for_execution(&execution, &missing).expect("missing proof digest");
   3083         let err = super::verify_order_acceptance_resolved_sp1_proof_artifact(
   3084             &execution,
   3085             &super::RadrootsSp1TradeResolvedProofArtifact::inline(missing.clone()),
   3086         )
   3087         .await
   3088         .expect_err("missing proof material");
   3089         assert_eq!(err, RadrootsSp1TradeHostError::MissingProofMaterial);
   3090 
   3091         let mut synthetic = missing;
   3092         synthetic.inline_proof_base64 =
   3093             Some(base64::engine::general_purpose::STANDARD.encode(b"synthetic proof material"));
   3094         synthetic.proof_digest = super::proof_digest_for_execution(&execution, &synthetic)
   3095             .expect("synthetic proof digest");
   3096         let err = super::verify_order_acceptance_resolved_sp1_proof_artifact(
   3097             &execution,
   3098             &super::RadrootsSp1TradeResolvedProofArtifact::inline(synthetic),
   3099         )
   3100         .await
   3101         .expect_err("synthetic proof material");
   3102         assert!(matches!(
   3103             err,
   3104             RadrootsSp1TradeHostError::Sp1ProofMaterialDecode(_)
   3105         ));
   3106     }
   3107 }