commit 4e3e52b300904045023980f91379b00e097f1c75
parent 3fa03e7b623a09eb4da7c5b2283d3aa32aa60919
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:
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,