cli

Command-line interface for Radroots
git clone https://radroots.dev/git/cli.git
Log | Files | Refs | README | LICENSE

commit 9ec7fe450fab67ec50acb27c35e2c3ddf42014c9
parent 196ee3e6493f8358da714bd7bf81093c7b03e18a
Author: triesap <tyson@radroots.org>
Date:   Thu, 21 May 2026 07:24:29 +0000

validation: type worker proof evidence

- parse trusted worker result fields into shared proof enums
- reject unknown backend, mode, status, role, and proof system values
- bind typed worker evidence to receipt and public value identities
- cover unknown typed values staying untrusted

Diffstat:
Msrc/runtime/validation_receipt.rs | 88++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------
1 file changed, 72 insertions(+), 16 deletions(-)

diff --git a/src/runtime/validation_receipt.rs b/src/runtime/validation_receipt.rs @@ -8,7 +8,9 @@ use radroots_nostr::prelude::{ radroots_event_from_nostr, radroots_nostr_filter_tag, }; use radroots_sp1_host_trade::{ - RadrootsSp1TradeHostError, verify_order_acceptance_validation_receipt_inline_sp1_proof, + RadrootsSp1TradeHostError, RadrootsSp1TradeProofMode, RadrootsSp1TradeProverBackend, + RadrootsSp1TradeWorkerResultPayload, RadrootsSp1TradeWorkerResultStatus, + RadrootsSp1TradeWorkerRole, verify_order_acceptance_validation_receipt_inline_sp1_proof, }; use radroots_trade::validation_receipt::{ RadrootsTradeValidationReceipt, RadrootsValidationReceiptError, @@ -198,7 +200,7 @@ enum ValidationReceiptCommandIntent { } #[derive(Debug, Deserialize)] -struct ValidationReceiptWorkerResultPayload { +struct RawValidationReceiptWorkerResultPayload { cryptographic_proof_verified: bool, decision_event_id: Option<String>, event_set_root: Option<String>, @@ -219,6 +221,44 @@ struct ValidationReceiptWorkerResultPayload { worker_role: Option<String>, } +impl RawValidationReceiptWorkerResultPayload { + fn typed(&self) -> Option<RadrootsSp1TradeWorkerResultPayload> { + Some(RadrootsSp1TradeWorkerResultPayload { + cryptographic_proof_verified: self.cryptographic_proof_verified, + decision_event_id: self.decision_event_id.clone(), + event_set_root: self.event_set_root.clone(), + listing_event_id: self.listing_event_id.clone(), + order_id: self.order_id.clone(), + proof_generated: self.proof_generated, + proof_mode: RadrootsSp1TradeProofMode::from_label(self.proof_mode.as_str())?, + proof_system: RadrootsValidationReceiptProofSystem::from_label( + self.proof_system.as_str(), + )?, + public_values_hash: self.public_values_hash.clone(), + prover_backend: RadrootsSp1TradeProverBackend::from_label( + self.prover_backend.as_str(), + )?, + receipt_event_id: self.receipt_event_id.clone(), + receipt_kind: self.receipt_kind, + reducer_output_root: self.reducer_output_root.clone(), + request_event_id: self.request_event_id.clone(), + sp1_execute_checked: self.sp1_execute_checked, + sp1_execute_public_values_hash: self.sp1_execute_public_values_hash.clone(), + status: match self.status.as_str() { + "succeeded" => RadrootsSp1TradeWorkerResultStatus::Succeeded, + _ => return None, + }, + worker_role: match self.worker_role.as_deref() { + Some("non_authoritative_prover") => { + Some(RadrootsSp1TradeWorkerRole::NonAuthoritativeProver) + } + Some(_) => return None, + None => None, + }, + }) + } +} + pub fn get( config: &RuntimeConfig, args: &ValidationReceiptEventArgs, @@ -1157,7 +1197,7 @@ fn worker_evidence_for_receipts( for event in fetch_receipt.events { let payload = - match serde_json::from_str::<ValidationReceiptWorkerResultPayload>(&event.content) { + match serde_json::from_str::<RawValidationReceiptWorkerResultPayload>(&event.content) { Ok(payload) => payload, Err(_) => continue, }; @@ -1167,7 +1207,10 @@ fn worker_evidence_for_receipts( let converted = radroots_event_from_nostr(&event); let author = converted.author.to_ascii_lowercase(); let trusted_author = trusted_pubkeys.contains(author.as_str()); - let bound = worker_payload_binds_receipt(&payload, binding); + let typed_payload = payload.typed(); + let bound = typed_payload + .as_ref() + .is_some_and(|payload| worker_payload_binds_receipt(payload, binding)); let trusted = trusted_author && bound; let receipt_event_id = payload.receipt_event_id.clone(); let result_event_id = event.id.to_hex(); @@ -1218,13 +1261,13 @@ fn worker_evidence_for_receipts( } fn worker_payload_binds_receipt( - payload: &ValidationReceiptWorkerResultPayload, + payload: &RadrootsSp1TradeWorkerResultPayload, binding: &WorkerEvidenceReceiptBinding<'_>, ) -> bool { let receipt = binding.receipt; let tags = binding.tags; - payload.status == "succeeded" - && payload.worker_role.as_deref() == Some("non_authoritative_prover") + payload.status == RadrootsSp1TradeWorkerResultStatus::Succeeded + && payload.worker_role == Some(RadrootsSp1TradeWorkerRole::NonAuthoritativeProver) && payload.receipt_kind == Some(KIND_TRADE_VALIDATION_RECEIPT) && payload.receipt_event_id == binding.receipt_event_id && payload.order_id.as_deref() == Some(tags.order_id.as_str()) @@ -1234,12 +1277,12 @@ fn worker_payload_binds_receipt( && payload.request_event_id.as_deref() == Some(tags.root_event_id.as_str()) && payload.decision_event_id.as_deref() == Some(tags.target_event_id.as_str()) && payload.public_values_hash == receipt.public_values_hash - && payload.proof_system == receipt.proof.system.as_str() - && payload.proof_mode == receipt.proof.mode.as_deref().unwrap_or("none") + && payload.proof_system == receipt.proof.system + && payload.proof_mode.mode_label().unwrap_or("none") + == receipt.proof.mode.as_deref().unwrap_or("none") && payload.proof_generated == (receipt.proof.system != RadrootsValidationReceiptProofSystem::None) && payload.cryptographic_proof_verified == payload.proof_generated - && !payload.prover_backend.trim().is_empty() && payload.sp1_execute_checked && payload.sp1_execute_public_values_hash.as_deref() == Some(receipt.public_values_hash.as_str()) @@ -1291,8 +1334,8 @@ fn relay_failures(failures: Vec<DirectRelayFailure>) -> Vec<RelayFailureView> { #[cfg(test)] mod tests { use super::{ - ValidationReceiptWorkerEvidenceSelection, ValidationReceiptWorkerEvidenceView, - ValidationReceiptWorkerResultPayload, WorkerEvidenceReceiptBinding, + RawValidationReceiptWorkerResultPayload, ValidationReceiptWorkerEvidenceSelection, + ValidationReceiptWorkerEvidenceView, WorkerEvidenceReceiptBinding, proof_verification_view_for_receipt, validation_receipt_invalid_reason_code, worker_payload_binds_receipt, }; @@ -1381,8 +1424,8 @@ mod tests { } } - fn worker_result_payload(listing_event_id: &str) -> ValidationReceiptWorkerResultPayload { - ValidationReceiptWorkerResultPayload { + fn worker_result_payload(listing_event_id: &str) -> RawValidationReceiptWorkerResultPayload { + RawValidationReceiptWorkerResultPayload { cryptographic_proof_verified: false, decision_event_id: Some( "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc".to_owned(), @@ -1428,7 +1471,9 @@ mod tests { assert!(worker_payload_binds_receipt( &worker_result_payload( "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" - ), + ) + .typed() + .expect("typed payload"), &binding )); } @@ -1446,12 +1491,23 @@ mod tests { assert!(!worker_payload_binds_receipt( &worker_result_payload( "dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd" - ), + ) + .typed() + .expect("typed payload"), &binding )); } #[test] + fn worker_evidence_unknown_typed_values_are_not_trusted() { + let mut payload = worker_result_payload( + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + ); + payload.prover_backend = "future_backend".to_owned(); + assert!(payload.typed().is_none()); + } + + #[test] fn none_receipts_report_deterministic_verification_without_crypto_claim() { let view = proof_verification_view_for_receipt( &deterministic_receipt(),