sdk

Radroots SDK and bindings
git clone https://radroots.dev/git/sdk.git
Log | Files | Refs | README

commit be22167ee5a1a3f4b93a8f892acbec76d34cf48e
parent a00c36a1b2d5ed269878b954041331e328b92d55
Author: triesap <tyson@radroots.org>
Date:   Tue, 28 Jul 2026 05:50:15 +0000

identity: migrate workspace consumers

- Replace legacy identity wrappers with canonical public value types.
- Migrate SDK adapters, runtimes, examples, bindings, and tests.
- Remove obsolete public aliases and duplicated identity constants.
- Validate the complete SDK workspace and dependency boundaries.

Diffstat:
MCargo.lock | 11++++++-----
Mcrates/event_codec_wasm/src/lib.rs | 13++++++++-----
Mcrates/identity_bindings/src/lib.rs | 33+++++++++++----------------------
Mcrates/replica_sync_wasm/src/lib.rs | 2+-
Mcrates/sdk/Cargo.toml | 4+---
Mcrates/sdk/README.md | 1-
Mcrates/sdk/examples/runtime_local.rs | 14+++++++++-----
Mcrates/sdk/examples/sdk_v1_listing_prepare.rs | 14+++++++++-----
Mcrates/sdk/examples/sdk_v1_local_enqueue_and_mock_sync.rs | 14+++++++++-----
Mcrates/sdk/src/actor_json.rs | 3++-
Mcrates/sdk/src/adapters/nostr.rs | 13++++++-------
Mcrates/sdk/src/adapters/radrootsd.rs | 2+-
Mcrates/sdk/src/farms_runtime.rs | 12++++++------
Mcrates/sdk/src/idempotency.rs | 7++++---
Mcrates/sdk/src/identity.rs | 20++------------------
Mcrates/sdk/src/listings_runtime.rs | 2+-
Mcrates/sdk/src/private_store.rs | 16+++++-----------
Mcrates/sdk/src/signer_provider.rs | 26++++++++++++++------------
Mcrates/sdk/src/trade_runtime.rs | 4++--
Mcrates/sdk/src/workflow_runtime.rs | 25+++++++++++++------------
Mcrates/sdk/tests/identity_public_api.rs | 109+++++++++++++++----------------------------------------------------------------
Mcrates/sdk/tests/knowledge_public_api.rs | 2+-
Mcrates/sdk/tests/listings_runtime.rs | 14+++++++++-----
Mcrates/sdk/tests/replica_ingest.rs | 2+-
Mcrates/sdk/tests/support/fixture_signer.rs | 5+++--
Mcrates/sdk/tests/sync_runtime.rs | 14+++++++++-----
Mcrates/sdk/tests/unit/adapters_nostr_tests.rs | 25++++++++++++-------------
Mcrates/sdk/tests/unit/adapters_radrootsd_tests.rs | 9++++++---
Mcrates/sdk/tests/unit/error_tests.rs | 2+-
Mcrates/sdk/tests/unit/idempotency_tests.rs | 5+++--
Dcrates/sdk/tests/unit/identity_tests.rs | 13-------------
Mcrates/sdk/tests/unit/listings_runtime_tests.rs | 14+++++++++-----
Mcrates/sdk/tests/unit/signer_provider_tests.rs | 4++--
Mcrates/sdk/tests/unit/sync_runtime_tests.rs | 7+++++--
Mcrates/sdk/tests/unit/trade_runtime_tests.rs | 7++++---
Mcrates/sdk/tests/unit/workflow_runtime_tests.rs | 11++++++-----
Mpackages/identity-bindings/src/generated/constants.ts | 6++----
37 files changed, 203 insertions(+), 282 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock @@ -1961,6 +1961,7 @@ name = "radroots_authority" version = "1.0.0-alpha.1" dependencies = [ "radroots_event", + "radroots_identity", "radroots_nostr", ] @@ -2001,6 +2002,7 @@ dependencies = [ "jiff-tzdb", "radroots_blossom", "radroots_core", + "radroots_identity", "secp256k1", "serde", "serde_json", @@ -2026,6 +2028,7 @@ dependencies = [ "radroots_blossom", "radroots_core", "radroots_event", + "radroots_identity", "serde", "serde_json", "sha2", @@ -2070,6 +2073,7 @@ dependencies = [ "radroots_blossom", "radroots_event", "radroots_event_codec", + "radroots_identity", "radroots_transport", "serde", "serde_json", @@ -2100,12 +2104,8 @@ name = "radroots_identity" version = "0.1.0" dependencies = [ "k256", - "radroots_protected_store", - "radroots_runtime_paths", - "radroots_secret_vault", - "serde_json", + "serde", "thiserror 2.0.18", - "tracing", ] [[package]] @@ -2428,6 +2428,7 @@ dependencies = [ "radroots_event", "radroots_event_codec", "radroots_event_store", + "radroots_identity", "serde", "serde_json", "sha2", diff --git a/crates/event_codec_wasm/src/lib.rs b/crates/event_codec_wasm/src/lib.rs @@ -469,7 +469,7 @@ fn authored_comment_from_input( } => { let root = RadrootsNip22AddressRootReference::parse(coordinate, relay.as_deref()) .map_err(comment_authored_error)?; - if root.author().as_str() != author { + if root.author().to_hex() != author { return Err(authored_error("comment_root_author_mismatch")); } if root.kind().as_u32() != kind { @@ -884,11 +884,14 @@ mod tests { }; fn sample_listing() -> RadrootsOperationalListing { - let quantity = Quantity::new(Decimal::from(1u32), Unit::Each); - let price = QuantityPrice::new( - Money::new(Decimal::from(10u32), Currency::USD), + let quantity = + Quantity::try_new(Decimal::from(1u32), Unit::Each).expect("positive fixture quantity"); + let price = QuantityPrice::try_new( + Money::try_new(Decimal::from(10u32), Currency::USD) + .expect("non-negative fixture money"), quantity.clone(), - ); + ) + .expect("non-zero fixture pricing quantity"); RadrootsOperationalListing { d_tag: "AAAAAAAAAAAAAAAAAAAAAg".parse().expect("listing d tag"), diff --git a/crates/identity_bindings/src/lib.rs b/crates/identity_bindings/src/lib.rs @@ -1,26 +1,19 @@ pub use radroots_identity as upstream; use dto_bindgen_backend_ts::{TypeScriptDeclaration, TypeScriptModule, TypeScriptValue}; -use radroots_identity::{ - RADROOTS_USERNAME_MAX_LEN, RADROOTS_USERNAME_MIN_LEN, RADROOTS_USERNAME_REGEX, -}; +use radroots_identity::username::{MAX_LENGTH, MIN_LENGTH}; pub fn constants_module() -> TypeScriptModule { TypeScriptModule::new("src/generated/constants.ts") .with_declaration(TypeScriptDeclaration::constant( - "RADROOTS_USERNAME_MIN_LEN", + "RADROOTS_USERNAME_MIN_LENGTH", None, - usize_value(RADROOTS_USERNAME_MIN_LEN), + usize_value(MIN_LENGTH), )) .with_declaration(TypeScriptDeclaration::constant( - "RADROOTS_USERNAME_MAX_LEN", + "RADROOTS_USERNAME_MAX_LENGTH", None, - usize_value(RADROOTS_USERNAME_MAX_LEN), - )) - .with_declaration(TypeScriptDeclaration::constant( - "RADROOTS_USERNAME_REGEX", - None, - TypeScriptValue::string(RADROOTS_USERNAME_REGEX), + usize_value(MAX_LENGTH), )) } @@ -30,19 +23,15 @@ fn usize_value(value: usize) -> TypeScriptValue { #[cfg(test)] mod tests { - use super::{ - RADROOTS_USERNAME_MAX_LEN, RADROOTS_USERNAME_MIN_LEN, RADROOTS_USERNAME_REGEX, - constants_module, - }; + use super::{MAX_LENGTH, MIN_LENGTH, constants_module}; #[test] fn preserves_username_constant_exports() { let rendered = constants_module().render_source(); - assert!(rendered.contains("RADROOTS_USERNAME_MIN_LEN")); - assert!(rendered.contains(&RADROOTS_USERNAME_MIN_LEN.to_string())); - assert!(rendered.contains("RADROOTS_USERNAME_MAX_LEN")); - assert!(rendered.contains(&RADROOTS_USERNAME_MAX_LEN.to_string())); - assert!(rendered.contains("RADROOTS_USERNAME_REGEX")); - assert!(rendered.contains(&format!("{RADROOTS_USERNAME_REGEX:?}"))); + assert!(rendered.contains("RADROOTS_USERNAME_MIN_LENGTH")); + assert!(rendered.contains(&MIN_LENGTH.to_string())); + assert!(rendered.contains("RADROOTS_USERNAME_MAX_LENGTH")); + assert!(rendered.contains(&MAX_LENGTH.to_string())); + assert!(!rendered.contains("REGEX")); } } diff --git a/crates/replica_sync_wasm/src/lib.rs b/crates/replica_sync_wasm/src/lib.rs @@ -124,7 +124,7 @@ mod tests { fn parse_event_accepts_author_domain_envelope() { let author = "a".repeat(64); let event = parse_event_model(&event_json(Some(author.as_str()), None)).expect("event"); - assert_eq!(event.author_str(), author); + assert_eq!(event.author().to_hex(), author); assert_eq!( event.tags_as_vec(), vec![vec!["d".to_owned(), "one".to_owned()]] diff --git a/crates/sdk/Cargo.toml b/crates/sdk/Cargo.toml @@ -44,10 +44,8 @@ knowledge = [ "radroots_event_codec/knowledge-nip54", ] identity-models = [ - "dep:radroots_identity", "radroots_identity/std", ] -identity-storage = ["identity-models", "std", "radroots_identity/std"] signing = ["dep:nostr", "dep:radroots_nostr", "nostr", "radroots_nostr/std"] transport-nostr-client = ["signing", "std", "serde_json", "radroots_nostr/client"] radrootsd-execution = [ @@ -153,7 +151,7 @@ radroots_runtime_paths = { workspace = true, optional = true, default-features = radroots_secret_vault = { workspace = true, optional = true, default-features = false } radroots_runtime_contract_v1 = { path = "../runtime_contract_v1", version = "0.1.0", default-features = false } radroots_trade = { workspace = true, default-features = false } -radroots_identity = { workspace = true, optional = true, default-features = false } +radroots_identity = { workspace = true, default-features = false } radroots_nostr = { workspace = true, optional = true, default-features = false } radroots_nostr_connect = { workspace = true, optional = true } radroots_nostr_signer = { workspace = true, optional = true, default-features = false } diff --git a/crates/sdk/README.md b/crates/sdk/README.md @@ -101,7 +101,6 @@ Radroots v1 listing and trade event contracts. Optional advanced substrate is explicitly feature-scoped: - `identity-models`: identity data types without local storage coupling -- `identity-storage`: encrypted identity-file helpers - `signing`: dependency substrate for curated Nostr adapters; it exposes no generic builder or caller-constructed wire-part signing module - `transport-nostr-client`: Nostr relay WebSocket client and publish adapters diff --git a/crates/sdk/examples/runtime_local.rs b/crates/sdk/examples/runtime_local.rs @@ -88,11 +88,15 @@ fn sample_listing(seller: &str) -> RadrootsOperationalListing { primary_bin_id: RadrootsInventoryBinId::parse("bin-1").expect("bin id"), bins: vec![RadrootsOperationalListingBin { bin_id: RadrootsInventoryBinId::parse("bin-1").expect("bin id"), - quantity: Quantity::new(Decimal::from(1000u32), Unit::MassG), - price_per_canonical_unit: QuantityPrice { - amount: Money::new(Decimal::from(20u32), Currency::USD), - quantity: Quantity::new(Decimal::from(1u32), Unit::MassG), - }, + quantity: Quantity::try_new(Decimal::from(1000u32), Unit::MassG) + .expect("positive example quantity"), + price_per_canonical_unit: QuantityPrice::try_new( + Money::try_new(Decimal::from(20u32), Currency::USD) + .expect("non-negative example money"), + Quantity::try_new(Decimal::from(1u32), Unit::MassG) + .expect("positive example pricing quantity"), + ) + .expect("non-zero example pricing quantity"), display_amount: None, display_unit: None, display_label: None, diff --git a/crates/sdk/examples/sdk_v1_listing_prepare.rs b/crates/sdk/examples/sdk_v1_listing_prepare.rs @@ -51,11 +51,15 @@ fn sample_listing(seller: &str) -> RadrootsOperationalListing { primary_bin_id: RadrootsInventoryBinId::parse("bin-1").expect("bin id"), bins: vec![RadrootsOperationalListingBin { bin_id: RadrootsInventoryBinId::parse("bin-1").expect("bin id"), - quantity: Quantity::new(Decimal::from(1000u32), Unit::MassG), - price_per_canonical_unit: QuantityPrice { - amount: Money::new(Decimal::from(20u32), Currency::USD), - quantity: Quantity::new(Decimal::from(1u32), Unit::MassG), - }, + quantity: Quantity::try_new(Decimal::from(1000u32), Unit::MassG) + .expect("positive example quantity"), + price_per_canonical_unit: QuantityPrice::try_new( + Money::try_new(Decimal::from(20u32), Currency::USD) + .expect("non-negative example money"), + Quantity::try_new(Decimal::from(1u32), Unit::MassG) + .expect("positive example pricing quantity"), + ) + .expect("non-zero example pricing quantity"), display_amount: None, display_unit: None, display_label: None, diff --git a/crates/sdk/examples/sdk_v1_local_enqueue_and_mock_sync.rs b/crates/sdk/examples/sdk_v1_local_enqueue_and_mock_sync.rs @@ -85,11 +85,15 @@ fn sample_listing(seller: &str) -> RadrootsOperationalListing { primary_bin_id: RadrootsInventoryBinId::parse("bin-1").expect("bin id"), bins: vec![RadrootsOperationalListingBin { bin_id: RadrootsInventoryBinId::parse("bin-1").expect("bin id"), - quantity: Quantity::new(Decimal::from(1000u32), Unit::MassG), - price_per_canonical_unit: QuantityPrice { - amount: Money::new(Decimal::from(20u32), Currency::USD), - quantity: Quantity::new(Decimal::from(1u32), Unit::MassG), - }, + quantity: Quantity::try_new(Decimal::from(1000u32), Unit::MassG) + .expect("positive example quantity"), + price_per_canonical_unit: QuantityPrice::try_new( + Money::try_new(Decimal::from(20u32), Currency::USD) + .expect("non-negative example money"), + Quantity::try_new(Decimal::from(1u32), Unit::MassG) + .expect("positive example pricing quantity"), + ) + .expect("non-zero example pricing quantity"), display_amount: None, display_unit: None, display_label: None, diff --git a/crates/sdk/src/actor_json.rs b/crates/sdk/src/actor_json.rs @@ -26,8 +26,9 @@ impl serde::Serialize for SdkActorContextJson<'_> { .map(actor_role_code) .collect::<Vec<_>>(); let account_id = self.0.account_id().map(|account_id| account_id.as_str()); + let pubkey = self.0.pubkey().to_hex(); let mut state = serializer.serialize_struct("SdkActorContext", 4)?; - state.serialize_field("pubkey", self.0.pubkey().as_str())?; + state.serialize_field("pubkey", &pubkey)?; state.serialize_field("roles", &roles)?; state.serialize_field("account_id", &account_id)?; state.serialize_field("source", actor_source_code(self.0.source()))?; diff --git a/crates/sdk/src/adapters/nostr.rs b/crates/sdk/src/adapters/nostr.rs @@ -1,9 +1,8 @@ use core::time::Duration; -use crate::identity::RadrootsIdentity; use radroots_nostr::prelude::{ RadrootsNostrClient, RadrootsNostrClientOptions, RadrootsNostrError, RadrootsNostrEvent, - RadrootsNostrEventId, RadrootsNostrOutput, + RadrootsNostrEventId, RadrootsNostrKeys, RadrootsNostrOutput, }; pub fn signerless_client() -> RadrootsNostrClient { @@ -16,8 +15,8 @@ pub fn signerless_client_with_options( RadrootsNostrClient::new_signerless_with_options(options) } -pub fn client_from_identity(identity: &RadrootsIdentity) -> RadrootsNostrClient { - RadrootsNostrClient::from_identity(identity) +pub fn client_from_keys(keys: RadrootsNostrKeys) -> RadrootsNostrClient { + RadrootsNostrClient::new(keys) } pub async fn configure_write_relays( @@ -33,12 +32,12 @@ pub async fn configure_write_relays( Ok(()) } -pub async fn connected_client_from_identity( - identity: &RadrootsIdentity, +pub async fn connected_client_from_keys( + keys: RadrootsNostrKeys, relay_urls: &[String], connect_timeout: Duration, ) -> Result<RadrootsNostrClient, RadrootsNostrError> { - let client = client_from_identity(identity); + let client = client_from_keys(keys); configure_write_relays(&client, relay_urls, connect_timeout).await?; Ok(client) } diff --git a/crates/sdk/src/adapters/radrootsd.rs b/crates/sdk/src/adapters/radrootsd.rs @@ -131,7 +131,7 @@ impl RadrootsdPublishEventIdentity { fn from_signed_event(event: &RadrootsSignedEvent) -> Self { Self { event_id: event.id_str().to_owned(), - pubkey: event.pubkey_str().to_owned(), + pubkey: event.pubkey().to_hex().to_owned(), kind: event.kind(), } } diff --git a/crates/sdk/src/farms_runtime.rs b/crates/sdk/src/farms_runtime.rs @@ -600,7 +600,7 @@ impl<'sdk> FarmsClient<'sdk> { let public_locality = public_locality_from_reverse(request.exact_location, &reverse)?; let record = SdkPrivateFarmLocationRecord { farm_addr: farm_addr.clone(), - farm_pubkey: request.actor.pubkey().as_str().to_owned(), + farm_pubkey: request.actor.pubkey().to_hex(), farm_d_tag: request.farm_d_tag, label, latitude: request.exact_location.latitude, @@ -645,7 +645,7 @@ impl<'sdk> FarmsClient<'sdk> { }; let record = SdkPrivateFarmLocationRecord { farm_addr: farm_addr.clone(), - farm_pubkey: actor.pubkey().as_str().to_owned(), + farm_pubkey: actor.pubkey().to_hex(), farm_d_tag, label, latitude: exact_location.latitude, @@ -670,7 +670,7 @@ impl<'sdk> FarmsClient<'sdk> { GeocoderLocalityLookup::NoMatch => Ok(FarmPrivateLocationSetResult::NoMatch( farm_private_location_lookup_receipt( farm_addr, - actor.pubkey().as_str(), + actor.pubkey().to_hex().as_str(), farm_d_tag, FarmPrivateLocationInput::Locality(locality_query), Vec::new(), @@ -679,7 +679,7 @@ impl<'sdk> FarmsClient<'sdk> { GeocoderLocalityLookup::Ambiguous { candidates } => Ok( FarmPrivateLocationSetResult::Ambiguous(farm_private_location_lookup_receipt( farm_addr, - actor.pubkey().as_str(), + actor.pubkey().to_hex().as_str(), farm_d_tag, FarmPrivateLocationInput::Locality(locality_query), candidates @@ -790,7 +790,7 @@ fn validate_farm_publish_plan( } let farm_addr = RadrootsAddressableCoordinate::parse(format!( "{KIND_FARM}:{}:{d_tag}", - plan.frozen_draft.expected_pubkey_str() + plan.frozen_draft.expected_pubkey().to_hex() )) .map_err(|_| invalid("frozen draft farm address is invalid"))?; if plan.farm_addr != farm_addr { @@ -822,7 +822,7 @@ fn farm_publish_plan( created_at_nostr.into(), parts.tags, parts.content, - actor.pubkey().as_str(), + actor.pubkey().to_hex(), ) .expect("validated farm publish draft freezes"); let expected_event_id = RadrootsEventId::parse(frozen_draft.expected_event_id_str()) diff --git a/crates/sdk/src/idempotency.rs b/crates/sdk/src/idempotency.rs @@ -1,6 +1,7 @@ use crate::RadrootsSdkError; use core::fmt; -use radroots_event::ids::{RadrootsEventId, RadrootsPublicKey}; +use radroots_event::ids::RadrootsEventId; +use radroots_identity::PublicKey; use serde::ser::SerializeStruct; pub const SDK_IDEMPOTENCY_KEY_MAX_LEN: usize = 256; @@ -69,7 +70,7 @@ impl serde::Serialize for SdkIdempotencyKey { pub struct SdkTradeIdempotencyRecord { pub idempotency_key: SdkIdempotencyKey, pub operation_kind: String, - pub actor_pubkey: RadrootsPublicKey, + pub actor_pubkey: PublicKey, pub digest: String, pub canonical_payload_hash: String, pub expected_event_id: RadrootsEventId, @@ -84,7 +85,7 @@ impl SdkTradeIdempotencyRecord { pub fn conflict_error(&self, new_digest: impl Into<String>) -> RadrootsSdkError { RadrootsSdkError::IdempotencyConflict { operation_kind: self.operation_kind.clone(), - expected_pubkey_prefix: self.actor_pubkey.as_str().chars().take(12).collect(), + expected_pubkey_prefix: self.actor_pubkey.to_hex().chars().take(12).collect(), existing_digest_prefix: self.digest.chars().take(12).collect(), new_digest_prefix: new_digest.into().chars().take(12).collect(), } diff --git a/crates/sdk/src/identity.rs b/crates/sdk/src/identity.rs @@ -1,20 +1,4 @@ pub use radroots_identity::{ - DEFAULT_IDENTITY_PATH, IdentityError, RADROOTS_USERNAME_MAX_LEN, RADROOTS_USERNAME_MIN_LEN, - RADROOTS_USERNAME_REGEX, RadrootsIdentity, RadrootsIdentityFile, RadrootsIdentityId, - RadrootsIdentityProfile, RadrootsIdentityPublic, RadrootsIdentitySecretKeyFormat, - radroots_username_is_valid, radroots_username_normalize, + AccountId, Error, IdentityId, Profile, PublicIdentity, PublicKey, Username, account, key, + profile, username, }; - -#[cfg(feature = "identity-storage")] -pub use radroots_identity::{ - RADROOTS_ENCRYPTED_IDENTITY_DEFAULT_KEY_SLOT, RADROOTS_ENCRYPTED_IDENTITY_KEY_SUFFIX, - RadrootsEncryptedIdentityFile, encrypted_identity_wrapping_key_path, load_encrypted_identity, - load_encrypted_identity_with_key_slot, load_identity_profile, rotate_encrypted_identity, - rotate_encrypted_identity_with_key_slot, store_encrypted_identity, - store_encrypted_identity_with_key_slot, store_identity_profile, -}; - -#[cfg(all(feature = "identity-models", feature = "identity-storage"))] -#[cfg(test)] -#[path = "../tests/unit/identity_tests.rs"] -mod tests; diff --git a/crates/sdk/src/listings_runtime.rs b/crates/sdk/src/listings_runtime.rs @@ -391,7 +391,7 @@ fn validate_listing_publish_plan( } let public_listing_addr = RadrootsClassifiedListingAddress::parse(format!( "{KIND_CLASSIFIED_LISTING}:{}:{d_tag}", - plan.frozen_draft.expected_pubkey_str() + plan.frozen_draft.expected_pubkey().to_hex() )) .map_err(|_| invalid("frozen draft listing address is invalid"))?; if plan.public_listing_addr != public_listing_addr { diff --git a/crates/sdk/src/private_store.rs b/crates/sdk/src/private_store.rs @@ -346,7 +346,7 @@ impl SdkPrivateStore { ) -> Result<(), RadrootsSdkError> { validate_location_record(record)?; let parts = farm_location_parts(&record.farm_addr)?; - let owner_pubkey = public_key_bytes(parts.pubkey.as_str())?; + let owner_pubkey = parts.pubkey.as_bytes().to_vec(); let envelope = self.seal_farm_location(record)?; let nonce = envelope.header.nonce.to_vec(); let ciphertext = envelope.encode_json().map_err(private_store_error)?; @@ -388,7 +388,7 @@ impl SdkPrivateStore { farm_addr: &RadrootsAddressableCoordinate, ) -> Result<Option<SdkPrivateFarmLocationRecord>, RadrootsSdkError> { let parts = farm_location_parts(farm_addr)?; - let owner_pubkey = public_key_bytes(parts.pubkey.as_str())?; + let owner_pubkey = parts.pubkey.as_bytes().to_vec(); let row = sqlx::query( r#" SELECT ciphertext, nonce @@ -411,7 +411,7 @@ impl SdkPrivateStore { farm_addr: &RadrootsAddressableCoordinate, ) -> Result<bool, RadrootsSdkError> { let parts = farm_location_parts(farm_addr)?; - let owner_pubkey = public_key_bytes(parts.pubkey.as_str())?; + let owner_pubkey = parts.pubkey.as_bytes().to_vec(); sqlx::query( r#" DELETE FROM private_farm_location @@ -781,7 +781,7 @@ impl SdkPrivateStore { serde_json::from_slice(plaintext.as_slice()).map_err(private_store_error)?; Ok(SdkPrivateFarmLocationRecord { farm_addr, - farm_pubkey: parts.pubkey.as_str().to_owned(), + farm_pubkey: parts.pubkey.to_hex(), farm_d_tag: parts.d_tag.as_str().to_owned(), label: payload.label, latitude: payload.latitude, @@ -922,12 +922,6 @@ fn farm_location_parts( Ok(parts) } -fn public_key_bytes(pubkey: &str) -> Result<Vec<u8>, RadrootsSdkError> { - hex::decode(pubkey).map_err(|error| RadrootsSdkError::InvalidRequest { - message: format!("public key is invalid hex: {error}"), - }) -} - fn farm_location_key_slot(farm_addr: &str) -> String { format!("private_farm_location:{farm_addr}") } @@ -969,7 +963,7 @@ fn validate_location_record(record: &SdkPrivateFarmLocationRecord) -> Result<(), }); } let parts = farm_location_parts(&record.farm_addr)?; - if parts.pubkey.as_str() != record.farm_pubkey { + if parts.pubkey.to_hex() != record.farm_pubkey { return Err(RadrootsSdkError::InvalidRequest { message: "farm private location address pubkey does not match record pubkey".to_owned(), }); diff --git a/crates/sdk/src/signer_provider.rs b/crates/sdk/src/signer_provider.rs @@ -1,16 +1,16 @@ use crate::RadrootsSdkError; -use nostr::{JsonUtil, Kind, PublicKey, Tag, Tags, Timestamp, UnsignedEvent}; +use nostr::{JsonUtil, Kind, PublicKey as NostrPublicKey, Tag, Tags, Timestamp, UnsignedEvent}; use radroots_authority::{ RadrootsActorContext, RadrootsEventSigner, RadrootsSignerError, authorize_actor_for_draft, authorize_signer_for_draft, sign_authorized_draft, validate_signed_event_matches_draft, }; use radroots_event::draft::{RadrootsEventDraft, RadrootsSignedEvent}; -use radroots_event::ids::RadrootsPublicKey; use radroots_event::kinds::{ KIND_CLASSIFIED_LISTING, KIND_FARM, KIND_TRADE_CANCELLATION, KIND_TRADE_DECISION, KIND_TRADE_PROPOSAL, KIND_TRADE_REVISION_DECISION, KIND_TRADE_REVISION_PROPOSAL, }; use radroots_event::wire::RadrootsNip01EventWire; +use radroots_identity::PublicKey; use radroots_nostr::prelude::{RadrootsNostrEvent, RadrootsNostrKeys}; use radroots_nostr_connect::prelude::{ RadrootsNostrConnectClientRequest, RadrootsNostrConnectClientTarget, @@ -230,7 +230,7 @@ impl RadrootsSdkLocalKeySigner { pub fn from_shared_event_signer( signer: Arc<RadrootsSdkLocalSignerCapability>, ) -> Result<Self, RadrootsSdkError> { - let signer_pubkey = signer.pubkey().as_str().to_owned(); + let signer_pubkey = signer.pubkey().to_hex(); Ok(Self { signer, signer_pubkey, @@ -325,7 +325,7 @@ impl Default for RadrootsSdkMycNip46RequestPolicy { pub struct RadrootsSdkMycNip46Signer { client_keys: RadrootsNostrKeys, target: RadrootsNostrConnectClientTarget, - user_pubkey: RadrootsPublicKey, + user_pubkey: PublicKey, transport: Arc<dyn RadrootsSdkNip46Transport>, request_policy: RadrootsSdkMycNip46RequestPolicy, request_id_generator: Arc<dyn RadrootsSdkMycNip46RequestIdGenerator>, @@ -373,7 +373,7 @@ impl RadrootsSdkMycNip46Signer { request_id_generator: Arc<dyn RadrootsSdkMycNip46RequestIdGenerator>, ) -> Result<Self, RadrootsSdkError> { RadrootsSdkMycNip46RequestPolicy::new(request_policy.request_timeout())?; - let user_pubkey = RadrootsPublicKey::parse(user_pubkey.as_ref()).map_err(|error| { + let user_pubkey = PublicKey::from_hex(user_pubkey.as_ref()).map_err(|error| { RadrootsSdkError::InvalidRequest { message: format!("myc_nip46 user pubkey is invalid: {error}"), } @@ -392,7 +392,7 @@ impl RadrootsSdkMycNip46Signer { RadrootsSdkSignerStatus { mode: RadrootsSdkSignerMode::MycNip46, state: RadrootsSdkSignerState::Ready, - signer_pubkey: self.user_pubkey.as_str().to_owned(), + signer_pubkey: self.user_pubkey.to_hex(), remote_signer_pubkey: Some(self.target.remote_signer_public_key.to_hex()), relay_count: self.target.relays.len(), } @@ -401,7 +401,7 @@ impl RadrootsSdkMycNip46Signer { pub fn capability(&self) -> RadrootsSdkSignerCapability { RadrootsSdkSignerCapability { mode: RadrootsSdkSignerMode::MycNip46, - signer_pubkey: self.user_pubkey.as_str().to_owned(), + signer_pubkey: self.user_pubkey.to_hex(), remote_signer_pubkey: Some(self.target.remote_signer_public_key.to_hex()), relays: self.target.relays.iter().map(ToString::to_string).collect(), can_sign_events: true, @@ -471,7 +471,7 @@ impl RadrootsSdkMycNip46Signer { Ok(sign_receipt( request.operation_kind, RadrootsSdkSignerMode::MycNip46, - self.user_pubkey.as_str().to_owned(), + self.user_pubkey.to_hex(), Some(self.target.remote_signer_public_key.to_hex()), signed_event, )) @@ -516,11 +516,11 @@ pub fn radroots_sdk_myc_nip46_product_permission_strings() -> Vec<String> { } struct RadrootsSdkSignerIdentityOnly { - pubkey: RadrootsPublicKey, + pubkey: PublicKey, } impl RadrootsEventSigner for RadrootsSdkSignerIdentityOnly { - fn pubkey(&self) -> &RadrootsPublicKey { + fn pubkey(&self) -> &PublicKey { &self.pubkey } @@ -568,8 +568,10 @@ fn sign_event_request_from_frozen_draft( Ok(RadrootsNostrConnectRequest::SignEvent(unsigned_event)) } -fn nip46_unsigned_event_pubkey(draft: &RadrootsEventDraft) -> Result<PublicKey, RadrootsSdkError> { - PublicKey::parse(draft.expected_pubkey_str()).map_err(|error| { +fn nip46_unsigned_event_pubkey( + draft: &RadrootsEventDraft, +) -> Result<NostrPublicKey, RadrootsSdkError> { + NostrPublicKey::from_slice(draft.expected_pubkey().as_bytes()).map_err(|error| { nip46_sign_event_protocol_error(format!( "failed to parse frozen draft pubkey for NIP-46 unsigned event: {error}" )) diff --git a/crates/sdk/src/trade_runtime.rs b/crates/sdk/src/trade_runtime.rs @@ -1202,7 +1202,7 @@ async fn trade_command_plan( canonical.authored_at_unix_s, wire.tags, wire.content, - actor.pubkey().as_str(), + actor.pubkey().to_hex(), ) .map_err(|error| { trade_command_error( @@ -1307,7 +1307,7 @@ fn validate_actor_matches_envelope( actor: &RadrootsActorContext, envelope: &RadrootsTradeMutationEnvelopeV1, ) -> Result<(), RadrootsSdkError> { - if actor.pubkey().as_str() == envelope.author_pubkey.as_str() { + if actor.pubkey() == &envelope.author_pubkey { Ok(()) } else { Err(RadrootsSdkError::UnauthorizedActor { diff --git a/crates/sdk/src/workflow_runtime.rs b/crates/sdk/src/workflow_runtime.rs @@ -617,7 +617,7 @@ async fn prepare_runtime_operation_journal( ) .bind(SDK_RUNTIME_CONTRACT_VERSION) .bind(request.operation_kind) - .bind(request.actor.pubkey().as_str()) + .bind(request.actor.pubkey().to_hex()) .bind(idempotency_key.as_str()) .fetch_optional(&mut *tx) .await @@ -634,7 +634,7 @@ async fn prepare_runtime_operation_journal( let new_digest_prefix = digest_prefix(command_hash.as_str()); let error = RadrootsSdkError::IdempotencyConflict { operation_kind: request.operation_kind.to_owned(), - expected_pubkey_prefix: request.actor.pubkey().as_str().chars().take(12).collect(), + expected_pubkey_prefix: request.actor.pubkey().to_hex().chars().take(12).collect(), existing_digest_prefix: existing_digest_prefix.clone(), new_digest_prefix: new_digest_prefix.clone(), }; @@ -643,7 +643,7 @@ async fn prepare_runtime_operation_journal( ) .bind("idempotency_conflict") .bind(request.operation_kind) - .bind(request.actor.pubkey().as_str()) + .bind(request.actor.pubkey().to_hex()) .bind(idempotency_key.as_str()) .bind("retry_operation_with_same_idempotency_key") .bind( @@ -688,7 +688,7 @@ async fn prepare_runtime_operation_journal( .bind(observed_at_ms) .bind(SDK_RUNTIME_CONTRACT_VERSION) .bind(request.operation_kind) - .bind(request.actor.pubkey().as_str()) + .bind(request.actor.pubkey().to_hex()) .bind(idempotency_key.as_str()) .execute(&mut *tx) .await @@ -703,7 +703,7 @@ async fn prepare_runtime_operation_journal( ) .bind(SDK_RUNTIME_CONTRACT_VERSION) .bind(request.operation_kind) - .bind(request.actor.pubkey().as_str()) + .bind(request.actor.pubkey().to_hex()) .bind(idempotency_key.as_str()) .bind(command_hash.as_str()) .bind(frozen_draft_json.as_str()) @@ -755,7 +755,7 @@ async fn mark_runtime_operation_state( .bind(observed_at_ms) .bind(SDK_RUNTIME_CONTRACT_VERSION) .bind(request.operation_kind) - .bind(request.actor.pubkey().as_str()) + .bind(request.actor.pubkey().to_hex()) .bind(idempotency_key.as_str()) .execute(sdk._event_store.pool()) .await @@ -791,12 +791,13 @@ async fn record_runtime_operation_failure( _ => None, }; if let Some((recovery_code, recovery_action)) = recovery { + let actor_pubkey = request.actor.pubkey().to_hex(); record_runtime_recovery_receipt( sdk._event_store.pool(), RuntimeRecoveryReceiptWrite { recovery_code, operation_kind: Some(request.operation_kind), - actor_pubkey: Some(request.actor.pubkey().as_str()), + actor_pubkey: Some(actor_pubkey.as_str()), idempotency_key: Some(idempotency_key.as_str()), recovery_action, detail_json: error.detail_json(), @@ -818,7 +819,7 @@ async fn ensure_runtime_operation_can_commit( ) .bind(SDK_RUNTIME_CONTRACT_VERSION) .bind(request.operation_kind) - .bind(request.actor.pubkey().as_str()) + .bind(request.actor.pubkey().to_hex()) .bind(idempotency_key.as_str()) .fetch_one(tx.as_mut()) .await @@ -859,7 +860,7 @@ async fn commit_runtime_operation_journal( .bind(observed_at_ms) .bind(SDK_RUNTIME_CONTRACT_VERSION) .bind(request.operation_kind) - .bind(request.actor.pubkey().as_str()) + .bind(request.actor.pubkey().to_hex()) .bind(idempotency_key.as_str()) .execute(tx.as_mut()) .await @@ -968,7 +969,7 @@ fn frozen_draft_json(frozen_draft: &RadrootsEventDraft) -> Result<String, Radroo "created_at": frozen_draft.created_at_u64(), "tags": frozen_draft.tags_as_vec(), "content": frozen_draft.content(), - "expected_pubkey": frozen_draft.expected_pubkey_str(), + "expected_pubkey": frozen_draft.expected_pubkey().to_hex(), "expected_event_id": frozen_draft.expected_event_id_str() })) .map_err(|error| RadrootsSdkError::EventStore { @@ -1014,7 +1015,7 @@ fn runtime_request_digest( let digest_document = serde_json::json!({ "contract_version": SDK_RUNTIME_CONTRACT_VERSION, "operation_kind": request.operation_kind, - "actor_pubkey": request.actor.pubkey().as_str(), + "actor_pubkey": request.actor.pubkey().to_hex(), "draft": { "contract_id": request.frozen_draft.contract_id(), "contract_registry_version": request.frozen_draft.contract_registry_version(), @@ -1022,7 +1023,7 @@ fn runtime_request_digest( "created_at": request.frozen_draft.created_at_u64(), "tags": request.frozen_draft.tags_as_vec(), "content_sha256": hex::encode(Sha256::digest(request.frozen_draft.content().as_bytes())), - "expected_pubkey": request.frozen_draft.expected_pubkey_str(), + "expected_pubkey": request.frozen_draft.expected_pubkey().to_hex(), "expected_event_id": request.frozen_draft.expected_event_id_str() }, "delivery_plan": { diff --git a/crates/sdk/tests/identity_public_api.rs b/crates/sdk/tests/identity_public_api.rs @@ -3,100 +3,31 @@ #[test] fn identity_models_are_public_through_identity_module() { use radroots_sdk::identity::{ - DEFAULT_IDENTITY_PATH, IdentityError, RADROOTS_USERNAME_MAX_LEN, RADROOTS_USERNAME_MIN_LEN, - RADROOTS_USERNAME_REGEX, RadrootsIdentity, RadrootsIdentityFile, RadrootsIdentityId, - RadrootsIdentityProfile, RadrootsIdentityPublic, RadrootsIdentitySecretKeyFormat, - radroots_username_is_valid, radroots_username_normalize, + AccountId, Error, IdentityId, Profile, PublicIdentity, PublicKey, Username, + username::{MAX_LENGTH, MIN_LENGTH}, }; - assert_eq!(DEFAULT_IDENTITY_PATH, "default.json"); - const { assert!(RADROOTS_USERNAME_MIN_LEN <= RADROOTS_USERNAME_MAX_LEN) }; - assert!(RADROOTS_USERNAME_REGEX.contains("[a-z0-9._-]")); + const { assert!(MIN_LENGTH <= MAX_LENGTH) }; - let normalized = radroots_username_normalize(" Field_User ").expect("normalized username"); - assert!(radroots_username_is_valid(normalized.as_str())); + let username = Username::parse(" Field_User ").expect("normalized username"); + assert_eq!(username.as_str(), "field_user"); - let identity = RadrootsIdentity::generate(); - let identity_id = RadrootsIdentityId::parse(identity.public_key_hex().as_str()) - .expect("identity id parses from public key"); - let public_identity = RadrootsIdentityPublic::new(identity.public_key()); - let empty_profile = RadrootsIdentityProfile::default(); - let identity_file = identity.to_file_with_secret_format(RadrootsIdentitySecretKeyFormat::Hex); + let public_key = + PublicKey::from_hex("585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df") + .expect("valid public key"); + let identity_id = IdentityId::from(public_key); + let public_identity = PublicIdentity::new(public_key) + .with_profile(Profile::new().with_username(username.clone())); + let account_id = AccountId::from(&public_identity); - assert_eq!(identity_id.as_str(), identity.public_key_hex()); - assert_eq!(public_identity.public_key_hex, identity.public_key_hex()); - assert!(empty_profile.is_empty()); - assert!(!identity_file.secret_key.is_empty()); - assert!(matches!( - RadrootsIdentityId::parse("not-a-public-key"), - Err(IdentityError::InvalidPublicKey(_)) - )); - let _: RadrootsIdentityFile = identity_file; -} - -#[cfg(feature = "identity-storage")] -#[test] -fn identity_storage_is_public_through_identity_module() { - use radroots_sdk::identity::{ - RADROOTS_ENCRYPTED_IDENTITY_DEFAULT_KEY_SLOT, RADROOTS_ENCRYPTED_IDENTITY_KEY_SUFFIX, - RadrootsEncryptedIdentityFile, RadrootsIdentity, encrypted_identity_wrapping_key_path, - load_encrypted_identity, load_encrypted_identity_with_key_slot, load_identity_profile, - rotate_encrypted_identity, rotate_encrypted_identity_with_key_slot, - store_encrypted_identity, store_encrypted_identity_with_key_slot, store_identity_profile, - }; - - let temp = tempfile::tempdir().expect("tempdir"); - let encrypted_path = temp.path().join("sdk-identity.enc.json"); - let profile_path = temp.path().join("sdk-profile.json"); - let identity = RadrootsIdentity::generate(); - - let encrypted_file = RadrootsEncryptedIdentityFile::new(encrypted_path.clone()); - encrypted_file.store(&identity).expect("store identity"); - assert_eq!( - encrypted_file - .load() - .expect("load identity") - .public_key_hex(), - identity.public_key_hex() - ); - - store_encrypted_identity(encrypted_path.as_path(), &identity).expect("store encrypted"); + assert_eq!(identity_id, public_identity.id()); + assert_eq!(account_id.to_hex(), public_key.to_hex()); assert_eq!( - load_encrypted_identity(encrypted_path.as_path()) - .expect("load encrypted") - .public_key_hex(), - identity.public_key_hex() - ); - - store_encrypted_identity_with_key_slot(encrypted_path.as_path(), "sdk-api", &identity) - .expect("store encrypted with key slot"); - assert_eq!( - load_encrypted_identity_with_key_slot(encrypted_path.as_path(), "sdk-api") - .expect("load encrypted with key slot") - .public_key_hex(), - identity.public_key_hex() - ); - - rotate_encrypted_identity(encrypted_path.as_path()).expect("rotate default key slot"); - rotate_encrypted_identity_with_key_slot(encrypted_path.as_path(), "sdk-api") - .expect("rotate named key slot"); - store_identity_profile(profile_path.as_path(), &identity).expect("store profile"); - - assert_eq!( - load_identity_profile(profile_path.as_path()) - .expect("load profile") - .public_key_hex, - identity.public_key_hex() - ); - assert_eq!( - RADROOTS_ENCRYPTED_IDENTITY_DEFAULT_KEY_SLOT, - "radroots_identity" - ); - assert_eq!(RADROOTS_ENCRYPTED_IDENTITY_KEY_SUFFIX, ".key"); - assert_eq!( - encrypted_identity_wrapping_key_path(encrypted_path.as_path()) - .file_name() - .and_then(|name| name.to_str()), - Some("sdk-identity.enc.json.key") + public_identity.profile().and_then(Profile::username), + Some(&username) ); + assert!(matches!( + PublicKey::from_hex("not-a-public-key"), + Err(Error::InvalidHexLength { .. }) + )); } diff --git a/crates/sdk/tests/knowledge_public_api.rs b/crates/sdk/tests/knowledge_public_api.rs @@ -342,7 +342,7 @@ fn knowledge_draft_builder_freezes_mvp_drafts_without_runtime() { field_report.contract_id(), KNOWLEDGE_FIELD_REPORT_CONTRACT_ID ); - assert_eq!(claim.expected_pubkey_str(), public_key_hex()); + assert_eq!(claim.expected_pubkey().to_hex(), public_key_hex()); assert_eq!(claim.created_at_u64(), u64::from(CREATED_AT)); assert_eq!(claim.kind_u32(), KIND_KNOWLEDGE_CLAIM); } diff --git a/crates/sdk/tests/listings_runtime.rs b/crates/sdk/tests/listings_runtime.rs @@ -91,11 +91,15 @@ fn listing(d_tag: &str, title: &str) -> RadrootsOperationalListing { primary_bin_id: RadrootsInventoryBinId::parse("bin-1").expect("bin id"), bins: vec![RadrootsOperationalListingBin { bin_id: RadrootsInventoryBinId::parse("bin-1").expect("bin id"), - quantity: Quantity::new(Decimal::from(1000u32), Unit::MassG), - price_per_canonical_unit: QuantityPrice { - amount: Money::new(Decimal::from(20u32), Currency::USD), - quantity: Quantity::new(Decimal::from(1u32), Unit::MassG), - }, + quantity: Quantity::try_new(Decimal::from(1000u32), Unit::MassG) + .expect("positive fixture quantity"), + price_per_canonical_unit: QuantityPrice::try_new( + Money::try_new(Decimal::from(20u32), Currency::USD) + .expect("non-negative fixture money"), + Quantity::try_new(Decimal::from(1u32), Unit::MassG) + .expect("positive fixture pricing quantity"), + ) + .expect("non-zero fixture pricing quantity"), display_amount: None, display_unit: None, display_label: None, diff --git a/crates/sdk/tests/replica_ingest.rs b/crates/sdk/tests/replica_ingest.rs @@ -82,5 +82,5 @@ fn sdk_farm_draft_ingests_into_replica_projection() { assert_eq!(farms.len(), 1); assert_eq!(farms[0].d_tag, sample_farm().d_tag); assert_eq!(farms[0].name, sample_farm().name); - assert_eq!(farms[0].pubkey, event.author_str()); + assert_eq!(farms[0].pubkey, event.author().to_hex()); } diff --git a/crates/sdk/tests/support/fixture_signer.rs b/crates/sdk/tests/support/fixture_signer.rs @@ -1,5 +1,6 @@ use radroots_authority::{RadrootsEventSigner, RadrootsSignerError, RadrootsSignerIdentity}; use radroots_event::draft::{RadrootsEventDraft, RadrootsSignedEvent}; +use radroots_identity::PublicKey; use radroots_nostr::prelude::{RadrootsNostrKeys, radroots_nostr_sign_frozen_draft}; use std::sync::LazyLock; @@ -48,7 +49,7 @@ impl FixtureSigner { } impl RadrootsEventSigner for FixtureSigner { - fn pubkey(&self) -> &radroots_event::ids::RadrootsPublicKey { + fn pubkey(&self) -> &PublicKey { self.identity.pubkey() } @@ -56,7 +57,7 @@ impl RadrootsEventSigner for FixtureSigner { &self, draft: &RadrootsEventDraft, ) -> Result<RadrootsSignedEvent, RadrootsSignerError> { - if self.pubkey().as_str() != draft.expected_pubkey_str() { + if self.pubkey() != draft.expected_pubkey() { return Err(RadrootsSignerError::SigningFailed { message: "wrong fixture signer".to_owned(), }); diff --git a/crates/sdk/tests/sync_runtime.rs b/crates/sdk/tests/sync_runtime.rs @@ -410,11 +410,15 @@ fn listing(d_tag: &str, title: &str) -> RadrootsOperationalListing { primary_bin_id: RadrootsInventoryBinId::parse("bin-1").expect("bin id"), bins: vec![RadrootsOperationalListingBin { bin_id: RadrootsInventoryBinId::parse("bin-1").expect("bin id"), - quantity: Quantity::new(Decimal::from(1000u32), Unit::MassG), - price_per_canonical_unit: QuantityPrice { - amount: Money::new(Decimal::from(20u32), Currency::USD), - quantity: Quantity::new(Decimal::from(1u32), Unit::MassG), - }, + quantity: Quantity::try_new(Decimal::from(1000u32), Unit::MassG) + .expect("positive fixture quantity"), + price_per_canonical_unit: QuantityPrice::try_new( + Money::try_new(Decimal::from(20u32), Currency::USD) + .expect("non-negative fixture money"), + Quantity::try_new(Decimal::from(1u32), Unit::MassG) + .expect("positive fixture pricing quantity"), + ) + .expect("non-zero fixture pricing quantity"), display_amount: None, display_unit: None, display_label: None, diff --git a/crates/sdk/tests/unit/adapters_nostr_tests.rs b/crates/sdk/tests/unit/adapters_nostr_tests.rs @@ -1,17 +1,16 @@ use super::{ - client_from_identity, configure_write_relays, connected_client_from_identity, - connected_relay_urls, publish_signed_event, signerless_client, signerless_client_with_options, + client_from_keys, configure_write_relays, connected_client_from_keys, connected_relay_urls, + publish_signed_event, signerless_client, signerless_client_with_options, }; -use crate::identity::RadrootsIdentity; use core::time::Duration; -use nostr::{EventBuilder, Kind}; +use nostr::{EventBuilder, Keys, Kind}; use radroots_nostr::prelude::RadrootsNostrClientOptions; use tokio::runtime::Runtime; #[test] fn client_constructors_build_without_runtime_net() { - let identity = RadrootsIdentity::generate(); - let _client = client_from_identity(&identity); + let keys = Keys::generate(); + let _client = client_from_keys(keys); let _signerless = signerless_client(); let _signerless_with_options = signerless_client_with_options(RadrootsNostrClientOptions::new()) @@ -31,8 +30,8 @@ fn signerless_client_has_no_signer() { fn relay_helpers_accept_empty_relay_sets_without_network_endpoints() { let runtime = Runtime::new().expect("tokio runtime"); runtime.block_on(async { - let identity = RadrootsIdentity::generate(); - let client = client_from_identity(&identity); + let keys = Keys::generate(); + let client = client_from_keys(keys.clone()); configure_write_relays(&client, &[], Duration::from_millis(1)) .await @@ -44,8 +43,8 @@ fn relay_helpers_accept_empty_relay_sets_without_network_endpoints() { .await .expect_err("invalid relay"); assert!(format!("{error:?}").contains("Url")); - let connected_error = match connected_client_from_identity( - &identity, + let connected_error = match connected_client_from_keys( + keys.clone(), &invalid_relays, Duration::from_millis(1), ) @@ -56,7 +55,7 @@ fn relay_helpers_accept_empty_relay_sets_without_network_endpoints() { }; assert!(format!("{connected_error:?}").contains("Url")); - let disconnected = client_from_identity(&identity); + let disconnected = client_from_keys(keys.clone()); disconnected .add_write_relay("wss://relay.example.com") .await @@ -66,7 +65,7 @@ fn relay_helpers_accept_empty_relay_sets_without_network_endpoints() { Vec::<String>::new() ); - let connected = connected_client_from_identity(&identity, &[], Duration::from_millis(1)) + let connected = connected_client_from_keys(keys.clone(), &[], Duration::from_millis(1)) .await .expect("connected client"); assert_eq!(connected_relay_urls(&connected).await, Vec::<String>::new()); @@ -74,7 +73,7 @@ fn relay_helpers_accept_empty_relay_sets_without_network_endpoints() { // Relay publication consumes an already-signed transport fixture; it // does not expose an SDK event-authoring path. let signed = EventBuilder::new(Kind::Custom(30_001), "hello") - .sign_with_keys(identity.keys()) + .sign_with_keys(&keys) .expect("signed event"); let error = publish_signed_event(&connected, &signed) .await diff --git a/crates/sdk/tests/unit/adapters_radrootsd_tests.rs b/crates/sdk/tests/unit/adapters_radrootsd_tests.rs @@ -11,6 +11,9 @@ use std::io::{Read, Write}; use std::net::TcpListener; use std::thread::JoinHandle; +const SIGNED_EVENT_PUBLIC_KEY: &str = + "585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df"; + struct RecordedHttpRequest { request_line: String, headers: Vec<(String, String)>, @@ -89,7 +92,7 @@ fn spawn_http_server( fn signed_event() -> RadrootsSignedEvent { let mut wire = RadrootsNip01EventWire { id: String::new(), - pubkey: "b".repeat(64), + pubkey: SIGNED_EVENT_PUBLIC_KEY.to_owned(), created_at: 1_700_000_000, kind: 30_402, tags: vec![vec!["d".to_owned(), "listing-1".to_owned()]], @@ -120,7 +123,7 @@ fn signed_event_id() -> String { } fn signed_event_pubkey() -> String { - signed_event().pubkey_str().to_owned() + signed_event().pubkey().to_hex().to_owned() } fn job_status_for_outcome(outcome_kind: TransportPublishOutcomeKind) -> TransportPublishJobStatus { @@ -362,7 +365,7 @@ fn publish_event_request_json_uses_signed_event_contract() { let raw_event_json = value["raw_event_json"].as_str().expect("raw event json"); let raw_event: serde_json::Value = serde_json::from_str(raw_event_json).expect("raw event"); assert_eq!(raw_event["id"], signed_event_id()); - assert_eq!(raw_event["pubkey"], "b".repeat(64)); + assert_eq!(raw_event["pubkey"], SIGNED_EVENT_PUBLIC_KEY); assert_eq!(raw_event["kind"], 30_402); assert_eq!(value["target_policy"]["kind"], "nostr"); assert_eq!( diff --git a/crates/sdk/tests/unit/error_tests.rs b/crates/sdk/tests/unit/error_tests.rs @@ -45,7 +45,7 @@ fn listing_and_store_errors_convert_to_sdk_error_classes() { let draft_fallback = RadrootsSdkError::from( radroots_trade::operational_listing::RadrootsOperationalListingEditError::InvalidFarmPubkey( - radroots_event::ids::RadrootsIdParseError::InvalidCharacter, + radroots_identity::PublicKey::from_hex("bad").expect_err("invalid public key"), ), ); assert!(matches!( diff --git a/crates/sdk/tests/unit/idempotency_tests.rs b/crates/sdk/tests/unit/idempotency_tests.rs @@ -1,6 +1,7 @@ use super::{SdkIdempotencyKey, SdkTradeIdempotencyRecord}; use crate::RadrootsSdkError; -use radroots_event::ids::{RadrootsEventId, RadrootsPublicKey}; +use radroots_event::ids::RadrootsEventId; +use radroots_identity::PublicKey; use crate::serializer_failure::assert_struct_serialize_error_paths; @@ -54,7 +55,7 @@ fn trade_idempotency_record_binds_payload_and_reports_conflicts() { idempotency_key: SdkIdempotencyKey::new("01890f0e-6c00-7000-8000-000000000003") .expect("key"), operation_kind: "trade.submit.v1".to_owned(), - actor_pubkey: RadrootsPublicKey::parse( + actor_pubkey: PublicKey::from_hex( "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", ) .expect("actor pubkey"), diff --git a/crates/sdk/tests/unit/identity_tests.rs b/crates/sdk/tests/unit/identity_tests.rs @@ -1,13 +0,0 @@ -use super::{RadrootsEncryptedIdentityFile, RadrootsIdentity}; - -#[test] -fn encrypted_identity_file_round_trips() { - let temp = tempfile::tempdir().expect("tempdir"); - let file = RadrootsEncryptedIdentityFile::new(temp.path().join("identity.enc.json")); - let identity = RadrootsIdentity::generate(); - - file.store(&identity).expect("store identity"); - let loaded = file.load().expect("load identity"); - - assert_eq!(loaded.public_key_hex(), identity.public_key_hex()); -} diff --git a/crates/sdk/tests/unit/listings_runtime_tests.rs b/crates/sdk/tests/unit/listings_runtime_tests.rs @@ -57,11 +57,15 @@ fn listing_for_seller(seller: &str, d_tag: &str, title: &str) -> RadrootsOperati primary_bin_id: RadrootsInventoryBinId::parse("bin-1").expect("bin id"), bins: vec![RadrootsOperationalListingBin { bin_id: RadrootsInventoryBinId::parse("bin-1").expect("bin id"), - quantity: Quantity::new(Decimal::from(12u32), Unit::Each), - price_per_canonical_unit: QuantityPrice { - amount: Money::new(Decimal::from(4u32), Currency::USD), - quantity: Quantity::new(Decimal::from(1u32), Unit::Each), - }, + quantity: Quantity::try_new(Decimal::from(12u32), Unit::Each) + .expect("positive fixture quantity"), + price_per_canonical_unit: QuantityPrice::try_new( + Money::try_new(Decimal::from(4u32), Currency::USD) + .expect("non-negative fixture money"), + Quantity::try_new(Decimal::from(1u32), Unit::Each) + .expect("positive fixture pricing quantity"), + ) + .expect("non-zero fixture pricing quantity"), display_amount: None, display_unit: None, display_label: None, diff --git a/crates/sdk/tests/unit/signer_provider_tests.rs b/crates/sdk/tests/unit/signer_provider_tests.rs @@ -411,7 +411,7 @@ fn signer_provider_reports_myc_status_capability_and_constructor_errors() { fn nip46_private_helpers_map_identity_adapter_and_response_edges() { let pubkey = user_pubkey().parse().expect("pubkey"); let identity = RadrootsSdkSignerIdentityOnly { pubkey }; - assert_eq!(identity.pubkey().as_str(), user_pubkey()); + assert_eq!(identity.pubkey().to_hex(), user_pubkey()); assert!(matches!( identity.sign_frozen_draft(&frozen_draft()), Err(RadrootsSignerError::Unavailable) @@ -587,7 +587,7 @@ async fn myc_nip46_provider_signs_and_validates_remote_event() { .collect::<Vec<_>>(); assert_eq!( sign_event_request.pubkey.to_hex(), - draft.expected_pubkey_str() + draft.expected_pubkey().to_hex() ); assert_eq!( sign_event_request.created_at.as_secs(), diff --git a/crates/sdk/tests/unit/sync_runtime_tests.rs b/crates/sdk/tests/unit/sync_runtime_tests.rs @@ -30,6 +30,8 @@ use radroots_event::ids::RadrootsEventId; use radroots_event::kinds::KIND_FARM; use radroots_event_store::RadrootsEventStoreStatusSummary; #[cfg(feature = "radrootsd-execution")] +use radroots_identity::PublicKey; +#[cfg(feature = "radrootsd-execution")] use radroots_nostr::prelude::{RadrootsNostrKeys, radroots_nostr_sign_frozen_draft}; #[cfg(feature = "radrootsd-execution")] use radroots_outbox::{ @@ -100,7 +102,7 @@ impl RadrootsdFixtureSigner { #[cfg(feature = "radrootsd-execution")] impl RadrootsEventSigner for RadrootsdFixtureSigner { - fn pubkey(&self) -> &radroots_event::ids::RadrootsPublicKey { + fn pubkey(&self) -> &PublicKey { self.identity.pubkey() } @@ -1459,7 +1461,8 @@ async fn radrootsd_completion_updates_outbox_for_success_retryable_and_terminal_ .signed_event .as_ref() .expect("signed event") - .pubkey_str() + .pubkey() + .to_hex() ); assert_eq!( publish.event_kind, diff --git a/crates/sdk/tests/unit/trade_runtime_tests.rs b/crates/sdk/tests/unit/trade_runtime_tests.rs @@ -8,7 +8,7 @@ use radroots_event::{ contract::RadrootsActorRole, ids::{ RadrootsClassifiedListingAddress, RadrootsDTag, RadrootsEventId, RadrootsInventoryBinId, - RadrootsPublicKey, RadrootsTradeId, + RadrootsTradeId, }, kinds::TRADE_MUTATION_EVENT_KINDS, trade::{ @@ -22,14 +22,15 @@ use radroots_event::{ RadrootsTradeMutationEnvelopeV1, canonical_trade_mutation_content, }, }; +use radroots_identity::PublicKey; use radroots_nostr::prelude::RadrootsNostrKeys; use radroots_trade::workflow::{ RADROOTS_TRADE_REDUCER_CONTRACT_ID, RADROOTS_TRADE_REDUCER_VERSION, RadrootsTradePrivateTermsStateV1, }; -fn pubkey(value: &str) -> RadrootsPublicKey { - RadrootsPublicKey::parse(value).expect("pubkey") +fn pubkey(value: &str) -> PublicKey { + PublicKey::from_hex(value).expect("pubkey") } fn event_id(marker: char) -> RadrootsEventId { diff --git a/crates/sdk/tests/unit/workflow_runtime_tests.rs b/crates/sdk/tests/unit/workflow_runtime_tests.rs @@ -5,6 +5,7 @@ use radroots_authority::{RadrootsSignerError, RadrootsSignerIdentity}; use radroots_event::contract::RadrootsActorRole; use radroots_event::draft::{RadrootsEventDraft, RadrootsSignedEvent, RadrootsSignedEventParts}; use radroots_event::kinds::{KIND_FARM, KIND_GEOCHAT}; +use radroots_identity::PublicKey; use radroots_nostr::prelude::{RadrootsNostrKeys, radroots_nostr_sign_frozen_draft}; use std::sync::LazyLock; @@ -55,7 +56,7 @@ impl FailIfCalledSigner { } impl RadrootsEventSigner for FailIfCalledSigner { - fn pubkey(&self) -> &radroots_event::ids::RadrootsPublicKey { + fn pubkey(&self) -> &PublicKey { self.identity.pubkey() } @@ -76,7 +77,7 @@ impl InvalidSignatureSigner { } impl RadrootsEventSigner for InvalidSignatureSigner { - fn pubkey(&self) -> &radroots_event::ids::RadrootsPublicKey { + fn pubkey(&self) -> &PublicKey { self.0.pubkey() } @@ -98,7 +99,7 @@ impl RadrootsEventSigner for InvalidSignatureSigner { } impl RadrootsEventSigner for WorkflowSigner { - fn pubkey(&self) -> &radroots_event::ids::RadrootsPublicKey { + fn pubkey(&self) -> &PublicKey { self.identity.pubkey() } @@ -151,7 +152,7 @@ fn signed_event() -> RadrootsSignedEvent { let sig = "c".repeat(128); let raw_json = serde_json::json!({ "id": draft.expected_event_id_str(), - "pubkey": draft.expected_pubkey_str(), + "pubkey": draft.expected_pubkey().to_hex(), "created_at": draft.created_at_u64(), "kind": draft.kind_u32(), "tags": draft.tags_as_vec(), @@ -161,7 +162,7 @@ fn signed_event() -> RadrootsSignedEvent { .to_string(); RadrootsSignedEvent::new(RadrootsSignedEventParts { id: draft.expected_event_id_str().to_owned(), - pubkey: draft.expected_pubkey_str().to_owned(), + pubkey: draft.expected_pubkey().to_hex().to_owned(), created_at: draft.created_at_u64(), kind: draft.kind_u32(), tags: draft.tags_as_vec(), diff --git a/packages/identity-bindings/src/generated/constants.ts b/packages/identity-bindings/src/generated/constants.ts @@ -1,7 +1,5 @@ // @generated by cargo xtask generate ts // Do not edit by hand. -export const RADROOTS_USERNAME_MIN_LEN = 3; +export const RADROOTS_USERNAME_MIN_LENGTH = 3; -export const RADROOTS_USERNAME_MAX_LEN = 30; - -export const RADROOTS_USERNAME_REGEX = "^(?!.*\\.\\.)(?!\\.)(?!.*\\.$)[a-z0-9._-]{3,30}$"; +export const RADROOTS_USERNAME_MAX_LENGTH = 30;