lib

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

commit 685e492a97c05709849a6638a3a062a8589f584a
parent e069cebfce543dff4e541f8c43b0f319e0e7664c
Author: triesap <tyson@radroots.org>
Date:   Wed, 15 Jul 2026 17:34:32 +0000

orders: require validator-set receipt trust

Replace trusted RHI pubkey trust decisions with validator-set-bound validation receipt policy.\n\nCarry validator set address/event identity through order runtime trust output and remove order revision DTO roots from the SDK binding generator.

Diffstat:
Mcrates/sdk/src/orders_runtime.rs | 194++++++++++++++++++++++++++++++++++++++++++++-----------------------------------
Mcrates/sdk/tests/orders_runtime.rs | 61++++++++++++++++++++++++++++++++++++++++++++++++-------------
Mtools/sdk_xtask_import/src/dto_roots.rs | 3---
3 files changed, 157 insertions(+), 101 deletions(-)

diff --git a/crates/sdk/src/orders_runtime.rs b/crates/sdk/src/orders_runtime.rs @@ -791,18 +791,6 @@ impl TradeResyncRequest { self } - pub fn try_with_trusted_rhi_pubkeys<I, S>( - mut self, - pubkeys: I, - ) -> Result<Self, RadrootsSdkError> - where - I: IntoIterator<Item = S>, - S: AsRef<str>, - { - self.validation_trust_policy.trusted_rhi_pubkeys = parse_worker_pubkeys(pubkeys)?; - Ok(self) - } - #[cfg(feature = "transport-nostr-runtime")] fn validate(&self) -> Result<(), RadrootsSdkError> { if self.limit == 0 || self.limit > TRADE_STATUS_MAX_LIMIT { @@ -1129,6 +1117,8 @@ pub struct TradeValidationReceiptTags { pub receipt_type: String, pub root_event_id: String, pub target_event_id: String, + pub validator_set_addr: String, + pub validator_set_event_id: String, } #[cfg(feature = "runtime")] @@ -1296,18 +1286,6 @@ impl TradeStatusRequest { self } - pub fn try_with_trusted_rhi_pubkeys<I, S>( - mut self, - pubkeys: I, - ) -> Result<Self, RadrootsSdkError> - where - I: IntoIterator<Item = S>, - S: AsRef<str>, - { - self.validation_trust_policy.trusted_rhi_pubkeys = parse_worker_pubkeys(pubkeys)?; - Ok(self) - } - fn validate(&self) -> Result<(), RadrootsSdkError> { if self.limit == 0 || self.limit > TRADE_STATUS_MAX_LIMIT { return Err(RadrootsSdkError::trade_status_limit_invalid( @@ -1570,8 +1548,9 @@ pub struct TradeStatusEvidenceSummary { #[derive(Clone, Debug, PartialEq, Eq, serde::Serialize)] pub struct TradeValidationTrustDecision { pub state: RadrootsTradeValidationTrustState, - pub trusted_rhi_pubkey_count: usize, - pub allow_deterministic_none: bool, + pub validator_count: usize, + pub validator_set_addr: Option<String>, + pub validator_set_event_id: Option<String>, pub require_cryptographic_proof: bool, pub receipt_event_id: Option<RadrootsEventId>, pub receipt_author: Option<RadrootsPublicKey>, @@ -3073,6 +3052,8 @@ async fn trade_status_validation_trust_decision( .as_ref() .or(status.decision_event_id.as_ref()) .map(RadrootsEventId::as_str), + validator_set_addr: policy.validator_set_addr.as_ref().map(|addr| addr.as_str()), + validator_set_event_id: policy.validator_set_event_id.as_deref(), ..RadrootsValidationReceiptExpectedBinding::default() }; let verified = match verify_validation_receipt_event(&event, expected) { @@ -3113,7 +3094,77 @@ async fn trade_status_validation_trust_decision( )), )); } - if policy.trusted_rhi_pubkeys.is_empty() { + let Some(validator_set) = policy.validator_set.as_ref() else { + return Ok(trade_validation_trust_decision( + policy, + RadrootsTradeValidationTrustState::Untrusted, + TradeValidationTrustEvidence::receipt( + receipt_event_id, + receipt_author, + &receipt.receipt, + None, + ), + false, + Some(TradeValidationTrustReason::new( + "validator_set_required", + "validation trust policy has no configured validator set", + )), + )); + }; + let Some(validator_set_addr) = policy.validator_set_addr.as_ref() else { + return Ok(trade_validation_trust_decision( + policy, + RadrootsTradeValidationTrustState::Untrusted, + TradeValidationTrustEvidence::receipt( + receipt_event_id, + receipt_author, + &receipt.receipt, + None, + ), + false, + Some(TradeValidationTrustReason::new( + "validator_set_address_required", + "validation trust policy has no validator set address", + )), + )); + }; + let Some(validator_set_event_id) = policy.validator_set_event_id.as_ref() else { + return Ok(trade_validation_trust_decision( + policy, + RadrootsTradeValidationTrustState::Untrusted, + TradeValidationTrustEvidence::receipt( + receipt_event_id, + receipt_author, + &receipt.receipt, + None, + ), + false, + Some(TradeValidationTrustReason::new( + "validator_set_event_id_required", + "validation trust policy has no validator set event id", + )), + )); + }; + if receipt.receipt.statement.validator_set_addr != *validator_set_addr + || receipt.receipt.statement.validator_set_event_id != *validator_set_event_id + { + return Ok(trade_validation_trust_decision( + policy, + RadrootsTradeValidationTrustState::Untrusted, + TradeValidationTrustEvidence::receipt( + receipt_event_id, + receipt_author, + &receipt.receipt, + None, + ), + false, + Some(TradeValidationTrustReason::new( + "validator_set_binding_mismatch", + "validation receipt does not bind the configured validator set", + )), + )); + } + if !policy.trusts_validator_pubkey(&receipt_author) { return Ok(trade_validation_trust_decision( policy, RadrootsTradeValidationTrustState::Untrusted, @@ -3125,12 +3176,15 @@ async fn trade_status_validation_trust_decision( ), false, Some(TradeValidationTrustReason::new( - "validation_trust_policy_empty", - "validation trust policy has no trusted RHI public keys", + "validator_set_author_untrusted", + "validation receipt author is not the configured validator set key", )), )); } - if !policy.trusts_rhi_pubkey(&receipt_author) { + let receipt_created_at = receipt.event.created_at_u64(); + if receipt_created_at < validator_set.valid_from + || receipt_created_at > validator_set.valid_until + { return Ok(trade_validation_trust_decision( policy, RadrootsTradeValidationTrustState::Untrusted, @@ -3142,8 +3196,8 @@ async fn trade_status_validation_trust_decision( ), false, Some(TradeValidationTrustReason::new( - "validation_receipt_author_untrusted", - "validation receipt author is not trusted by the active policy", + "validator_set_not_valid_at_receipt_time", + "validation receipt was authored outside the configured validator set validity window", )), )); } @@ -3242,26 +3296,9 @@ fn evaluate_trade_validation_trust_receipt( let metadata = validation_receipt_trust_metadata(receipt); let authority = metadata.validation_authority; let confidence = metadata.commitment_confidence; - if authority == RadrootsTradeValidationAuthority::DevDeterministicOnly - || confidence == RadrootsTradeCommitmentConfidence::LocalOnly + if authority == RadrootsTradeValidationAuthority::ValidatorSetDeterministic + || confidence == RadrootsTradeCommitmentConfidence::CommittedByValidatorSet { - if !policy.allow_deterministic_none { - return trade_validation_trust_decision( - policy, - RadrootsTradeValidationTrustState::Untrusted, - TradeValidationTrustEvidence::receipt( - receipt_event_id, - receipt_author, - receipt, - Some(metadata), - ), - false, - Some(TradeValidationTrustReason::new( - "deterministic_none_not_allowed", - "deterministic-none validation is not allowed by the active policy", - )), - ); - } if policy.require_cryptographic_proof { return trade_validation_trust_decision( policy, @@ -3281,14 +3318,14 @@ fn evaluate_trade_validation_trust_receipt( } return trade_validation_trust_decision( policy, - RadrootsTradeValidationTrustState::TrustedLocal, + RadrootsTradeValidationTrustState::ValidatorSetCommitted, TradeValidationTrustEvidence::receipt( receipt_event_id, receipt_author, receipt, Some(metadata), ), - false, + true, None, ); } @@ -3298,8 +3335,8 @@ fn evaluate_trade_validation_trust_receipt( RadrootsTradeValidationAuthority::CryptographicProofVerified, RadrootsTradeCommitmentConfidence::CommittedByCryptographicProof ) | ( - RadrootsTradeValidationAuthority::TrustedServiceAndProofVerified, - RadrootsTradeCommitmentConfidence::CommittedByTrustedServiceAndProof + RadrootsTradeValidationAuthority::ValidatorSetAndProofVerified, + RadrootsTradeCommitmentConfidence::CommittedByValidatorSetAndProof ) ); if cryptographic_metadata @@ -3339,11 +3376,8 @@ fn evaluate_trade_validation_trust_receipt( if matches!( (authority, confidence), ( - RadrootsTradeValidationAuthority::TrustedRhiServiceKey, - RadrootsTradeCommitmentConfidence::CommittedByTrustedService - ) | ( - RadrootsTradeValidationAuthority::TrustedServiceAndProofVerified, - RadrootsTradeCommitmentConfidence::CommittedByTrustedServiceAndProof + RadrootsTradeValidationAuthority::ValidatorSetAndProofVerified, + RadrootsTradeCommitmentConfidence::CommittedByValidatorSetAndProof ) | ( RadrootsTradeValidationAuthority::CryptographicProofVerified, RadrootsTradeCommitmentConfidence::CommittedByCryptographicProof @@ -3351,7 +3385,7 @@ fn evaluate_trade_validation_trust_receipt( ) { return trade_validation_trust_decision( policy, - RadrootsTradeValidationTrustState::TrustedLocal, + RadrootsTradeValidationTrustState::ValidatorSetCommitted, TradeValidationTrustEvidence::receipt( receipt_event_id, receipt_author, @@ -3385,14 +3419,15 @@ fn validation_receipt_trust_metadata( ) -> TradeValidationTrustMetadata { if receipt.proof.system == RadrootsValidationReceiptProofSystem::None { TradeValidationTrustMetadata { - validation_authority: RadrootsTradeValidationAuthority::DevDeterministicOnly, - commitment_confidence: RadrootsTradeCommitmentConfidence::LocalOnly, + validation_authority: RadrootsTradeValidationAuthority::ValidatorSetDeterministic, + commitment_confidence: RadrootsTradeCommitmentConfidence::CommittedByValidatorSet, cryptographic_proof_verified: false, } } else { TradeValidationTrustMetadata { - validation_authority: RadrootsTradeValidationAuthority::CryptographicProofVerified, - commitment_confidence: RadrootsTradeCommitmentConfidence::CommittedByCryptographicProof, + validation_authority: RadrootsTradeValidationAuthority::ValidatorSetAndProofVerified, + commitment_confidence: + RadrootsTradeCommitmentConfidence::CommittedByValidatorSetAndProof, cryptographic_proof_verified: true, } } @@ -3415,8 +3450,12 @@ fn trade_validation_trust_decision( }; TradeValidationTrustDecision { state, - trusted_rhi_pubkey_count: policy.trusted_rhi_pubkey_count(), - allow_deterministic_none: policy.allow_deterministic_none, + validator_count: policy.validator_count(), + validator_set_addr: policy + .validator_set_addr + .as_ref() + .map(|addr| addr.as_str().to_owned()), + validator_set_event_id: policy.validator_set_event_id.clone(), require_cryptographic_proof: policy.require_cryptographic_proof, receipt_event_id: evidence.receipt_event_id, receipt_author: evidence.receipt_author, @@ -3465,7 +3504,7 @@ fn apply_validation_trust_decision_to_status(status: &mut TradeStatusReceipt) { status.lifecycle_terminal = true; status.next_action = TradeStatusNextActionKind::InspectEvidenceIssues; } - RadrootsTradeValidationTrustState::TrustedLocal + RadrootsTradeValidationTrustState::ValidatorSetCommitted | RadrootsTradeValidationTrustState::CryptographicCommitted => {} } } @@ -3550,6 +3589,8 @@ impl From<RadrootsValidationReceiptTags> for TradeValidationReceiptTags { receipt_type: tags.receipt_type.as_str().to_owned(), root_event_id: tags.root_event_id, target_event_id: tags.target_event_id, + validator_set_addr: tags.validator_set_addr.as_str().to_owned(), + validator_set_event_id: tags.validator_set_event_id, } } } @@ -3617,23 +3658,6 @@ fn validate_validation_receipt_limit(limit: u32) -> Result<(), RadrootsSdkError> Ok(()) } -#[cfg(feature = "runtime")] -fn parse_worker_pubkeys<I, S>(pubkeys: I) -> Result<Vec<RadrootsPublicKey>, RadrootsSdkError> -where - I: IntoIterator<Item = S>, - S: AsRef<str>, -{ - pubkeys - .into_iter() - .map(|pubkey| { - let value = pubkey.as_ref(); - RadrootsPublicKey::parse(value).map_err(|error| RadrootsSdkError::InvalidRequest { - message: format!("invalid trusted worker pubkey `{value}`: {error}"), - }) - }) - .collect() -} - #[cfg(all(feature = "runtime", feature = "transport-nostr-runtime"))] fn validation_receipt_invalid_reason_code(error: &RadrootsValidationReceiptError) -> &'static str { match error { diff --git a/crates/sdk/tests/orders_runtime.rs b/crates/sdk/tests/orders_runtime.rs @@ -19,7 +19,6 @@ use radroots_core::{ }; #[cfg(feature = "signer-adapters")] use radroots_event::contract::RadrootsActorRole; -#[cfg(feature = "transport-nostr-runtime")] use radroots_event::ids::RadrootsPublicKey; use radroots_event::wire::RadrootsNip01EventWireParts; use radroots_event::{ @@ -75,8 +74,9 @@ use radroots_trade::order::RadrootsOrderIssue; use radroots_trade::validation_receipt::{ RadrootsTradeValidationReceipt, RadrootsValidationReceiptProof, RadrootsValidationReceiptProofSystem, RadrootsValidationReceiptResult, - RadrootsValidationReceiptStatement, RadrootsValidationReceiptType, + RadrootsValidationReceiptStatement, RadrootsValidationReceiptType, RadrootsValidatorSetV1, validation_receipt_event_build, validation_receipt_public_values_hash_hex, + validator_set_address_from_str, }; #[cfg(feature = "transport-nostr-runtime")] use radroots_transport_nostr::{ @@ -555,6 +555,39 @@ fn deterministic_event_id(raw: &str) -> RadrootsEventId { RadrootsEventId::parse(hex).expect("event id") } +fn validator_set_id() -> &'static str { + "018f3d99-7d35-7c0c-8a0f-7f3b645abcde" +} + +fn validator_set_event_id() -> RadrootsEventId { + deterministic_event_id("validator-set-event") +} + +fn validator_set_addr_raw(author_pubkey: &str) -> String { + format!("30381:{author_pubkey}:{}", validator_set_id()) +} + +fn validator_set_policy_for_validator_pubkey( + validator_pubkey: &str, +) -> RadrootsTradeValidationTrustPolicy { + let validator_set = RadrootsValidatorSetV1 { + set_id: validator_set_id().to_owned(), + validator_pubkey: RadrootsPublicKey::parse(validator_pubkey).expect("validator pubkey"), + threshold: 1, + valid_from: 1_700_000_000, + valid_until: 1_800_000_000, + protocol_contract_hash: hash32('7'), + operator_name: "Radroots validation operator".to_owned(), + operator_contact: None, + }; + RadrootsTradeValidationTrustPolicy::production().with_validator_set( + validator_set, + validator_set_address_from_str(validator_set_addr_raw(validator_pubkey)) + .expect("validator set address"), + validator_set_event_id().into_string(), + ) +} + fn decimal(raw: &str) -> RadrootsCoreDecimal { raw.parse().expect("decimal") } @@ -910,10 +943,7 @@ async fn trade_product_clients_resync_committed_after_rhi_validation_receipt() { Some(RadrootsTradeValidationTrustState::Untrusted) ); - let trusted_local_policy = RadrootsTradeValidationTrustPolicy::production() - .with_trusted_rhi_pubkeys(vec![service_pubkey.parse().expect("service pubkey")]) - .with_allow_deterministic_none(true) - .with_require_cryptographic_proof(false); + let trusted_local_policy = validator_set_policy_for_validator_pubkey(service_pubkey.as_str()); let seller_trusted_local = seller_sdk .trades() .status( @@ -929,9 +959,9 @@ async fn trade_product_clients_resync_committed_after_rhi_validation_receipt() { .expect("seller validation trust"); assert_eq!( seller_trust.state, - RadrootsTradeValidationTrustState::TrustedLocal + RadrootsTradeValidationTrustState::ValidatorSetCommitted ); - assert!(!seller_trust.production_committed); + assert!(seller_trust.production_committed); assert_eq!( seller_trust .receipt_author @@ -1018,8 +1048,7 @@ async fn trade_status_trust_policy_requires_trusted_cryptographic_receipt_for_co ); let service_pubkey = public_key_hex_for_secret(SERVICE_SECRET_KEY_HEX); - let trusted_policy = RadrootsTradeValidationTrustPolicy::production() - .with_trusted_rhi_pubkeys(vec![service_pubkey.parse().expect("service pubkey")]); + let trusted_policy = validator_set_policy_for_validator_pubkey(service_pubkey.as_str()); let trusted_status = sdk .trades() .status(status_request(order_id).with_validation_trust_policy(trusted_policy)) @@ -2724,6 +2753,11 @@ fn validation_receipt_wire_parts_with_proof( listing_event_id: listing_event_id.as_str().to_owned(), root_event_id: root_event_id.as_str().to_owned(), target_event_id: target_event_id.as_str().to_owned(), + validator_set_addr: validator_set_address_from_str(validator_set_addr_raw( + public_key_hex_for_secret(SERVICE_SECRET_KEY_HEX).as_str(), + )) + .expect("validator set address"), + validator_set_event_id: validator_set_event_id().into_string(), statement_type: RadrootsValidationReceiptType::TradeTransition, }, version: 1, @@ -3184,9 +3218,10 @@ async fn order_status_contract_dtos_serialize_deterministically() { "limit": 25, "source": "local_only", "validation_trust_policy": { - "trusted_rhi_pubkeys": [], - "allow_deterministic_none": false, - "require_cryptographic_proof": true + "validator_set": null, + "validator_set_addr": null, + "validator_set_event_id": null, + "require_cryptographic_proof": false } }) ); diff --git a/tools/sdk_xtask_import/src/dto_roots.rs b/tools/sdk_xtask_import/src/dto_roots.rs @@ -642,9 +642,6 @@ mod tests { "RadrootsOrderItem", "RadrootsOrderPricingBasis", "RadrootsOrderRequest", - "RadrootsOrderRevisionDecision", - "RadrootsOrderRevisionOutcome", - "RadrootsOrderRevisionProposal", "RadrootsPlot", "RadrootsPlotLocation", "RadrootsPlotRef",