lib

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

commit a67cf7a93a9462341307d85f361122f4e0f01d07
parent c5d6ee72094ccb1f368389569d6e179dd9a3a4e1
Author: triesap <tyson@radroots.org>
Date:   Fri, 31 Jul 2026 14:38:30 +0000

nostr: migrate workspace consumers

- Remove legacy Nostr alias paths from SDK production, test, and example consumers.
- Seal NIP-46 client credentials behind a single-owner redacted SDK boundary.
- Restrict raw upstream-key transport constructors to internal test support.
- Verify all-feature checks, strict Clippy, tests, doctests, and architecture.

Diffstat:
Mcrates/sdk/examples/sdk_v1_myc_nip46_signer_setup.rs | 12++++++------
Mcrates/sdk/src/adapters/nostr.rs | 15+++++++++++----
Mcrates/sdk/src/lib.rs | 8++++----
Mcrates/sdk/src/signer_provider.rs | 62+++++++++++++++++++++++++++++++++++++++++++++++++++++++-------
Mcrates/sdk/tests/support/fixture_signer.rs | 4++--
Mcrates/sdk/tests/unit/runtime_tests.rs | 2+-
Mcrates/sdk/tests/unit/signer_provider_tests.rs | 13++++++++++---
Mcrates/sdk/tests/unit/sync_runtime_tests.rs | 4++--
Mcrates/sdk/tests/unit/workflow_runtime_tests.rs | 2+-
9 files changed, 92 insertions(+), 30 deletions(-)

