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:
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",