lib

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

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:
Mevents-codec/src/coop/mod.rs | 18+++++++++---------
Mevents-codec/src/d_tag.rs | 12+++++++++---
Mevents-codec/src/document/mod.rs | 4++--
Mevents-codec/src/farm/mod.rs | 28++++++++++++++--------------
Mevents-codec/src/plot/mod.rs | 4++--
Mevents-codec/src/resource_area/mod.rs | 20++++++++++----------
Mevents-codec/src/resource_cap/mod.rs | 4++--
Mevents-codec/tests/list_private.rs | 4++--
Mevents-codec/tests/listing.rs | 36++++++++++++++++++------------------
Mtrade/src/listing/codec.rs | 4++--
Mtrade/src/listing/dvm.rs | 2+-
Mtrade/src/listing/tags.rs | 4++--
Mtrade/src/listing/validation.rs | 8++++----
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()],