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 }