diff --git a/crates/sdk/examples/sdk_v1_myc_nip46_signer_setup.rs b/crates/sdk/examples/sdk_v1_myc_nip46_signer_setup.rs @@ -1,13 +1,13 @@ +use nostr::Keys as RadrootsNostrKeys; use radroots_event::envelope::kind::KIND_TRADE_PROPOSAL; use radroots_nostr::event::Event as RadrootsNostrEvent; -use radroots_nostr::types::RadrootsNostrKeys; use radroots_nostr_connect::prelude::{ RadrootsNostrConnectClientTarget, RadrootsNostrConnectError, }; use radroots_sdk::{ - RadrootsClient, RadrootsSdkMycNip46Signer, RadrootsSdkNip46Transport, - RadrootsSdkNip46TransportFuture, RadrootsSdkSignerMode, RadrootsSdkSignerProvider, - radroots_sdk_myc_nip46_product_permission_strings, + RadrootsClient, RadrootsSdkMycNip46Signer, RadrootsSdkNip46ClientKey, + RadrootsSdkNip46Transport, RadrootsSdkNip46TransportFuture, RadrootsSdkSignerMode, + RadrootsSdkSignerProvider, radroots_sdk_myc_nip46_product_permission_strings, }; use std::sync::Arc; @@ -30,7 +30,7 @@ impl RadrootsSdkNip46Transport for ExampleNip46Transport { #[tokio::main] async fn main() -> Result<(), Box<dyn std::error::Error>> { - let client_keys = RadrootsNostrKeys::generate(); + let client_key = RadrootsSdkNip46ClientKey::generate(); let remote_signer_keys = RadrootsNostrKeys::generate(); let user_keys = RadrootsNostrKeys::generate(); let target = RadrootsNostrConnectClientTarget::new( @@ -38,7 +38,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> { vec![nostr::RelayUrl::parse("wss://relay.example.com")?], ); let signer = RadrootsSdkMycNip46Signer::new( - client_keys, + client_key, target, user_keys.public_key().to_hex(), Arc::new(ExampleNip46Transport), diff --git a/crates/sdk/src/adapters/nostr.rs b/crates/sdk/src/adapters/nostr.rs @@ -1,7 +1,10 @@ use core::time::Duration; +#[cfg(test)] +use nostr::Keys as RadrootsNostrKeys; use radroots_nostr::event::{Event as RadrootsNostrEvent, EventId as RadrootsNostrEventId}; -use radroots_nostr::types::RadrootsNostrKeys; +#[cfg(test)] +use radroots_transport_nostr::RadrootsNostrClientKey; use radroots_transport_nostr::{ RadrootsNostrClient, RadrootsNostrClientOptions, RadrootsNostrOutput, RadrootsRelayTransportError, @@ -15,8 +18,11 @@ pub fn signerless_client_with_options(options: RadrootsNostrClientOptions) -> Ra RadrootsNostrClient::new_signerless_with_options(options) } -pub fn client_from_keys(keys: RadrootsNostrKeys) -> RadrootsNostrClient { - RadrootsNostrClient::new(keys) +#[cfg(test)] +pub(crate) fn client_from_keys(keys: RadrootsNostrKeys) -> RadrootsNostrClient { + let key = RadrootsNostrClientKey::from_secret_key_bytes(keys.secret_key().to_secret_bytes()) + .expect("an existing Nostr key remains valid at the transport boundary"); + RadrootsNostrClient::new(key) } pub async fn configure_write_relays( @@ -32,7 +38,8 @@ pub async fn configure_write_relays( Ok(()) } -pub async fn connected_client_from_keys( +#[cfg(test)] +pub(crate) async fn connected_client_from_keys( keys: RadrootsNostrKeys, relay_urls: &[String], connect_timeout: Duration, diff --git a/crates/sdk/src/lib.rs b/crates/sdk/src/lib.rs @@ -153,10 +153,10 @@ pub use crate::signer_provider::{ RADROOTS_SDK_MYC_NIP46_DEFAULT_REQUEST_TIMEOUT_MS, RADROOTS_SDK_MYC_NIP46_PRODUCT_SIGN_EVENT_KINDS, RadrootsSdkLocalKeySigner, RadrootsSdkLocalSignerCapability, RadrootsSdkMycNip46RequestPolicy, RadrootsSdkMycNip46Signer, - RadrootsSdkNip46Transport, RadrootsSdkNip46TransportFuture, RadrootsSdkSignReceipt, - RadrootsSdkSignRequest, RadrootsSdkSignerCapability, RadrootsSdkSignerMode, - RadrootsSdkSignerProgress, RadrootsSdkSignerProgressSink, RadrootsSdkSignerProvider, - RadrootsSdkSignerState, RadrootsSdkSignerStatus, + RadrootsSdkNip46ClientKey, RadrootsSdkNip46Transport, RadrootsSdkNip46TransportFuture, + RadrootsSdkSignReceipt, RadrootsSdkSignRequest, RadrootsSdkSignerCapability, + RadrootsSdkSignerMode, RadrootsSdkSignerProgress, RadrootsSdkSignerProgressSink, + RadrootsSdkSignerProvider, RadrootsSdkSignerState, RadrootsSdkSignerStatus, radroots_sdk_myc_nip46_product_permission_strings, radroots_sdk_myc_nip46_product_permissions, }; #[cfg(feature = "runtime")] diff --git a/crates/sdk/src/signer_provider.rs b/crates/sdk/src/signer_provider.rs @@ -1,4 +1,6 @@ use crate::{RadrootsSdkError, workflow_runtime::signing_operation_id}; +use core::fmt; +use nostr::Keys as RadrootsNostrKeys; use nostr::{JsonUtil, Kind, PublicKey as NostrPublicKey, Tag, Tags, Timestamp, UnsignedEvent}; use radroots_event::draft::{EventDraft, SignedEvent}; use radroots_event::envelope::kind::{ @@ -8,7 +10,6 @@ use radroots_event::envelope::kind::{ use radroots_event::wire::Nip01EventWire; use radroots_identity::PublicKey; use radroots_nostr::event::Event as RadrootsNostrEvent; -use radroots_nostr::types::RadrootsNostrKeys; use radroots_nostr_connect::prelude::{ RadrootsNostrConnectClientRequest, RadrootsNostrConnectClientTarget, RadrootsNostrConnectClientTransport, RadrootsNostrConnectClientTransportFuture, @@ -39,6 +40,53 @@ pub const RADROOTS_SDK_MYC_NIP46_PRODUCT_SIGN_EVENT_KINDS: [u32; 7] = [ ]; pub const RADROOTS_SDK_MYC_NIP46_DEFAULT_REQUEST_TIMEOUT_MS: u64 = 30_000; +/// Opaque client key used only to authenticate and encrypt one NIP-46 session. +/// +/// The key is single-owner, cannot be serialized, and never exposes its +/// secret representation. Generate it explicitly and move it into +/// [`RadrootsSdkMycNip46Signer::new`]. +/// +/// ```compile_fail +/// use radroots_sdk::RadrootsSdkNip46ClientKey; +/// +/// let key = RadrootsSdkNip46ClientKey::generate(); +/// let _duplicate = key.clone(); +/// ``` +/// +/// ```compile_fail +/// use radroots_sdk::RadrootsSdkNip46ClientKey; +/// +/// let key = RadrootsSdkNip46ClientKey::generate(); +/// let _json = serde_json::to_string(&key)?; +/// # Ok::<(), serde_json::Error>(()) +/// ``` +pub struct RadrootsSdkNip46ClientKey { + keys: RadrootsNostrKeys, +} + +impl RadrootsSdkNip46ClientKey { + /// Generates fresh client key material for one NIP-46 signer session. + #[must_use] + pub fn generate() -> Self { + Self { + keys: RadrootsNostrKeys::generate(), + } + } + + fn into_keys(self) -> RadrootsNostrKeys { + self.keys + } +} + +impl fmt::Debug for RadrootsSdkNip46ClientKey { + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter + .debug_tuple("RadrootsSdkNip46ClientKey") + .field(&"[redacted]") + .finish() + } +} + #[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize)] #[serde(rename_all = "snake_case")] #[non_exhaustive] @@ -343,13 +391,13 @@ pub struct RadrootsSdkMycNip46Signer { impl RadrootsSdkMycNip46Signer { pub fn new( - client_keys: RadrootsNostrKeys, + client_key: RadrootsSdkNip46ClientKey, target: RadrootsNostrConnectClientTarget, user_pubkey: impl AsRef<str>, transport: Arc<dyn RadrootsSdkNip46Transport>, ) -> Result<Self, RadrootsSdkError> { Self::new_with_request_policy( - client_keys, + client_key, target, user_pubkey, transport, @@ -358,14 +406,14 @@ impl RadrootsSdkMycNip46Signer { } pub fn new_with_request_policy( - client_keys: RadrootsNostrKeys, + client_key: RadrootsSdkNip46ClientKey, target: RadrootsNostrConnectClientTarget, user_pubkey: impl AsRef<str>, transport: Arc<dyn RadrootsSdkNip46Transport>, request_policy: RadrootsSdkMycNip46RequestPolicy, ) -> Result<Self, RadrootsSdkError> { Self::new_with_request_id_generator( - client_keys, + client_key, target, user_pubkey, transport, @@ -375,7 +423,7 @@ impl RadrootsSdkMycNip46Signer { } fn new_with_request_id_generator( - client_keys: RadrootsNostrKeys, + client_key: RadrootsSdkNip46ClientKey, target: RadrootsNostrConnectClientTarget, user_pubkey: impl AsRef<str>, transport: Arc<dyn RadrootsSdkNip46Transport>, @@ -389,7 +437,7 @@ impl RadrootsSdkMycNip46Signer { } })?; Ok(Self { - client_keys, + client_keys: client_key.into_keys(), target, user_pubkey, transport, diff --git a/crates/sdk/tests/support/fixture_signer.rs b/crates/sdk/tests/support/fixture_signer.rs @@ -1,5 +1,5 @@ +use nostr::Keys as RadrootsNostrKeys; use radroots_nostr::draft_signing::radroots_nostr_sign_frozen_draft; -use radroots_nostr::types::RadrootsNostrKeys; use radroots_signing::{ Error, SignReceipt, SignRequest, Signer, SignerStatus, error::Kind, signer::BoxFuture, }; @@ -49,7 +49,7 @@ impl FixtureSigner { pub fn sign_frozen_draft( &self, draft: &radroots_event::EventDraft, - ) -> Result<radroots_event::SignedEvent, radroots_nostr::error::RadrootsNostrError> { + ) -> Result<radroots_event::SignedEvent, radroots_nostr::Error> { radroots_nostr_sign_frozen_draft(&self.keys, draft) } } diff --git a/crates/sdk/tests/unit/runtime_tests.rs b/crates/sdk/tests/unit/runtime_tests.rs @@ -1975,7 +1975,7 @@ async fn restore_archive_private_failures_cover_staging_and_verification_edges() .build() .await .expect("populated sdk"); - let populated_event_keys = radroots_nostr::types::RadrootsNostrKeys::generate(); + let populated_event_keys = nostr::Keys::generate(); let populated_event_draft = radroots_event::draft::EventDraft::new( "radroots.farm.profile.v1", radroots_event::envelope::kind::KIND_FARM, diff --git a/crates/sdk/tests/unit/signer_provider_tests.rs b/crates/sdk/tests/unit/signer_provider_tests.rs @@ -26,7 +26,6 @@ static LOCAL_SIGNER: LazyLock<Arc<LocalSigner>> = static LOCAL_SIGNER_PUBLIC_KEY: LazyLock<String> = LazyLock::new(|| LOCAL_SIGNER.public_key().to_hex()); static REMOTE_KEYS: LazyLock<RadrootsNostrKeys> = LazyLock::new(RadrootsNostrKeys::generate); -static CLIENT_KEYS: LazyLock<RadrootsNostrKeys> = LazyLock::new(RadrootsNostrKeys::generate); fn user_keys() -> RadrootsNostrKeys { USER_KEYS.clone() @@ -50,8 +49,16 @@ fn remote_keys() -> RadrootsNostrKeys { REMOTE_KEYS.clone() } -fn client_keys() -> RadrootsNostrKeys { - CLIENT_KEYS.clone() +fn client_keys() -> RadrootsSdkNip46ClientKey { + RadrootsSdkNip46ClientKey::generate() +} + +#[test] +fn nip46_client_key_debug_output_is_always_redacted() { + assert_eq!( + format!("{:?}", RadrootsSdkNip46ClientKey::generate()), + "RadrootsSdkNip46ClientKey(\"[redacted]\")" + ); } fn actor() -> Actor { diff --git a/crates/sdk/tests/unit/sync_runtime_tests.rs b/crates/sdk/tests/unit/sync_runtime_tests.rs @@ -17,6 +17,7 @@ use crate::adapters::radrootsd::{RadrootsdError, RadrootsdPublishAdapter, Radroo #[cfg(feature = "radrootsd-execution")] use crate::workflow_runtime::{SdkWorkflowEnqueueRequest, enqueue_signed_workflow}; use futures::future::BoxFuture; +use nostr::Keys as RadrootsNostrKeys; #[cfg(feature = "radrootsd-execution")] use radroots_event::contract::AuthorRole; #[cfg(feature = "radrootsd-execution")] @@ -27,7 +28,6 @@ use radroots_event::id::EventId; use radroots_event_store::RadrootsEventStoreStatusSummary; #[cfg(feature = "radrootsd-execution")] use radroots_nostr::draft_signing::radroots_nostr_sign_frozen_draft; -use radroots_nostr::types::RadrootsNostrKeys; #[cfg(feature = "radrootsd-execution")] use radroots_outbox::{ RadrootsOutboxClaimedEvent, RadrootsOutboxDeliveryPlanInput, RadrootsOutboxDeliveryPlanStatus, @@ -101,7 +101,7 @@ impl RadrootsdFixtureSigner { fn sign_frozen_draft( &self, draft: &EventDraft, - ) -> Result<radroots_event::SignedEvent, radroots_nostr::error::RadrootsNostrError> { + ) -> Result<radroots_event::SignedEvent, radroots_nostr::Error> { radroots_nostr_sign_frozen_draft(&self.keys, draft) } } diff --git a/crates/sdk/tests/unit/workflow_runtime_tests.rs b/crates/sdk/tests/unit/workflow_runtime_tests.rs @@ -1,11 +1,11 @@ use super::*; #[cfg(feature = "signer-adapters")] use crate::{RadrootsSdkLocalKeySigner, RadrootsSdkSignerProvider}; +use nostr::Keys as RadrootsNostrKeys; use radroots_event::contract::AuthorRole; use radroots_event::draft::{EventDraft, SignedEvent, SignedEventParts}; use radroots_event::envelope::kind::{KIND_FARM, KIND_GEOCHAT}; use radroots_nostr::draft_signing::radroots_nostr_sign_frozen_draft; -use radroots_nostr::types::RadrootsNostrKeys; use radroots_signing::{ Error as SigningError, SignReceipt, SignRequest, SignerStatus, actor::ActorSource, error::Kind as SigningErrorKind, signer::BoxFuture,