commit 24c20687d6c6ef1a108be32f5cb515f45eaf9684
parent 02436cb72a83bf2e430b634f90fdd009779f19cb
Author: triesap <tyson@radroots.org>
Date: Mon, 5 Jan 2026 20:02:19 +0000
events-codec: tighten d_tag validation to 22-byte base64url
- Require d_tag length == 22 and restrict terminal padding bits
- Update event and list-set tests to use compliant d_tag fixtures
- Adjust document subject and address test data for new d_tag format
- Align trade listing tests and envelopes with validated d_tag values
Diffstat:
13 files changed, 77 insertions(+), 71 deletions(-)
diff --git a/events-codec/src/coop/mod.rs b/events-codec/src/coop/mod.rs
@@ -19,7 +19,7 @@ mod tests {
#[test]
fn coop_tags_include_required_fields() {
let coop = RadrootsCoop {
- d_tag: "coop-1".to_string(),
+ d_tag: "BAAAAAAAAAAAAAAAAAAAAA".to_string(),
name: "Test Coop".to_string(),
about: None,
website: None,
@@ -76,7 +76,7 @@ mod tests {
fn coop_ref_tags_include_p_and_a() {
let coop = RadrootsCoopRef {
pubkey: "coop_pubkey".to_string(),
- d_tag: "coop-1".to_string(),
+ d_tag: "BAAAAAAAAAAAAAAAAAAAAA".to_string(),
};
let tags = coop_ref_tags(&coop).expect("coop ref tags");
@@ -88,26 +88,26 @@ mod tests {
#[test]
fn coop_list_sets_include_expected_tags() {
- let members = coop_members_list_set("coop-1", ["member_pubkey"]).expect("members list");
- assert_eq!(members.d_tag, "coop:coop-1:members");
+ let members = coop_members_list_set("BAAAAAAAAAAAAAAAAAAAAA", ["member_pubkey"]).expect("members list");
+ assert_eq!(members.d_tag, "coop:BAAAAAAAAAAAAAAAAAAAAA:members");
assert_eq!(members.entries.len(), 1);
assert_eq!(members.entries[0].tag, "p");
let farm_members = coop_members_farms_list_set(
- "coop-1",
+ "BAAAAAAAAAAAAAAAAAAAAA",
[RadrootsFarmRef {
pubkey: "farm_pubkey".to_string(),
- d_tag: "farm-1".to_string(),
+ d_tag: "AAAAAAAAAAAAAAAAAAAAAA".to_string(),
}],
)
.expect("farm members list");
- assert_eq!(farm_members.d_tag, "coop:coop-1:members.farms");
+ assert_eq!(farm_members.d_tag, "coop:BAAAAAAAAAAAAAAAAAAAAA:members.farms");
assert!(farm_members.entries.iter().any(|entry| entry.tag == "a"));
assert!(farm_members.entries.iter().any(|entry| entry.tag == "p"));
- let items = coop_items_list_set("coop-1", ["30361:coop_pubkey:charter-1"])
+ let items = coop_items_list_set("BAAAAAAAAAAAAAAAAAAAAA", ["30361:coop_pubkey:FAAAAAAAAAAAAAAAAAAAAA"])
.expect("items list");
- assert_eq!(items.d_tag, "coop:coop-1:items");
+ assert_eq!(items.d_tag, "coop:BAAAAAAAAAAAAAAAAAAAAA:items");
assert_eq!(items.entries.len(), 1);
assert_eq!(items.entries[0].tag, "a");
diff --git a/events-codec/src/d_tag.rs b/events-codec/src/d_tag.rs
@@ -3,15 +3,21 @@
use crate::error::{EventEncodeError, EventParseError};
pub fn is_d_tag_base64url(value: &str) -> bool {
- if value.is_empty() {
+ const D_TAG_LEN: usize = 22;
+
+ if value.len() != D_TAG_LEN {
return false;
}
- value.as_bytes().iter().all(|byte| {
+ let bytes = value.as_bytes();
+ if !bytes.iter().all(|byte| {
matches!(
byte,
b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'-' | b'_'
)
- })
+ }) {
+ return false;
+ }
+ matches!(bytes[D_TAG_LEN - 1], b'A' | b'Q' | b'g' | b'w')
}
pub(crate) fn validate_d_tag(value: &str, field: &'static str) -> Result<(), EventEncodeError> {
diff --git a/events-codec/src/document/mod.rs b/events-codec/src/document/mod.rs
@@ -11,7 +11,7 @@ mod tests {
#[test]
fn document_tags_include_required_fields() {
let document = RadrootsDocument {
- d_tag: "doc-1".to_string(),
+ d_tag: "EAAAAAAAAAAAAAAAAAAAAA".to_string(),
doc_type: "charter".to_string(),
title: "Sierra Co-op Charter".to_string(),
version: "1.0.0".to_string(),
@@ -20,7 +20,7 @@ mod tests {
body_markdown: None,
subject: RadrootsDocumentSubject {
pubkey: "coop_pubkey".to_string(),
- address: Some("30360:coop_pubkey:coop-1".to_string()),
+ address: Some("30360:coop_pubkey:BAAAAAAAAAAAAAAAAAAAAA".to_string()),
},
tags: Some(vec!["charter".to_string()]),
};
diff --git a/events-codec/src/farm/mod.rs b/events-codec/src/farm/mod.rs
@@ -32,7 +32,7 @@ mod tests {
#[test]
fn farm_tags_include_required_fields() {
let farm = RadrootsFarm {
- d_tag: "farm-1".to_string(),
+ d_tag: "AAAAAAAAAAAAAAAAAAAAAA".to_string(),
name: "Test Farm".to_string(),
about: None,
website: None,
@@ -106,7 +106,7 @@ mod tests {
fn farm_ref_tags_include_p_and_a() {
let farm = RadrootsFarmRef {
pubkey: "farm_pubkey".to_string(),
- d_tag: "farm-1".to_string(),
+ d_tag: "AAAAAAAAAAAAAAAAAAAAAA".to_string(),
};
let tags = farm_ref_tags(&farm).expect("farm ref tags");
@@ -118,8 +118,8 @@ mod tests {
#[test]
fn farm_list_sets_include_expected_tags() {
- let members = farm_members_list_set("farm-1", ["owner_pubkey"]).expect("members list");
- assert_eq!(members.d_tag, "farm:farm-1:members");
+ let members = farm_members_list_set("AAAAAAAAAAAAAAAAAAAAAA", ["owner_pubkey"]).expect("members list");
+ assert_eq!(members.d_tag, "farm:AAAAAAAAAAAAAAAAAAAAAA:members");
assert_eq!(members.entries.len(), 1);
assert_eq!(members.entries[0].tag, "p");
@@ -132,10 +132,10 @@ mod tests {
#[test]
fn farm_plots_list_set_uses_plot_addresses() {
let plots = vec![RadrootsPlot {
- d_tag: "plot-1".to_string(),
+ d_tag: "AAAAAAAAAAAAAAAAAAAAAQ".to_string(),
farm: RadrootsFarmRef {
pubkey: "farm_pubkey".to_string(),
- d_tag: "farm-1".to_string(),
+ d_tag: "AAAAAAAAAAAAAAAAAAAAAA".to_string(),
},
name: "Plot 1".to_string(),
about: None,
@@ -143,24 +143,24 @@ mod tests {
tags: None,
}];
- let plots_list = farm_plots_list_set_from_plots("farm-1", "farm_pubkey", &plots)
+ let plots_list = farm_plots_list_set_from_plots("AAAAAAAAAAAAAAAAAAAAAA", "farm_pubkey", &plots)
.expect("plots list");
- assert_eq!(plots_list.d_tag, "farm:farm-1:plots");
+ assert_eq!(plots_list.d_tag, "farm:AAAAAAAAAAAAAAAAAAAAAA:plots");
assert_eq!(plots_list.entries.len(), 1);
assert_eq!(plots_list.entries[0].tag, "a");
assert_eq!(
plots_list.entries[0].values[0],
- "30350:farm_pubkey:plot-1"
+ "30350:farm_pubkey:AAAAAAAAAAAAAAAAAAAAAQ"
);
}
#[test]
fn farm_listings_list_set_uses_listing_addresses() {
let listings = vec![RadrootsListing {
- d_tag: "listing-1".to_string(),
+ d_tag: "AAAAAAAAAAAAAAAAAAAAAg".to_string(),
farm: RadrootsListingFarmRef {
pubkey: "farm_pubkey".to_string(),
- d_tag: "farm-1".to_string(),
+ d_tag: "AAAAAAAAAAAAAAAAAAAAAA".to_string(),
},
product: RadrootsListingProduct {
key: "coffee".to_string(),
@@ -206,14 +206,14 @@ mod tests {
images: None,
}];
- let listings_list = farm_listings_list_set_from_listings("farm-1", "farm_pubkey", &listings)
+ let listings_list = farm_listings_list_set_from_listings("AAAAAAAAAAAAAAAAAAAAAA", "farm_pubkey", &listings)
.expect("listings list");
- assert_eq!(listings_list.d_tag, "farm:farm-1:listings");
+ assert_eq!(listings_list.d_tag, "farm:AAAAAAAAAAAAAAAAAAAAAA:listings");
assert_eq!(listings_list.entries.len(), 1);
assert_eq!(listings_list.entries[0].tag, "a");
assert_eq!(
listings_list.entries[0].values[0],
- "30402:farm_pubkey:listing-1"
+ "30402:farm_pubkey:AAAAAAAAAAAAAAAAAAAAAg"
);
}
}
diff --git a/events-codec/src/plot/mod.rs b/events-codec/src/plot/mod.rs
@@ -12,10 +12,10 @@ mod tests {
#[test]
fn plot_tags_include_farm_address() {
let plot = RadrootsPlot {
- d_tag: "plot-1".to_string(),
+ d_tag: "AAAAAAAAAAAAAAAAAAAAAQ".to_string(),
farm: RadrootsFarmRef {
pubkey: "farm_pubkey".to_string(),
- d_tag: "farm-1".to_string(),
+ d_tag: "AAAAAAAAAAAAAAAAAAAAAA".to_string(),
},
name: "Orchard".to_string(),
about: None,
diff --git a/events-codec/src/resource_area/mod.rs b/events-codec/src/resource_area/mod.rs
@@ -61,7 +61,7 @@ mod tests {
#[test]
fn resource_area_tags_include_required_fields() {
let area = RadrootsResourceArea {
- d_tag: "area-1".to_string(),
+ d_tag: "AAAAAAAAAAAAAAAAAAAAAw".to_string(),
name: "Banda Grove".to_string(),
about: None,
location: sample_location(),
@@ -78,7 +78,7 @@ mod tests {
fn resource_area_ref_tags_include_p_and_a() {
let area_ref = RadrootsResourceAreaRef {
pubkey: "area_pubkey".to_string(),
- d_tag: "area-1".to_string(),
+ d_tag: "AAAAAAAAAAAAAAAAAAAAAw".to_string(),
};
let tags = resource_area_ref_tags(&area_ref).expect("ref tags");
@@ -89,32 +89,32 @@ mod tests {
#[test]
fn resource_area_list_sets_include_expected_tags() {
let farms = resource_area_members_farms_list_set(
- "area-1",
+ "AAAAAAAAAAAAAAAAAAAAAw",
[RadrootsFarmRef {
pubkey: "farm_pubkey".to_string(),
- d_tag: "farm-1".to_string(),
+ d_tag: "AAAAAAAAAAAAAAAAAAAAAA".to_string(),
}],
)
.expect("farm members");
- assert_eq!(farms.d_tag, "resource:area-1:members.farms");
+ assert_eq!(farms.d_tag, "resource:AAAAAAAAAAAAAAAAAAAAAw:members.farms");
assert!(farms.entries.iter().any(|entry| entry.tag == "a"));
assert!(farms.entries.iter().any(|entry| entry.tag == "p"));
let plots = resource_area_members_plots_list_set(
- "area-1",
+ "AAAAAAAAAAAAAAAAAAAAAw",
[RadrootsPlotRef {
pubkey: "farm_pubkey".to_string(),
- d_tag: "plot-1".to_string(),
+ d_tag: "AAAAAAAAAAAAAAAAAAAAAQ".to_string(),
}],
)
.expect("plot members");
- assert_eq!(plots.d_tag, "resource:area-1:members.plots");
+ assert_eq!(plots.d_tag, "resource:AAAAAAAAAAAAAAAAAAAAAw:members.plots");
assert!(plots.entries.iter().any(|entry| entry.tag == "a"));
assert!(plots.entries.iter().any(|entry| entry.tag == "p"));
- let stewards = resource_area_stewards_list_set("area-1", ["steward_pubkey"])
+ let stewards = resource_area_stewards_list_set("AAAAAAAAAAAAAAAAAAAAAw", ["steward_pubkey"])
.expect("stewards");
- assert_eq!(stewards.d_tag, "resource:area-1:members.stewards");
+ assert_eq!(stewards.d_tag, "resource:AAAAAAAAAAAAAAAAAAAAAw:members.stewards");
assert!(stewards.entries.iter().any(|entry| entry.tag == "p"));
}
}
diff --git a/events-codec/src/resource_cap/mod.rs b/events-codec/src/resource_cap/mod.rs
@@ -13,10 +13,10 @@ mod tests {
#[test]
fn resource_harvest_cap_tags_include_required_fields() {
let cap = RadrootsResourceHarvestCap {
- d_tag: "cap-2025".to_string(),
+ d_tag: "DAAAAAAAAAAAAAAAAAAAAA".to_string(),
resource_area: RadrootsResourceAreaRef {
pubkey: "area_pubkey".to_string(),
- d_tag: "area-1".to_string(),
+ d_tag: "AAAAAAAAAAAAAAAAAAAAAw".to_string(),
},
product: RadrootsResourceHarvestProduct {
key: "nutmeg".to_string(),
diff --git a/events-codec/tests/list_private.rs b/events-codec/tests/list_private.rs
@@ -15,7 +15,7 @@ fn list_private_entries_roundtrip() {
},
RadrootsListEntry {
tag: "a".to_string(),
- values: vec!["30340:pubkey:farm-1".to_string()],
+ values: vec!["30340:pubkey:AAAAAAAAAAAAAAAAAAAAAA".to_string()],
},
];
@@ -23,7 +23,7 @@ fn list_private_entries_roundtrip() {
let parsed = list_private_entries_from_json(&json).expect("parsed");
assert_eq!(parsed.len(), entries.len());
assert_eq!(parsed[0].tag, "p");
- assert_eq!(parsed[1].values[0], "30340:pubkey:farm-1");
+ assert_eq!(parsed[1].values[0], "30340:pubkey:AAAAAAAAAAAAAAAAAAAAAA");
}
#[test]
diff --git a/events-codec/tests/listing.rs b/events-codec/tests/listing.rs
@@ -32,7 +32,7 @@ fn sample_listing(d_tag: &str) -> RadrootsListing {
d_tag: d_tag.to_string(),
farm: RadrootsListingFarmRef {
pubkey: "farm_pubkey".to_string(),
- d_tag: "farm-1".to_string(),
+ d_tag: "AAAAAAAAAAAAAAAAAAAAAA".to_string(),
},
product: RadrootsListingProduct {
key: "sku".to_string(),
@@ -77,7 +77,7 @@ fn sample_listing_full(d_tag: &str) -> RadrootsListing {
d_tag: d_tag.to_string(),
farm: RadrootsListingFarmRef {
pubkey: "farm_pubkey".to_string(),
- d_tag: "farm-1".to_string(),
+ d_tag: "AAAAAAAAAAAAAAAAAAAAAA".to_string(),
},
product: RadrootsListingProduct {
key: "sku".to_string(),
@@ -158,7 +158,7 @@ fn listing_build_tags_rejects_invalid_d_tag() {
#[test]
fn listing_roundtrip_from_event() {
- let listing = sample_listing("listing-1");
+ let listing = sample_listing("AAAAAAAAAAAAAAAAAAAAAg");
let parts = to_wire_parts(&listing).unwrap();
let decoded = listing_from_event(parts.kind, &parts.tags, &parts.content).unwrap();
@@ -174,23 +174,23 @@ fn listing_from_event_fills_missing_d_tag() {
let listing = sample_listing("");
let content = serde_json::to_string(&listing).unwrap();
let tags = vec![
- vec![TAG_D.to_string(), "filled".to_string()],
+ vec![TAG_D.to_string(), "FAAAAAAAAAAAAAAAAAAAAA".to_string()],
vec!["p".to_string(), "farm_pubkey".to_string()],
- vec!["a".to_string(), "30340:farm_pubkey:farm-1".to_string()],
+ vec!["a".to_string(), "30340:farm_pubkey:AAAAAAAAAAAAAAAAAAAAAA".to_string()],
];
let decoded = listing_from_event(KIND_LISTING, &tags, &content).unwrap();
- assert_eq!(decoded.d_tag, "filled");
+ assert_eq!(decoded.d_tag, "FAAAAAAAAAAAAAAAAAAAAA");
}
#[test]
fn listing_from_event_rejects_mismatched_d_tag() {
- let listing = sample_listing("a");
+ let listing = sample_listing("AAAAAAAAAAAAAAAAAAAAAg");
let content = serde_json::to_string(&listing).unwrap();
let tags = vec![
- vec![TAG_D.to_string(), "b".to_string()],
+ vec![TAG_D.to_string(), "AAAAAAAAAAAAAAAAAAAAAQ".to_string()],
vec!["p".to_string(), "farm_pubkey".to_string()],
- vec!["a".to_string(), "30340:farm_pubkey:farm-1".to_string()],
+ vec!["a".to_string(), "30340:farm_pubkey:AAAAAAAAAAAAAAAAAAAAAA".to_string()],
];
let err = listing_from_event(KIND_LISTING, &tags, &content).unwrap_err();
@@ -199,12 +199,12 @@ fn listing_from_event_rejects_mismatched_d_tag() {
#[test]
fn listing_from_event_rejects_wrong_kind() {
- let listing = sample_listing("listing-1");
+ let listing = sample_listing("AAAAAAAAAAAAAAAAAAAAAg");
let content = serde_json::to_string(&listing).unwrap();
let tags = vec![
- vec![TAG_D.to_string(), "listing-1".to_string()],
+ vec![TAG_D.to_string(), "AAAAAAAAAAAAAAAAAAAAAg".to_string()],
vec!["p".to_string(), "farm_pubkey".to_string()],
- vec!["a".to_string(), "30340:farm_pubkey:farm-1".to_string()],
+ vec!["a".to_string(), "30340:farm_pubkey:AAAAAAAAAAAAAAAAAAAAAA".to_string()],
];
let err = listing_from_event(KIND_POST, &tags, &content).unwrap_err();
@@ -219,12 +219,12 @@ fn listing_from_event_rejects_wrong_kind() {
#[test]
fn listing_build_tags_includes_listing_fields() {
- let listing = sample_listing_full("listing-1");
+ let listing = sample_listing_full("AAAAAAAAAAAAAAAAAAAAAg");
let tags = listing_build_tags(&listing).unwrap();
assert!(tags.iter().any(|t| {
t.get(0).map(|s| s.as_str()) == Some(TAG_D)
- && t.get(1).map(|s| s.as_str()) == Some("listing-1")
+ && t.get(1).map(|s| s.as_str()) == Some("AAAAAAAAAAAAAAAAAAAAAg")
}));
assert!(tags.iter().any(|t| {
t.get(0).map(|s| s.as_str()) == Some("p")
@@ -232,7 +232,7 @@ fn listing_build_tags_includes_listing_fields() {
}));
assert!(tags.iter().any(|t| {
t.get(0).map(|s| s.as_str()) == Some("a")
- && t.get(1).map(|s| s.as_str()) == Some("30340:farm_pubkey:farm-1")
+ && t.get(1).map(|s| s.as_str()) == Some("30340:farm_pubkey:AAAAAAAAAAAAAAAAAAAAAA")
}));
assert!(tags.iter().any(|t| {
t.get(0).map(|s| s.as_str()) == Some("key")
@@ -331,7 +331,7 @@ fn listing_build_tags_includes_listing_fields() {
#[test]
fn listing_tags_full_includes_trade_fields() {
- let mut listing = sample_listing("listing-1");
+ let mut listing = sample_listing("AAAAAAAAAAAAAAAAAAAAAg");
let inventory = RadrootsCoreDecimal::from_str("12.5").unwrap();
let inventory_value = inventory.to_string();
listing.inventory_available = Some(inventory);
@@ -363,7 +363,7 @@ fn listing_tags_full_includes_trade_fields() {
#[test]
fn listing_tags_full_includes_status_tag() {
- let mut listing = sample_listing("listing-1");
+ let mut listing = sample_listing("AAAAAAAAAAAAAAAAAAAAAg");
listing.availability = Some(RadrootsListingAvailability::Status {
status: RadrootsListingStatus::Active,
});
@@ -378,7 +378,7 @@ fn listing_tags_full_includes_status_tag() {
#[test]
fn listing_build_tags_ignores_null_strings() {
- let mut listing = sample_listing_full("listing-1");
+ let mut listing = sample_listing_full("AAAAAAAAAAAAAAAAAAAAAg");
listing.product.summary = Some("null".to_string());
listing.product.process = Some("null".to_string());
listing.product.lot = Some("null".to_string());
diff --git a/trade/src/listing/codec.rs b/trade/src/listing/codec.rs
@@ -640,7 +640,7 @@ mod tests {
fn farm_ref() -> RadrootsListingFarmRef {
RadrootsListingFarmRef {
pubkey: "seller".to_string(),
- d_tag: "farm-1".to_string(),
+ d_tag: "AAAAAAAAAAAAAAAAAAAAAA".to_string(),
}
}
@@ -674,7 +674,7 @@ mod tests {
let listing = listing_from_tags(
&tags,
- "listing-1".to_string(),
+ "AAAAAAAAAAAAAAAAAAAAAg".to_string(),
farm_ref(),
"seller".to_string(),
None,
diff --git a/trade/src/listing/dvm.rs b/trade/src/listing/dvm.rs
@@ -355,7 +355,7 @@ mod tests {
fn envelope_requires_order_id_for_order_scoped() {
let env = TradeListingEnvelope::new(
TradeListingMessageType::OrderRequest,
- format!("{KIND_LISTING}:pubkey:listing"),
+ format!("{KIND_LISTING}:pubkey:AAAAAAAAAAAAAAAAAAAAAg"),
None,
TradeListingValidateRequest { listing_event: None },
);
diff --git a/trade/src/listing/tags.rs b/trade/src/listing/tags.rs
@@ -142,7 +142,7 @@ mod tests {
#[test]
fn trade_listing_dvm_tags_builds_expected_tags() {
- let listing_addr = format!("{KIND_LISTING}:pubkey:listing");
+ let listing_addr = format!("{KIND_LISTING}:pubkey:AAAAAAAAAAAAAAAAAAAAAg");
let tags = trade_listing_dvm_tags("pubkey", &listing_addr, Some("order-1"));
let expected: Vec<Vec<String>> = vec![
vec![String::from("p"), String::from("pubkey")],
@@ -154,7 +154,7 @@ mod tests {
#[test]
fn trade_listing_dvm_tags_omit_order_id_when_missing() {
- let listing_addr = format!("{KIND_LISTING}:pubkey:listing");
+ let listing_addr = format!("{KIND_LISTING}:pubkey:AAAAAAAAAAAAAAAAAAAAAg");
let tags = trade_listing_dvm_tags("pubkey", &listing_addr, None::<String>);
let expected: Vec<Vec<String>> = vec![
vec![String::from("p"), String::from("pubkey")],
diff --git a/trade/src/listing/validation.rs b/trade/src/listing/validation.rs
@@ -280,10 +280,10 @@ mod tests {
fn base_listing() -> RadrootsListing {
RadrootsListing {
- d_tag: "listing-1".into(),
+ d_tag: "AAAAAAAAAAAAAAAAAAAAAg".into(),
farm: RadrootsListingFarmRef {
pubkey: "seller".into(),
- d_tag: "farm-1".into(),
+ d_tag: "AAAAAAAAAAAAAAAAAAAAAA".into(),
},
product: RadrootsListingProduct {
key: "coffee".into(),
@@ -383,9 +383,9 @@ mod tests {
let mut event = base_event(&base_listing());
event.content = String::new();
event.tags = vec![
- vec!["d".into(), "listing-1".into()],
+ vec!["d".into(), "AAAAAAAAAAAAAAAAAAAAAg".into()],
vec!["p".into(), "seller".into()],
- vec!["a".into(), "30340:seller:farm-1".into()],
+ vec!["a".into(), "30340:seller:AAAAAAAAAAAAAAAAAAAAAA".into()],
vec!["key".into(), "coffee".into()],
vec!["title".into(), "Coffee".into()],
vec!["category".into(), "coffee".into()],