commit 451b58b9eb465b5e0d3755f82d0459fb172de096
parent 4bf5145d67db265d6d391d382813c6da6848f566
Author: triesap <tyson@radroots.org>
Date: Thu, 30 Jul 2026 12:58:03 +0000
signing: migrate workspace consumers
- replace authority actor and signer imports with the final signing SPI
- route local remote and workflow signing through request-bound receipts
- migrate SDK examples fixtures feature wiring and operation identities
- enforce retired-surface absence across manifests sources and tests
Diffstat:
28 files changed, 721 insertions(+), 442 deletions(-)
diff --git a/.cargo/config.toml b/.cargo/config.toml
@@ -5,7 +5,6 @@ xtask = "run -p radroots_sdk_xtask --"
# registry identities; package-realistic checks run extracted crates without
# this repository configuration.
[patch.crates-io]
-radroots_authority = { path = "../lib/crates/authority" }
radroots_blossom = { path = "../lib/crates/blossom" }
radroots_core = { path = "../lib/crates/core" }
radroots_event_store = { path = "../lib/crates/event_store" }
@@ -22,6 +21,7 @@ radroots_protocol = { path = "../lib/crates/protocol" }
radroots_protocol_contract_v1 = { path = "../lib/crates/protocol_contract_v1" }
radroots_protected_store = { path = "../lib/crates/protected_store" }
radroots_secret_vault = { path = "../lib/crates/secret_vault" }
+radroots_signing = { path = "../lib/crates/signing" }
radroots_transport = { path = "../lib/crates/transport" }
radroots_transport_nostr = { path = "../lib/crates/transport_nostr" }
radroots_transport_reticulum = { path = "../lib/crates/transport_reticulum" }
diff --git a/Cargo.lock b/Cargo.lock
@@ -1957,15 +1957,6 @@ name = "radroots"
version = "0.1.0-alpha"
[[package]]
-name = "radroots_authority"
-version = "0.1.0-alpha"
-dependencies = [
- "radroots_event",
- "radroots_identity",
- "radroots_nostr",
-]
-
-[[package]]
name = "radroots_blossom"
version = "0.1.0-alpha"
dependencies = [
@@ -2138,6 +2129,7 @@ dependencies = [
"radroots_event",
"radroots_event_codec",
"radroots_identity",
+ "radroots_signing",
"serde",
"serde_json",
"thiserror 1.0.69",
@@ -2338,7 +2330,6 @@ dependencies = [
"futures",
"hex",
"nostr",
- "radroots_authority",
"radroots_core",
"radroots_event",
"radroots_event_codec",
@@ -2356,6 +2347,7 @@ dependencies = [
"radroots_replica_sync",
"radroots_runtime_paths",
"radroots_secret_vault",
+ "radroots_signing",
"radroots_sql_core",
"radroots_trade",
"radroots_transport",
@@ -2411,6 +2403,15 @@ name = "radroots_secret_vault"
version = "0.1.0-alpha"
[[package]]
+name = "radroots_signing"
+version = "0.1.0-alpha"
+dependencies = [
+ "radroots_event",
+ "radroots_identity",
+ "radroots_protocol",
+]
+
+[[package]]
name = "radroots_sql_core"
version = "0.1.0-alpha"
dependencies = [
diff --git a/Cargo.toml b/Cargo.toml
@@ -48,7 +48,6 @@ unimplemented = "deny"
dto_bindgen = { version = "0.1.0" }
dto_bindgen_backend_ts = { version = "0.1.0" }
dto_bindgen_core = { version = "0.1.0" }
-radroots_authority = { version = "=0.1.0-alpha", default-features = false }
radroots_blossom = { package = "radroots_blossom", version = "=0.1.0-alpha", default-features = false }
radroots_core = { package = "radroots_core", version = "=0.1.0-alpha", default-features = false }
radroots_event_store = { version = "=0.1.0-alpha", default-features = false }
@@ -68,6 +67,7 @@ radroots_protocol_contract_v1 = { version = "=0.1.0-alpha", default-features = f
radroots_protected_store = { version = "=0.1.0-alpha", default-features = false }
radroots_runtime_contract_v1 = { path = "crates/runtime_contract_v1", version = "=0.1.0-alpha", default-features = false }
radroots_secret_vault = { version = "=0.1.0-alpha", default-features = false }
+radroots_signing = { package = "radroots_signing", version = "=0.1.0-alpha", default-features = false }
radroots_transport = { package = "radroots_transport", version = "=0.1.0-alpha", default-features = false }
radroots_transport_nostr = { package = "radroots_transport_nostr", version = "=0.1.0-alpha", default-features = false }
radroots_transport_reticulum = { version = "=0.1.0-alpha", default-features = false }
diff --git a/crates/sdk/Cargo.toml b/crates/sdk/Cargo.toml
@@ -43,7 +43,15 @@ knowledge = [
identity-models = [
"radroots_identity/std",
]
-signing = ["dep:nostr", "dep:radroots_nostr", "nostr", "radroots_nostr/std"]
+signing = [
+ "dep:nostr",
+ "dep:radroots_nostr",
+ "dep:radroots_protocol",
+ "nostr",
+ "radroots_nostr/std",
+ "radroots_protocol/std",
+ "radroots_signing/std",
+]
transport-nostr-client = ["signing", "std", "serde_json", "radroots_nostr/client"]
radrootsd-execution = [
"std",
@@ -74,11 +82,11 @@ runtime = [
"dep:tokio",
"dep:base64",
"dep:hex",
- "dep:radroots_authority",
"dep:radroots_event_store",
"dep:radroots_geocoder",
"dep:radroots_outbox",
"dep:radroots_protected_store",
+ "dep:radroots_protocol",
"dep:radroots_transport",
"dep:radroots_transport_nostr",
"dep:radroots_transport_reticulum",
@@ -87,19 +95,20 @@ runtime = [
"dep:sha2",
"dep:sqlx",
"dep:uuid",
- "radroots_authority/std",
+ "radroots_signing/std",
"radroots_event_store/sqlite",
"radroots_event_store/runtime-tokio",
"radroots_outbox/sqlite",
"radroots_outbox/runtime-tokio",
"radroots_protected_store/std",
+ "radroots_protocol/std",
"radroots_secret_vault/std",
"radroots_transport/serde",
"radroots_transport_nostr/std",
"radroots_transport_nostr/storage",
"radroots_transport_nostr/runtime-tokio",
]
-local-signer = ["runtime", "radroots_authority/local_signer"]
+local-signer = ["runtime", "signing", "radroots_nostr/signing"]
transport-nostr-runtime = [
"runtime",
"dep:radroots_nostr",
@@ -131,7 +140,6 @@ test-fixture-geonames-asset = [
[dependencies]
base64 = { workspace = true, optional = true }
futures = { workspace = true, optional = true }
-radroots_authority = { workspace = true, optional = true, default-features = false }
radroots_event_store = { workspace = true, optional = true, default-features = false }
radroots_event = { workspace = true, default-features = false }
radroots_event_codec = { workspace = true, default-features = false }
@@ -144,6 +152,7 @@ radroots_transport_nostr = { workspace = true, optional = true, default-features
radroots_transport_reticulum = { workspace = true, optional = true, default-features = false }
radroots_runtime_paths = { workspace = true, optional = true, default-features = false }
radroots_secret_vault = { workspace = true, optional = true, default-features = false }
+radroots_signing = { workspace = true, default-features = false }
radroots_trade = { workspace = true, default-features = false }
radroots_identity = { workspace = true, default-features = false }
radroots_nostr = { workspace = true, optional = true, default-features = false }
@@ -201,10 +210,7 @@ nostr = { workspace = true }
radroots_core = { workspace = true, default-features = false, features = [
"std",
] }
-radroots_authority = { workspace = true, default-features = false, features = [
- "std",
- "local_signer",
-] }
+radroots_signing = { workspace = true, default-features = false, features = ["std"] }
radroots_replica_store = { workspace = true, default-features = false, features = [
"native",
] }
@@ -214,6 +220,7 @@ radroots_sql_core = { workspace = true, features = ["native"] }
radroots_nostr = { workspace = true, default-features = false, features = [
"std",
"events",
+ "signing",
] }
tempfile = { workspace = true }
tokio = { workspace = true, features = ["macros", "rt-multi-thread"] }
diff --git a/crates/sdk/examples/runtime_local.rs b/crates/sdk/examples/runtime_local.rs
@@ -1,4 +1,3 @@
-use radroots_authority::{RadrootsActorContext, RadrootsLocalEventSigner};
use radroots_core::{Currency, Decimal, Money, Quantity, QuantityPrice, Unit};
use radroots_event::contract::AuthorRole;
use radroots_event::farm::FarmRef;
@@ -9,11 +8,13 @@ use radroots_event::listing::operational::{
OperationalListingStatus,
};
use radroots_nostr::prelude::RadrootsNostrKeys;
+use radroots_nostr::signing::LocalSigner;
use radroots_sdk::{
ListingPreparePublishRequest, NostrRelayUrlPolicy, PushOutboxRequest, RadrootsClient,
RadrootsSdkError, RadrootsSdkLocalKeySigner, RadrootsSdkSignerProvider, RadrootsSdkTimestamp,
SdkIdempotencyKey, TargetPolicy,
};
+use radroots_signing::{Actor, actor::ActorSource};
const RELAY: &str = "wss://relay.example.com";
@@ -21,14 +22,17 @@ const RELAY: &str = "wss://relay.example.com";
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let keys = RadrootsNostrKeys::generate();
let seller = keys.public_key().to_hex();
- let signer =
- RadrootsSdkLocalKeySigner::from_event_signer(RadrootsLocalEventSigner::new(keys)?)?;
+ let signer = RadrootsSdkLocalKeySigner::from_signer(LocalSigner::new(keys), seller.as_str())?;
let sdk = RadrootsClient::builder()
.fixed_clock(RadrootsSdkTimestamp::from_unix_seconds(1_700_000_000))
.signer_provider(RadrootsSdkSignerProvider::LocalKey(signer))
.build()
.await?;
- let actor = RadrootsActorContext::test(seller.as_str(), [AuthorRole::Seller])?;
+ let actor = Actor::from_public_key_hex(
+ seller.as_str(),
+ ActorSource::ExplicitPublicKey,
+ [AuthorRole::Seller],
+ )?;
let listing = sample_listing(seller.as_str());
let prepare_request = ListingPreparePublishRequest::new(actor.clone(), listing);
let target_policy = TargetPolicy::try_nostr_relays([RELAY], NostrRelayUrlPolicy::Public)?;
diff --git a/crates/sdk/examples/sdk_v1_listing_prepare.rs b/crates/sdk/examples/sdk_v1_listing_prepare.rs
@@ -1,4 +1,3 @@
-use radroots_authority::RadrootsActorContext;
use radroots_core::{Currency, Decimal, Money, Quantity, QuantityPrice, Unit};
use radroots_event::contract::AuthorRole;
use radroots_event::farm::FarmRef;
@@ -9,6 +8,7 @@ use radroots_event::listing::operational::{
OperationalListingStatus,
};
use radroots_sdk::{ListingPreparePublishRequest, RadrootsClient, RadrootsSdkTimestamp};
+use radroots_signing::{Actor, actor::ActorSource};
const SELLER: &str = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
@@ -18,7 +18,8 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
.fixed_clock(RadrootsSdkTimestamp::from_unix_seconds(1_700_000_000))
.build()
.await?;
- let actor = RadrootsActorContext::test(SELLER, [AuthorRole::Seller])?;
+ let actor =
+ Actor::from_public_key_hex(SELLER, ActorSource::ExplicitPublicKey, [AuthorRole::Seller])?;
let request = ListingPreparePublishRequest::new(actor, sample_listing(SELLER));
let plan = sdk.listings().prepare_publish(request)?;
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
@@ -1,4 +1,3 @@
-use radroots_authority::{RadrootsActorContext, RadrootsLocalEventSigner};
use radroots_core::{Currency, Decimal, Money, Quantity, QuantityPrice, Unit};
use radroots_event::contract::AuthorRole;
use radroots_event::farm::FarmRef;
@@ -9,11 +8,13 @@ use radroots_event::listing::operational::{
OperationalListingStatus,
};
use radroots_nostr::prelude::RadrootsNostrKeys;
+use radroots_nostr::signing::LocalSigner;
use radroots_sdk::{
ListingPreparePublishRequest, NostrRelayUrlPolicy, PushOutboxRequest, RadrootsClient,
RadrootsSdkLocalKeySigner, RadrootsSdkSignerProvider, RadrootsSdkTimestamp, SdkIdempotencyKey,
TargetPolicy, TargetSet,
};
+use radroots_signing::{Actor, actor::ActorSource};
use radroots_transport_nostr::{RadrootsMockRelayPublishAdapter, RadrootsNostrTransport};
const LOCAL_RELAY: &str = "ws://localhost:7777";
@@ -22,14 +23,17 @@ const LOCAL_RELAY: &str = "ws://localhost:7777";
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let keys = RadrootsNostrKeys::generate();
let seller = keys.public_key().to_hex();
- let signer =
- RadrootsSdkLocalKeySigner::from_event_signer(RadrootsLocalEventSigner::new(keys)?)?;
+ let signer = RadrootsSdkLocalKeySigner::from_signer(LocalSigner::new(keys), seller.as_str())?;
let sdk = RadrootsClient::builder()
.fixed_clock(RadrootsSdkTimestamp::from_unix_seconds(1_700_000_000))
.signer_provider(RadrootsSdkSignerProvider::LocalKey(signer))
.build()
.await?;
- let actor = RadrootsActorContext::test(seller.as_str(), [AuthorRole::Seller])?;
+ let actor = Actor::from_public_key_hex(
+ seller.as_str(),
+ ActorSource::ExplicitPublicKey,
+ [AuthorRole::Seller],
+ )?;
let targets = TargetSet::nostr_relays([LOCAL_RELAY], NostrRelayUrlPolicy::Localhost)?;
let target_policy = TargetPolicy::explicit(targets);
diff --git a/crates/sdk/src/actor_json.rs b/crates/sdk/src/actor_json.rs
@@ -1,13 +1,10 @@
-use radroots_authority::{RadrootsActorContext, RadrootsActorSource};
use radroots_event::contract::AuthorRole;
+use radroots_signing::{Actor, actor::ActorSource};
use serde::{Serialize, ser::SerializeStruct};
-pub(crate) struct SdkActorContextJson<'a>(pub(crate) &'a RadrootsActorContext);
+pub(crate) struct SdkActorContextJson<'a>(pub(crate) &'a Actor);
-pub(crate) fn serialize_actor_context<S>(
- actor: &RadrootsActorContext,
- serializer: S,
-) -> Result<S::Ok, S::Error>
+pub(crate) fn serialize_actor_context<S>(actor: &Actor, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
@@ -25,8 +22,8 @@ impl serde::Serialize for SdkActorContextJson<'_> {
.iter()
.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 account_id = self.0.account_id().map(|account_id| account_id.to_hex());
+ let pubkey = self.0.public_key().to_hex();
let mut state = serializer.serialize_struct("SdkActorContext", 4)?;
state.serialize_field("pubkey", &pubkey)?;
state.serialize_field("roles", &roles)?;
@@ -50,13 +47,13 @@ fn actor_role_code(role: &AuthorRole) -> &'static str {
}
}
-fn actor_source_code(source: RadrootsActorSource) -> &'static str {
+fn actor_source_code(source: ActorSource) -> &'static str {
match source {
- RadrootsActorSource::LocalAccount => "local_account",
- RadrootsActorSource::ExplicitPubkey => "explicit_pubkey",
- RadrootsActorSource::RemoteSigner => "remote_signer",
- RadrootsActorSource::Service => "service",
- RadrootsActorSource::Test => "test",
+ ActorSource::LocalAccount(_) => "local_account",
+ ActorSource::ExplicitPublicKey => "explicit_public_key",
+ ActorSource::RemoteSigner(_) => "remote_signer",
+ ActorSource::Service(_) => "service",
+ _ => "unknown",
}
}
diff --git a/crates/sdk/src/error.rs b/crates/sdk/src/error.rs
@@ -1107,36 +1107,42 @@ impl fmt::Display for RadrootsSdkError {
impl std::error::Error for RadrootsSdkError {}
#[cfg(feature = "runtime")]
-impl From<radroots_authority::RadrootsAuthorityError> for RadrootsSdkError {
- fn from(error: radroots_authority::RadrootsAuthorityError) -> Self {
- match error {
- radroots_authority::RadrootsAuthorityError::ActorRoleUnsatisfied {
- contract_id,
- required_role,
- } => Self::UnauthorizedActor {
- operation: contract_id,
- reason: format!("missing role {required_role:?}"),
+impl From<radroots_signing::Error> for RadrootsSdkError {
+ fn from(error: radroots_signing::Error) -> Self {
+ use radroots_signing::error::Kind;
+
+ match error.kind() {
+ Kind::AuthorizationDenied => Self::UnauthorizedActor {
+ operation: "event signing".to_owned(),
+ reason: "actor or signer is not authorized for the frozen draft".to_owned(),
},
- radroots_authority::RadrootsAuthorityError::ActorPubkeyMismatch {
- expected_pubkey,
- actor_pubkey,
- } => Self::UnauthorizedActor {
- operation: "event authorization".to_owned(),
- reason: format!(
- "actor_pubkey_prefix={} expected_pubkey_prefix={}",
- redacted_prefix(actor_pubkey.as_str()),
- redacted_prefix(expected_pubkey.as_str())
- ),
+ Kind::SignerCapabilityMissing => Self::SignerUnavailable {
+ mode: "configured".to_owned(),
+ reason: error.to_string(),
},
- radroots_authority::RadrootsAuthorityError::SignerPubkeyMismatch {
- expected_pubkey,
- signer_pubkey,
- } => Self::SignerPubkeyMismatch {
+ Kind::SignerUnavailable => Self::SignerUnavailable {
+ mode: "configured".to_owned(),
+ reason: error.to_string(),
+ },
+ Kind::SignerRejected => Self::SignerRequestRejected {
+ mode: "configured".to_owned(),
+ reason: error.to_string(),
+ },
+ Kind::SignerTimeout | Kind::DeadlineExceeded => Self::SignerRequestTimedOut {
+ mode: "configured".to_owned(),
+ },
+ Kind::SignerCancelled => Self::SignerRequestRejected {
+ mode: "configured".to_owned(),
+ reason: error.to_string(),
+ },
+ Kind::SignerOutputInvalid => Self::SignerReturnedEventDrift {
operation: "event signing".to_owned(),
- expected_pubkey_prefix: redacted_prefix(expected_pubkey.as_str()),
- signer_pubkey_prefix: redacted_prefix(signer_pubkey.as_str()),
+ reason: error.to_string(),
+ },
+ Kind::InvalidArgument | Kind::InternalError => Self::Authority {
+ message: error.to_string(),
},
- error => Self::Authority {
+ _ => Self::Authority {
message: error.to_string(),
},
}
diff --git a/crates/sdk/src/farms_runtime.rs b/crates/sdk/src/farms_runtime.rs
@@ -12,8 +12,6 @@ use crate::{
workflow_runtime::{SdkWorkflowEnqueueRequest, enqueue_signed_workflow},
};
#[cfg(feature = "runtime")]
-use radroots_authority::{RadrootsActorContext, RadrootsEventSigner};
-#[cfg(feature = "runtime")]
use radroots_event::{
contract::AuthorRole,
draft::EventDraft,
@@ -23,6 +21,7 @@ use radroots_event::{
listing::operational::OperationalListingPublicLocation,
};
#[cfg(feature = "runtime")]
+use radroots_signing::{Actor, Signer};
#[cfg(feature = "runtime")]
pub const FARM_PUBLISH_OPERATION_KIND: &str = "farm.publish.v1";
@@ -40,14 +39,14 @@ const GEOHASH_BASE32: &[u8; 32] = b"0123456789bcdefghjkmnpqrstuvwxyz";
#[non_exhaustive]
pub struct FarmPreparePublishRequest {
#[serde(serialize_with = "crate::actor_json::serialize_actor_context")]
- pub actor: RadrootsActorContext,
+ pub actor: Actor,
pub farm: Farm,
pub created_at: Option<RadrootsSdkTimestamp>,
}
#[cfg(feature = "runtime")]
impl FarmPreparePublishRequest {
- pub fn new(actor: RadrootsActorContext, farm: Farm) -> Self {
+ pub fn new(actor: Actor, farm: Farm) -> Self {
Self {
actor,
farm,
@@ -66,7 +65,7 @@ impl FarmPreparePublishRequest {
#[non_exhaustive]
pub struct FarmEnqueuePublishRequest {
#[serde(serialize_with = "crate::actor_json::serialize_actor_context")]
- pub actor: RadrootsActorContext,
+ pub actor: Actor,
pub farm: Farm,
pub target_policy: TargetPolicy,
pub idempotency_key: Option<SdkIdempotencyKey>,
@@ -75,7 +74,7 @@ pub struct FarmEnqueuePublishRequest {
#[cfg(feature = "runtime")]
impl FarmEnqueuePublishRequest {
- pub fn new(actor: RadrootsActorContext, farm: Farm, target_policy: TargetPolicy) -> Self {
+ pub fn new(actor: Actor, farm: Farm, target_policy: TargetPolicy) -> Self {
Self {
actor,
farm,
@@ -213,7 +212,7 @@ impl SdkPublicLocality {
#[non_exhaustive]
pub struct FarmPrivateLocationUpsertRequest {
#[serde(serialize_with = "crate::actor_json::serialize_actor_context")]
- pub actor: RadrootsActorContext,
+ pub actor: Actor,
pub farm_d_tag: String,
pub exact_location: SdkExactLocation,
pub label: Option<String>,
@@ -223,7 +222,7 @@ pub struct FarmPrivateLocationUpsertRequest {
#[cfg(feature = "runtime")]
impl FarmPrivateLocationUpsertRequest {
pub fn new(
- actor: RadrootsActorContext,
+ actor: Actor,
farm_d_tag: impl Into<String>,
exact_location: SdkExactLocation,
) -> Self {
@@ -279,7 +278,7 @@ impl FarmPrivateLocationInput {
#[non_exhaustive]
pub struct FarmPrivateLocationSetRequest {
#[serde(serialize_with = "crate::actor_json::serialize_actor_context")]
- pub actor: RadrootsActorContext,
+ pub actor: Actor,
pub farm_d_tag: String,
pub input: FarmPrivateLocationInput,
pub label: Option<String>,
@@ -289,7 +288,7 @@ pub struct FarmPrivateLocationSetRequest {
#[cfg(feature = "runtime")]
impl FarmPrivateLocationSetRequest {
pub fn new(
- actor: RadrootsActorContext,
+ actor: Actor,
farm_d_tag: impl Into<String>,
input: FarmPrivateLocationInput,
) -> Self {
@@ -303,7 +302,7 @@ impl FarmPrivateLocationSetRequest {
}
pub fn exact(
- actor: RadrootsActorContext,
+ actor: Actor,
farm_d_tag: impl Into<String>,
exact_location: SdkExactLocation,
) -> Self {
@@ -314,27 +313,15 @@ impl FarmPrivateLocationSetRequest {
)
}
- pub fn city(
- actor: RadrootsActorContext,
- farm_d_tag: impl Into<String>,
- city: impl Into<String>,
- ) -> Self {
+ pub fn city(actor: Actor, farm_d_tag: impl Into<String>, city: impl Into<String>) -> Self {
Self::new(actor, farm_d_tag, FarmPrivateLocationInput::city(city))
}
- pub fn query(
- actor: RadrootsActorContext,
- farm_d_tag: impl Into<String>,
- query: impl Into<String>,
- ) -> Self {
+ pub fn query(actor: Actor, farm_d_tag: impl Into<String>, query: impl Into<String>) -> Self {
Self::new(actor, farm_d_tag, FarmPrivateLocationInput::query(query))
}
- pub fn geonames_id(
- actor: RadrootsActorContext,
- farm_d_tag: impl Into<String>,
- id: i64,
- ) -> Self {
+ pub fn geonames_id(actor: Actor, farm_d_tag: impl Into<String>, id: i64) -> Self {
Self::new(actor, farm_d_tag, FarmPrivateLocationInput::geonames_id(id))
}
@@ -354,13 +341,13 @@ impl FarmPrivateLocationSetRequest {
#[non_exhaustive]
pub struct FarmPrivateLocationClearRequest {
#[serde(serialize_with = "crate::actor_json::serialize_actor_context")]
- pub actor: RadrootsActorContext,
+ pub actor: Actor,
pub farm_d_tag: String,
}
#[cfg(feature = "runtime")]
impl FarmPrivateLocationClearRequest {
- pub fn new(actor: RadrootsActorContext, farm_d_tag: impl Into<String>) -> Self {
+ pub fn new(actor: Actor, farm_d_tag: impl Into<String>) -> Self {
Self {
actor,
farm_d_tag: farm_d_tag.into(),
@@ -455,7 +442,7 @@ impl<'sdk> FarmsClient<'sdk> {
pub async fn enqueue_publish_with_explicit_signer(
&self,
request: FarmEnqueuePublishRequest,
- signer: &dyn RadrootsEventSigner,
+ signer: &dyn Signer,
) -> Result<FarmEnqueueReceipt, RadrootsSdkError> {
let FarmEnqueuePublishRequest {
actor,
@@ -483,7 +470,7 @@ impl<'sdk> FarmsClient<'sdk> {
#[cfg(feature = "signer-adapters")]
pub async fn enqueue_prepared_publish(
&self,
- actor: &RadrootsActorContext,
+ actor: &Actor,
plan: FarmPublishPlan,
target_policy: TargetPolicy,
idempotency_key: Option<SdkIdempotencyKey>,
@@ -506,11 +493,11 @@ impl<'sdk> FarmsClient<'sdk> {
pub async fn enqueue_prepared_publish_with_explicit_signer(
&self,
- actor: &RadrootsActorContext,
+ actor: &Actor,
plan: FarmPublishPlan,
target_policy: TargetPolicy,
idempotency_key: Option<SdkIdempotencyKey>,
- signer: &dyn RadrootsEventSigner,
+ signer: &dyn Signer,
) -> Result<FarmEnqueueReceipt, RadrootsSdkError> {
let metadata = validate_farm_publish_plan(&plan)?;
let enqueue = enqueue_signed_workflow(
@@ -596,7 +583,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().to_hex(),
+ farm_pubkey: request.actor.public_key().to_hex(),
farm_d_tag: request.farm_d_tag,
label,
latitude: request.exact_location.latitude,
@@ -619,7 +606,7 @@ impl<'sdk> FarmsClient<'sdk> {
async fn set_private_location_from_locality(
&self,
- actor: RadrootsActorContext,
+ actor: Actor,
farm_d_tag: String,
locality_query: GeocoderLocalityQuery,
label: Option<String>,
@@ -641,7 +628,7 @@ impl<'sdk> FarmsClient<'sdk> {
};
let record = SdkPrivateFarmLocationRecord {
farm_addr: farm_addr.clone(),
- farm_pubkey: actor.pubkey().to_hex(),
+ farm_pubkey: actor.public_key().to_hex(),
farm_d_tag,
label,
latitude: exact_location.latitude,
@@ -666,7 +653,7 @@ impl<'sdk> FarmsClient<'sdk> {
GeocoderLocalityLookup::NoMatch => Ok(FarmPrivateLocationSetResult::NoMatch(
farm_private_location_lookup_receipt(
farm_addr,
- actor.pubkey().to_hex().as_str(),
+ actor.public_key().to_hex().as_str(),
farm_d_tag,
FarmPrivateLocationInput::Locality(locality_query),
Vec::new(),
@@ -675,7 +662,7 @@ impl<'sdk> FarmsClient<'sdk> {
GeocoderLocalityLookup::Ambiguous { candidates } => Ok(
FarmPrivateLocationSetResult::Ambiguous(farm_private_location_lookup_receipt(
farm_addr,
- actor.pubkey().to_hex().as_str(),
+ actor.public_key().to_hex().as_str(),
farm_d_tag,
FarmPrivateLocationInput::Locality(locality_query),
candidates
@@ -800,7 +787,7 @@ fn validate_farm_publish_plan(
#[cfg(feature = "runtime")]
fn farm_publish_plan(
- actor: &RadrootsActorContext,
+ actor: &Actor,
farm_value: Farm,
created_at: RadrootsSdkTimestamp,
) -> Result<FarmPublishPlan, RadrootsSdkError> {
@@ -818,7 +805,7 @@ fn farm_publish_plan(
created_at_nostr.into(),
parts.tags,
parts.content,
- actor.pubkey().to_hex(),
+ actor.public_key().to_hex(),
)
.expect("validated farm publish draft freezes");
let expected_event_id = EventId::parse(frozen_draft.expected_event_id_hex())
@@ -832,10 +819,7 @@ fn farm_publish_plan(
}
#[cfg(feature = "runtime")]
-fn require_farmer_actor(
- actor: &RadrootsActorContext,
- operation: &'static str,
-) -> Result<(), RadrootsSdkError> {
+fn require_farmer_actor(actor: &Actor, operation: &'static str) -> Result<(), RadrootsSdkError> {
if actor.satisfies(AuthorRole::Farmer) {
Ok(())
} else {
@@ -847,11 +831,8 @@ fn require_farmer_actor(
}
#[cfg(feature = "runtime")]
-fn farm_addr(
- actor: &RadrootsActorContext,
- d_tag: &str,
-) -> Result<AddressableCoordinate, RadrootsSdkError> {
- AddressableCoordinate::parse(format!("{KIND_FARM}:{}:{d_tag}", actor.pubkey())).map_err(
+fn farm_addr(actor: &Actor, d_tag: &str) -> Result<AddressableCoordinate, RadrootsSdkError> {
+ AddressableCoordinate::parse(format!("{KIND_FARM}:{}:{d_tag}", actor.public_key())).map_err(
|error| RadrootsSdkError::InvalidRequest {
message: format!("farm address is invalid: {error}"),
},
diff --git a/crates/sdk/src/lib.rs b/crates/sdk/src/lib.rs
@@ -18,7 +18,7 @@ mod error;
mod farm;
#[cfg(feature = "runtime")]
mod farms_runtime;
-#[cfg(all(test, feature = "runtime"))]
+#[cfg(all(test, feature = "runtime", feature = "signer-adapters"))]
#[path = "../tests/support/fixture_signer.rs"]
pub(crate) mod fixture_signer;
#[cfg(feature = "runtime")]
diff --git a/crates/sdk/src/listings_runtime.rs b/crates/sdk/src/listings_runtime.rs
@@ -7,8 +7,6 @@ use crate::{
workflow_runtime::{SdkWorkflowEnqueueRequest, enqueue_signed_workflow},
};
#[cfg(feature = "runtime")]
-use radroots_authority::{RadrootsActorContext, RadrootsEventSigner};
-#[cfg(feature = "runtime")]
use radroots_event::{
contract::AuthorRole,
draft::EventDraft,
@@ -19,6 +17,8 @@ use radroots_event::{
#[cfg(feature = "runtime")]
use radroots_outbox::RadrootsOutboxEnqueueStatus;
#[cfg(feature = "runtime")]
+use radroots_signing::{Actor, Signer};
+#[cfg(feature = "runtime")]
use radroots_trade::operational_listing::{
RadrootsOperationalListingCanonicalEdit, RadrootsOperationalListingEditDocumentV1,
RadrootsOperationalListingMutation, build_operational_listing_mutation_draft,
@@ -35,14 +35,14 @@ const OPERATIONAL_LISTING_PUBLISHED_CONTRACT_ID: &str = "radroots.operational_li
#[non_exhaustive]
pub struct ListingPreparePublishRequest {
#[serde(serialize_with = "crate::actor_json::serialize_actor_context")]
- pub actor: RadrootsActorContext,
+ pub actor: Actor,
pub document: RadrootsOperationalListingEditDocumentV1,
pub created_at: Option<RadrootsSdkTimestamp>,
}
#[cfg(feature = "runtime")]
impl ListingPreparePublishRequest {
- pub fn new(actor: RadrootsActorContext, listing: OperationalListing) -> Self {
+ pub fn new(actor: Actor, listing: OperationalListing) -> Self {
Self {
actor,
document: RadrootsOperationalListingEditDocumentV1::new(listing),
@@ -50,10 +50,7 @@ impl ListingPreparePublishRequest {
}
}
- pub fn from_document(
- actor: RadrootsActorContext,
- document: RadrootsOperationalListingEditDocumentV1,
- ) -> Self {
+ pub fn from_document(actor: Actor, document: RadrootsOperationalListingEditDocumentV1) -> Self {
Self {
actor,
document,
@@ -72,7 +69,7 @@ impl ListingPreparePublishRequest {
#[non_exhaustive]
pub struct ListingEnqueuePublishRequest {
#[serde(serialize_with = "crate::actor_json::serialize_actor_context")]
- pub actor: RadrootsActorContext,
+ pub actor: Actor,
pub document: RadrootsOperationalListingEditDocumentV1,
pub target_policy: TargetPolicy,
pub idempotency_key: Option<SdkIdempotencyKey>,
@@ -81,11 +78,7 @@ pub struct ListingEnqueuePublishRequest {
#[cfg(feature = "runtime")]
impl ListingEnqueuePublishRequest {
- pub fn new(
- actor: RadrootsActorContext,
- listing: OperationalListing,
- target_policy: TargetPolicy,
- ) -> Self {
+ pub fn new(actor: Actor, listing: OperationalListing, target_policy: TargetPolicy) -> Self {
Self::from_document(
actor,
RadrootsOperationalListingEditDocumentV1::new(listing),
@@ -94,7 +87,7 @@ impl ListingEnqueuePublishRequest {
}
pub fn from_document(
- actor: RadrootsActorContext,
+ actor: Actor,
document: RadrootsOperationalListingEditDocumentV1,
target_policy: TargetPolicy,
) -> Self {
@@ -234,7 +227,7 @@ impl<'sdk> ListingsClient<'sdk> {
pub async fn enqueue_publish_with_explicit_signer(
&self,
request: ListingEnqueuePublishRequest,
- signer: &dyn RadrootsEventSigner,
+ signer: &dyn Signer,
) -> Result<ListingEnqueueReceipt, RadrootsSdkError> {
let ListingEnqueuePublishRequest {
actor,
@@ -262,7 +255,7 @@ impl<'sdk> ListingsClient<'sdk> {
#[cfg(feature = "signer-adapters")]
pub async fn enqueue_prepared_publish(
&self,
- actor: &RadrootsActorContext,
+ actor: &Actor,
plan: ListingPublishPlan,
target_policy: TargetPolicy,
idempotency_key: Option<SdkIdempotencyKey>,
@@ -285,11 +278,11 @@ impl<'sdk> ListingsClient<'sdk> {
pub async fn enqueue_prepared_publish_with_explicit_signer(
&self,
- actor: &RadrootsActorContext,
+ actor: &Actor,
plan: ListingPublishPlan,
target_policy: TargetPolicy,
idempotency_key: Option<SdkIdempotencyKey>,
- signer: &dyn RadrootsEventSigner,
+ signer: &dyn Signer,
) -> Result<ListingEnqueueReceipt, RadrootsSdkError> {
let metadata = validate_listing_publish_plan(&plan)?;
let enqueue = enqueue_signed_workflow(
@@ -338,7 +331,7 @@ fn listing_enqueue_receipt(
#[cfg(feature = "runtime")]
fn canonical_listing_edit(
- actor: &RadrootsActorContext,
+ actor: &Actor,
document: RadrootsOperationalListingEditDocumentV1,
) -> Result<RadrootsOperationalListingCanonicalEdit, RadrootsSdkError> {
if !actor.satisfies(AuthorRole::Seller) {
@@ -347,7 +340,7 @@ fn canonical_listing_edit(
reason: "missing role Seller".to_owned(),
});
}
- canonicalize_operational_listing_edit(*actor.pubkey(), document).map_err(Into::into)
+ canonicalize_operational_listing_edit(actor.public_key(), document).map_err(Into::into)
}
#[cfg(feature = "runtime")]
@@ -412,7 +405,7 @@ fn validate_listing_publish_plan(
#[cfg(feature = "runtime")]
fn listing_publish_plan(
- actor: &RadrootsActorContext,
+ actor: &Actor,
document: RadrootsOperationalListingEditDocumentV1,
created_at: RadrootsSdkTimestamp,
) -> Result<ListingPublishPlan, RadrootsSdkError> {
diff --git a/crates/sdk/src/signer_provider.rs b/crates/sdk/src/signer_provider.rs
@@ -1,9 +1,5 @@
-use crate::RadrootsSdkError;
+use crate::{RadrootsSdkError, workflow_runtime::signing_operation_id};
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::{EventDraft, SignedEvent};
use radroots_event::envelope::kind::{
KIND_CLASSIFIED_LISTING, KIND_FARM, KIND_TRADE_CANCELLATION, KIND_TRADE_DECISION,
@@ -19,13 +15,17 @@ use radroots_nostr_connect::prelude::{
RadrootsNostrConnectPermissions, RadrootsNostrConnectRequest, RadrootsNostrConnectResponse,
execute_request_with_transport,
};
+use radroots_signing::{
+ Actor, SignReceipt, SignRequest, Signer,
+ request::{CancellationPolicy, SignPolicy},
+};
use std::sync::Arc;
use std::time::Duration;
use tokio::time::timeout;
use uuid::Uuid;
pub type RadrootsSdkNip46TransportFuture<'a, T> = RadrootsNostrConnectClientTransportFuture<'a, T>;
-pub type RadrootsSdkLocalSignerCapability = dyn RadrootsEventSigner + Send + Sync;
+pub type RadrootsSdkLocalSignerCapability = dyn Signer;
pub const RADROOTS_SDK_MYC_NIP46_PRODUCT_SIGN_EVENT_KINDS: [u32; 7] = [
KIND_FARM,
@@ -119,17 +119,13 @@ where
pub struct RadrootsSdkSignRequest<'a> {
pub operation_kind: &'a str,
- pub actor: &'a RadrootsActorContext,
+ pub actor: &'a Actor,
pub frozen_draft: &'a EventDraft,
progress_sink: Option<&'a mut dyn RadrootsSdkSignerProgressSink>,
}
impl<'a> RadrootsSdkSignRequest<'a> {
- pub fn new(
- operation_kind: &'a str,
- actor: &'a RadrootsActorContext,
- frozen_draft: &'a EventDraft,
- ) -> Self {
+ pub fn new(operation_kind: &'a str, actor: &'a Actor, frozen_draft: &'a EventDraft) -> Self {
Self {
operation_kind,
actor,
@@ -220,20 +216,28 @@ pub struct RadrootsSdkLocalKeySigner {
#[cfg(feature = "local-signer")]
impl RadrootsSdkLocalKeySigner {
- pub fn from_event_signer<S>(signer: S) -> Result<Self, RadrootsSdkError>
+ pub fn from_signer<S>(
+ signer: S,
+ signer_pubkey: impl AsRef<str>,
+ ) -> Result<Self, RadrootsSdkError>
where
- S: RadrootsEventSigner + Send + Sync + 'static,
+ S: Signer + 'static,
{
- Self::from_shared_event_signer(Arc::new(signer))
+ let signer_pubkey = PublicKey::from_hex(signer_pubkey.as_ref()).map_err(|_| {
+ RadrootsSdkError::InvalidRequest {
+ message: "local signer public key is invalid".to_owned(),
+ }
+ })?;
+ Self::from_shared_signer(Arc::new(signer), signer_pubkey)
}
- pub fn from_shared_event_signer(
+ pub fn from_shared_signer(
signer: Arc<RadrootsSdkLocalSignerCapability>,
+ signer_pubkey: PublicKey,
) -> Result<Self, RadrootsSdkError> {
- let signer_pubkey = signer.pubkey().to_hex();
Ok(Self {
signer,
- signer_pubkey,
+ signer_pubkey: signer_pubkey.to_hex(),
})
}
@@ -265,17 +269,22 @@ impl RadrootsSdkLocalKeySigner {
request.emit_progress(RadrootsSdkSignerProgress::RequestStarted {
mode: RadrootsSdkSignerMode::LocalKey,
})?;
- let signed_event =
- sign_authorized_draft(request.actor, self.signer.as_ref(), request.frozen_draft)?;
+ let operation_kind = request.operation_kind.to_owned();
+ let sign_request = final_sign_request(
+ &request,
+ CancellationPolicy::LocalCooperative,
+ Duration::from_millis(RADROOTS_SDK_MYC_NIP46_DEFAULT_REQUEST_TIMEOUT_MS),
+ )?;
+ let receipt = self.signer.sign(sign_request).await?;
request.emit_progress(RadrootsSdkSignerProgress::RequestCompleted {
mode: RadrootsSdkSignerMode::LocalKey,
})?;
- Ok(sign_receipt(
- request.operation_kind,
+ Ok(sdk_sign_receipt(
+ operation_kind.as_str(),
RadrootsSdkSignerMode::LocalKey,
self.signer_pubkey.clone(),
None,
- signed_event,
+ receipt,
))
}
}
@@ -416,12 +425,19 @@ impl RadrootsSdkMycNip46Signer {
request.emit_progress(RadrootsSdkSignerProgress::RequestStarted {
mode: RadrootsSdkSignerMode::MycNip46,
})?;
- authorize_actor_for_draft(request.actor, request.frozen_draft)?;
- let signer_identity = RadrootsSdkSignerIdentityOnly {
- pubkey: self.user_pubkey,
- };
- authorize_signer_for_draft(&signer_identity, request.frozen_draft)?;
- let sign_event_request = sign_event_request_from_frozen_draft(request.frozen_draft)?;
+ let operation_kind = request.operation_kind.to_owned();
+ let sign_request = final_sign_request(
+ &request,
+ CancellationPolicy::PreservePublishedRequest,
+ self.request_policy.request_timeout(),
+ )?;
+ if self.user_pubkey != sign_request.actor().public_key() {
+ return Err(radroots_signing::Error::new(
+ radroots_signing::error::Kind::AuthorizationDenied,
+ )
+ .into());
+ }
+ let sign_event_request = sign_event_request_from_frozen_draft(sign_request.draft())?;
let request_id = self.next_request_id();
let mut adapter = RadrootsSdkNip46TransportAdapter {
transport: self.transport.as_ref(),
@@ -458,22 +474,26 @@ impl RadrootsSdkMycNip46Signer {
return Err(error);
}
let response = response.map_err(sdk_error_from_nip46_error)?;
- let signed_event = signed_event_from_nip46_response(request.operation_kind, response)?;
- validate_signed_event_matches_draft(&signed_event, request.frozen_draft).map_err(
- |error| RadrootsSdkError::SignerReturnedEventDrift {
- operation: request.operation_kind.to_owned(),
- reason: error.to_string(),
- },
- )?;
+ let signed_event = signed_event_from_nip46_response(operation_kind.as_str(), response)?;
+ let receipt = SignReceipt::from_signed_event(&sign_request, signed_event, unix_time_now()?)
+ .map_err(|error| match error.kind() {
+ radroots_signing::error::Kind::SignerOutputInvalid => {
+ RadrootsSdkError::SignerReturnedEventDrift {
+ operation: operation_kind.clone(),
+ reason: error.to_string(),
+ }
+ }
+ _ => error.into(),
+ })?;
request.emit_progress(RadrootsSdkSignerProgress::RequestCompleted {
mode: RadrootsSdkSignerMode::MycNip46,
})?;
- Ok(sign_receipt(
- request.operation_kind,
+ Ok(sdk_sign_receipt(
+ operation_kind.as_str(),
RadrootsSdkSignerMode::MycNip46,
self.user_pubkey.to_hex(),
Some(self.target.remote_signer_public_key.to_hex()),
- signed_event,
+ receipt,
))
}
@@ -515,20 +535,6 @@ pub fn radroots_sdk_myc_nip46_product_permission_strings() -> Vec<String> {
.collect()
}
-struct RadrootsSdkSignerIdentityOnly {
- pubkey: PublicKey,
-}
-
-impl RadrootsEventSigner for RadrootsSdkSignerIdentityOnly {
- fn pubkey(&self) -> &PublicKey {
- &self.pubkey
- }
-
- fn sign_frozen_draft(&self, _draft: &EventDraft) -> Result<SignedEvent, RadrootsSignerError> {
- Err(RadrootsSignerError::Unavailable)
- }
-}
-
struct RadrootsSdkNip46TransportAdapter<'a> {
transport: &'a dyn RadrootsSdkNip46Transport,
}
@@ -676,13 +682,46 @@ fn sdk_error_from_nip46_error(error: RadrootsNostrConnectError) -> RadrootsSdkEr
}
}
-fn sign_receipt(
+fn final_sign_request(
+ request: &RadrootsSdkSignRequest<'_>,
+ cancellation: CancellationPolicy,
+ timeout: Duration,
+) -> Result<SignRequest, RadrootsSdkError> {
+ let operation_id = signing_operation_id(request.operation_kind).ok_or_else(|| {
+ RadrootsSdkError::InvalidRequest {
+ message: format!("unknown signing operation `{}`", request.operation_kind),
+ }
+ })?;
+ let now = unix_time_now()?;
+ let timeout_seconds = timeout.as_secs().max(1);
+ let deadline_unix = now
+ .checked_add(timeout_seconds)
+ .ok_or(RadrootsSdkError::TimestampOutOfRange { value: now })?;
+ let policy = SignPolicy::new(deadline_unix, cancellation)?;
+ SignRequest::new(
+ operation_id,
+ request.actor.clone(),
+ request.frozen_draft.clone(),
+ policy,
+ )
+ .map_err(Into::into)
+}
+
+fn unix_time_now() -> Result<u64, RadrootsSdkError> {
+ std::time::SystemTime::now()
+ .duration_since(std::time::UNIX_EPOCH)
+ .map(|duration| duration.as_secs())
+ .map_err(|_| RadrootsSdkError::ClockBeforeUnixEpoch)
+}
+
+fn sdk_sign_receipt(
operation_kind: &str,
mode: RadrootsSdkSignerMode,
signer_pubkey: String,
remote_signer_pubkey: Option<String>,
- signed_event: SignedEvent,
+ receipt: SignReceipt,
) -> RadrootsSdkSignReceipt {
+ let signed_event = receipt.signed_event().clone();
RadrootsSdkSignReceipt {
operation_kind: operation_kind.to_owned(),
mode,
diff --git a/crates/sdk/src/trade_runtime.rs b/crates/sdk/src/trade_runtime.rs
@@ -16,8 +16,6 @@ use crate::{
#[cfg(feature = "runtime")]
use base64::{Engine as _, engine::general_purpose::URL_SAFE_NO_PAD};
#[cfg(feature = "runtime")]
-use radroots_authority::{RadrootsActorContext, RadrootsEventSigner};
-#[cfg(feature = "runtime")]
use radroots_event::{
draft::EventDraft,
envelope::kind::TRADE_MUTATION_EVENT_KINDS,
@@ -33,6 +31,8 @@ use radroots_event_codec::encode::trade::trade_mutation_event_build;
#[cfg(feature = "runtime")]
use radroots_event_store::{RadrootsStoredTradeMutation, RadrootsTradeProjectionCheckpoint};
#[cfg(feature = "runtime")]
+use radroots_signing::{Actor, Signer};
+#[cfg(feature = "runtime")]
use radroots_trade::evidence::{
RadrootsTradeEvidenceStateV1, RadrootsTradeMutationRecordV1,
RadrootsTradePrivateTermsEvidenceV1,
@@ -112,7 +112,7 @@ impl<'client> TradeCommandService<'client> {
pub async fn submit_proposal_with_explicit_signer(
&self,
request: SubmitProposalRequest,
- signer: &dyn RadrootsEventSigner,
+ signer: &dyn Signer,
) -> Result<TradeCommandReceipt, RadrootsSdkError> {
let command = TradeCommandRequest::SubmitProposal(request);
enqueue_trade_command_with_explicit_signer(self.sdk, command, signer).await
@@ -130,7 +130,7 @@ impl<'client> TradeCommandService<'client> {
pub async fn propose_revision_with_explicit_signer(
&self,
request: ProposeRevisionRequest,
- signer: &dyn RadrootsEventSigner,
+ signer: &dyn Signer,
) -> Result<TradeCommandReceipt, RadrootsSdkError> {
let command = TradeCommandRequest::ProposeRevision(request);
enqueue_trade_command_with_explicit_signer(self.sdk, command, signer).await
@@ -148,7 +148,7 @@ impl<'client> TradeCommandService<'client> {
pub async fn decide_candidate_with_explicit_signer(
&self,
request: DecideCandidateRequest,
- signer: &dyn RadrootsEventSigner,
+ signer: &dyn Signer,
) -> Result<TradeCommandReceipt, RadrootsSdkError> {
let command = TradeCommandRequest::DecideCandidate(request);
enqueue_trade_command_with_explicit_signer(self.sdk, command, signer).await
@@ -166,7 +166,7 @@ impl<'client> TradeCommandService<'client> {
pub async fn cancel_trade_with_explicit_signer(
&self,
request: CancelTradeRequest,
- signer: &dyn RadrootsEventSigner,
+ signer: &dyn Signer,
) -> Result<TradeCommandReceipt, RadrootsSdkError> {
let command = TradeCommandRequest::CancelTrade(request);
enqueue_trade_command_with_explicit_signer(self.sdk, command, signer).await
@@ -184,7 +184,7 @@ impl<'client> TradeCommandService<'client> {
pub async fn resume_operation_with_explicit_signer(
&self,
request: ResumeOperationRequest,
- signer: &dyn RadrootsEventSigner,
+ signer: &dyn Signer,
) -> Result<TradeCommandReceipt, RadrootsSdkError> {
let command = TradeCommandRequest::ResumeOperation(request);
enqueue_trade_command_with_explicit_signer(self.sdk, command, signer).await
@@ -305,7 +305,7 @@ impl<'client> TradesClient<'client> {
#[non_exhaustive]
pub struct SubmitProposalRequest {
#[serde(serialize_with = "crate::actor_json::serialize_actor_context")]
- pub actor: RadrootsActorContext,
+ pub actor: Actor,
pub envelope: TradeMutationEnvelopeV1,
pub target_policy: TargetPolicy,
pub satisfaction_policy: SatisfactionPolicy,
@@ -315,7 +315,7 @@ pub struct SubmitProposalRequest {
#[cfg(feature = "runtime")]
impl SubmitProposalRequest {
pub fn new(
- actor: RadrootsActorContext,
+ actor: Actor,
envelope: TradeMutationEnvelopeV1,
target_policy: TargetPolicy,
) -> Self {
@@ -352,7 +352,7 @@ impl SubmitProposalRequest {
#[non_exhaustive]
pub struct ProposeRevisionRequest {
#[serde(serialize_with = "crate::actor_json::serialize_actor_context")]
- pub actor: RadrootsActorContext,
+ pub actor: Actor,
pub envelope: TradeMutationEnvelopeV1,
pub target_policy: TargetPolicy,
pub satisfaction_policy: SatisfactionPolicy,
@@ -362,7 +362,7 @@ pub struct ProposeRevisionRequest {
#[cfg(feature = "runtime")]
impl ProposeRevisionRequest {
pub fn new(
- actor: RadrootsActorContext,
+ actor: Actor,
envelope: TradeMutationEnvelopeV1,
target_policy: TargetPolicy,
) -> Self {
@@ -391,7 +391,7 @@ impl ProposeRevisionRequest {
#[non_exhaustive]
pub struct DecideCandidateRequest {
#[serde(serialize_with = "crate::actor_json::serialize_actor_context")]
- pub actor: RadrootsActorContext,
+ pub actor: Actor,
pub envelope: TradeMutationEnvelopeV1,
pub target_policy: TargetPolicy,
pub satisfaction_policy: SatisfactionPolicy,
@@ -402,7 +402,7 @@ pub struct DecideCandidateRequest {
#[cfg(feature = "runtime")]
impl DecideCandidateRequest {
pub fn new(
- actor: RadrootsActorContext,
+ actor: Actor,
envelope: TradeMutationEnvelopeV1,
target_policy: TargetPolicy,
) -> Self {
@@ -437,7 +437,7 @@ impl DecideCandidateRequest {
#[non_exhaustive]
pub struct CancelTradeRequest {
#[serde(serialize_with = "crate::actor_json::serialize_actor_context")]
- pub actor: RadrootsActorContext,
+ pub actor: Actor,
pub envelope: TradeMutationEnvelopeV1,
pub target_policy: TargetPolicy,
pub satisfaction_policy: SatisfactionPolicy,
@@ -447,7 +447,7 @@ pub struct CancelTradeRequest {
#[cfg(feature = "runtime")]
impl CancelTradeRequest {
pub fn new(
- actor: RadrootsActorContext,
+ actor: Actor,
envelope: TradeMutationEnvelopeV1,
target_policy: TargetPolicy,
) -> Self {
@@ -471,7 +471,7 @@ impl CancelTradeRequest {
#[non_exhaustive]
pub struct ResumeOperationRequest {
#[serde(serialize_with = "crate::actor_json::serialize_actor_context")]
- pub actor: RadrootsActorContext,
+ pub actor: Actor,
pub envelope: TradeMutationEnvelopeV1,
pub operation_kind: &'static str,
pub target_policy: TargetPolicy,
@@ -483,7 +483,7 @@ pub struct ResumeOperationRequest {
#[cfg(feature = "runtime")]
impl ResumeOperationRequest {
pub fn new(
- actor: RadrootsActorContext,
+ actor: Actor,
envelope: TradeMutationEnvelopeV1,
operation_kind: &'static str,
target_policy: TargetPolicy,
@@ -1040,7 +1040,7 @@ pub struct EvidenceView {
#[derive(Clone, Debug)]
struct TradeCommandPlan {
operation_kind: &'static str,
- actor: RadrootsActorContext,
+ actor: Actor,
frozen_draft: EventDraft,
trade_id: TradeId,
mutation_id: MutationId,
@@ -1073,7 +1073,7 @@ impl TradeCommandRequest {
fn into_parts(
self,
) -> (
- RadrootsActorContext,
+ Actor,
TradeMutationEnvelopeV1,
TargetPolicy,
SatisfactionPolicy,
@@ -1163,7 +1163,7 @@ async fn enqueue_configured_trade_command(
async fn enqueue_trade_command_with_explicit_signer(
sdk: &RadrootsClient,
request: TradeCommandRequest,
- signer: &dyn RadrootsEventSigner,
+ signer: &dyn Signer,
) -> Result<TradeCommandReceipt, RadrootsSdkError> {
let plan = trade_command_plan(sdk, request).await?;
let enqueue = enqueue_signed_workflow(sdk, workflow_request(&plan), signer).await?;
@@ -1209,7 +1209,7 @@ async fn trade_command_plan(
canonical.authored_at_unix_s,
wire.tags,
wire.content,
- actor.pubkey().to_hex(),
+ actor.public_key().to_hex(),
)
.map_err(|error| {
trade_command_error(
@@ -1304,10 +1304,10 @@ fn validate_operation_body(
#[cfg(feature = "runtime")]
fn validate_actor_matches_envelope(
operation_kind: &'static str,
- actor: &RadrootsActorContext,
+ actor: &Actor,
envelope: &TradeMutationEnvelopeV1,
) -> Result<(), RadrootsSdkError> {
- if actor.pubkey() == &envelope.author_pubkey {
+ if actor.public_key() == envelope.author_pubkey {
Ok(())
} else {
Err(RadrootsSdkError::UnauthorizedActor {
diff --git a/crates/sdk/src/workflow_runtime.rs b/crates/sdk/src/workflow_runtime.rs
@@ -5,7 +5,6 @@ use crate::{
TargetPolicy, TargetSet, TransportProfile,
runtime::{RuntimeRecoveryReceiptWrite, record_runtime_recovery_receipt, sdk_now_ms},
};
-use radroots_authority::{RadrootsActorContext, RadrootsEventSigner, sign_authorized_draft};
use radroots_event::{
draft::{EventDraft, SignedEvent},
envelope::{EventKind, EventKindClass},
@@ -19,6 +18,11 @@ use radroots_outbox::{
RadrootsOutboxDeliveryPlanInput, RadrootsOutboxEnqueueStatus, RadrootsOutboxReticulumBehavior,
RadrootsOutboxSignedOperationInput, RadrootsOutboxSignedTradeMutationInput,
};
+use radroots_protocol::runtime::v1::OperationId;
+use radroots_signing::{
+ Actor, SignRequest, Signer,
+ request::{CancellationPolicy, SignPolicy},
+};
use radroots_transport::{
RADROOTS_RETICULUM_ENDPOINT_URI, RadrootsTransportKind, RadrootsTransportTarget,
};
@@ -30,7 +34,7 @@ const SDK_RUNTIME_CONTRACT_VERSION: &str = "1";
pub(crate) struct SdkWorkflowEnqueueRequest<'a> {
pub(crate) operation_kind: &'static str,
- pub(crate) actor: &'a RadrootsActorContext,
+ pub(crate) actor: &'a Actor,
pub(crate) frozen_draft: &'a EventDraft,
pub(crate) target_policy: TargetPolicy,
pub(crate) satisfaction_policy: SatisfactionPolicy,
@@ -50,7 +54,7 @@ pub(crate) struct SdkWorkflowEnqueueReceipt {
pub(crate) async fn enqueue_signed_workflow(
sdk: &RadrootsClient,
request: SdkWorkflowEnqueueRequest<'_>,
- signer: &dyn RadrootsEventSigner,
+ signer: &dyn Signer,
) -> Result<SdkWorkflowEnqueueReceipt, RadrootsSdkError> {
ensure_durable_workflow_kind(&request)?;
let delivery_plan =
@@ -67,8 +71,9 @@ pub(crate) async fn enqueue_signed_workflow(
None,
)
.await?;
- let signed_event = match sign_authorized_draft(request.actor, signer, request.frozen_draft) {
- Ok(signed_event) => signed_event,
+ let sign_request = signing_request(&request)?;
+ let signed_event = match signer.sign(sign_request).await {
+ Ok(receipt) => receipt.signed_event().clone(),
Err(error) => {
let sdk_error: RadrootsSdkError = error.into();
record_runtime_operation_failure(sdk, &request, &prepared.idempotency_key, &sdk_error)
@@ -86,6 +91,55 @@ pub(crate) async fn enqueue_signed_workflow(
}
}
+fn signing_request(
+ request: &SdkWorkflowEnqueueRequest<'_>,
+) -> Result<SignRequest, RadrootsSdkError> {
+ let operation_id = signing_operation_id(request.operation_kind).ok_or_else(|| {
+ RadrootsSdkError::InvalidRequest {
+ message: format!("unknown signing operation `{}`", request.operation_kind),
+ }
+ })?;
+ let now = std::time::SystemTime::now()
+ .duration_since(std::time::UNIX_EPOCH)
+ .map_err(|_| RadrootsSdkError::ClockBeforeUnixEpoch)?
+ .as_secs();
+ let deadline_unix = now
+ .checked_add(30)
+ .ok_or(RadrootsSdkError::TimestampOutOfRange { value: now })?;
+ let policy = SignPolicy::new(deadline_unix, CancellationPolicy::PreservePublishedRequest)?;
+ SignRequest::new(
+ operation_id,
+ request.actor.clone(),
+ request.frozen_draft.clone(),
+ policy,
+ )
+ .map_err(Into::into)
+}
+
+pub(crate) fn signing_operation_id(operation: &str) -> Option<OperationId> {
+ match operation {
+ "farm.publish" | "farm.publish.v1" => Some(OperationId::FarmPublish),
+ "listing.publish" | "listing.publish.v1" => Some(OperationId::ListingPublish),
+ "trade.proposal.submit" | "trade.submit_proposal.v1" => {
+ Some(OperationId::TradeProposalSubmit)
+ }
+ "trade.revision.propose" | "trade.propose_revision.v1" => {
+ Some(OperationId::TradeRevisionPropose)
+ }
+ "trade.candidate.decide" | "trade.decide_candidate.v1" => {
+ Some(OperationId::TradeCandidateDecide)
+ }
+ "trade.cancellation.submit" | "trade.cancel.v1" => {
+ Some(OperationId::TradeCancellationSubmit)
+ }
+ "trade.operation.resume" | "trade.resume_operation.v1" => {
+ Some(OperationId::TradeOperationResume)
+ }
+ "sync.push" | "sync.push.v1" => Some(OperationId::SyncPush),
+ _ => OperationId::parse(operation.strip_suffix(".v1").unwrap_or(operation)).ok(),
+ }
+}
+
#[cfg(feature = "signer-adapters")]
pub(crate) async fn enqueue_configured_signed_workflow(
sdk: &RadrootsClient,
@@ -616,7 +670,7 @@ async fn prepare_runtime_operation_journal(
)
.bind(SDK_RUNTIME_CONTRACT_VERSION)
.bind(request.operation_kind)
- .bind(request.actor.pubkey().to_hex())
+ .bind(request.actor.public_key().to_hex())
.bind(idempotency_key.as_str())
.fetch_optional(&mut *tx)
.await
@@ -633,7 +687,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().to_hex().chars().take(12).collect(),
+ expected_pubkey_prefix: request.actor.public_key().to_hex().chars().take(12).collect(),
existing_digest_prefix: existing_digest_prefix.clone(),
new_digest_prefix: new_digest_prefix.clone(),
};
@@ -642,7 +696,7 @@ async fn prepare_runtime_operation_journal(
)
.bind("idempotency_conflict")
.bind(request.operation_kind)
- .bind(request.actor.pubkey().to_hex())
+ .bind(request.actor.public_key().to_hex())
.bind(idempotency_key.as_str())
.bind("retry_operation_with_same_idempotency_key")
.bind(
@@ -687,7 +741,7 @@ async fn prepare_runtime_operation_journal(
.bind(observed_at_ms)
.bind(SDK_RUNTIME_CONTRACT_VERSION)
.bind(request.operation_kind)
- .bind(request.actor.pubkey().to_hex())
+ .bind(request.actor.public_key().to_hex())
.bind(idempotency_key.as_str())
.execute(&mut *tx)
.await
@@ -702,7 +756,7 @@ async fn prepare_runtime_operation_journal(
)
.bind(SDK_RUNTIME_CONTRACT_VERSION)
.bind(request.operation_kind)
- .bind(request.actor.pubkey().to_hex())
+ .bind(request.actor.public_key().to_hex())
.bind(idempotency_key.as_str())
.bind(command_hash.as_str())
.bind(frozen_draft_json.as_str())
@@ -754,7 +808,7 @@ async fn mark_runtime_operation_state(
.bind(observed_at_ms)
.bind(SDK_RUNTIME_CONTRACT_VERSION)
.bind(request.operation_kind)
- .bind(request.actor.pubkey().to_hex())
+ .bind(request.actor.public_key().to_hex())
.bind(idempotency_key.as_str())
.execute(sdk._event_store.pool())
.await
@@ -790,7 +844,7 @@ async fn record_runtime_operation_failure(
_ => None,
};
if let Some((recovery_code, recovery_action)) = recovery {
- let actor_pubkey = request.actor.pubkey().to_hex();
+ let actor_pubkey = request.actor.public_key().to_hex();
record_runtime_recovery_receipt(
sdk._event_store.pool(),
RuntimeRecoveryReceiptWrite {
@@ -818,7 +872,7 @@ async fn ensure_runtime_operation_can_commit(
)
.bind(SDK_RUNTIME_CONTRACT_VERSION)
.bind(request.operation_kind)
- .bind(request.actor.pubkey().to_hex())
+ .bind(request.actor.public_key().to_hex())
.bind(idempotency_key.as_str())
.fetch_one(tx.as_mut())
.await
@@ -859,7 +913,7 @@ async fn commit_runtime_operation_journal(
.bind(observed_at_ms)
.bind(SDK_RUNTIME_CONTRACT_VERSION)
.bind(request.operation_kind)
- .bind(request.actor.pubkey().to_hex())
+ .bind(request.actor.public_key().to_hex())
.bind(idempotency_key.as_str())
.execute(tx.as_mut())
.await
@@ -1016,7 +1070,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().to_hex(),
+ "actor_pubkey": request.actor.public_key().to_hex(),
"draft": {
"contract_id": request.frozen_draft.contract_id(),
"contract_registry_version": request.frozen_draft.contract_registry_version(),
diff --git a/crates/sdk/tests/farms_runtime.rs b/crates/sdk/tests/farms_runtime.rs
@@ -1,6 +1,5 @@
#![cfg(feature = "runtime")]
-use radroots_authority::RadrootsActorContext;
use radroots_event::{
contract::AuthorRole,
envelope::kind::{KIND_FARM, KIND_PROFILE},
@@ -24,6 +23,7 @@ use radroots_sdk::{
SdkIdempotencyKey, SdkMutationState, SdkPublicLocality, StorageStatusRequest, TargetPolicy,
TargetSet, TransportProfile,
};
+use radroots_signing::{Actor, actor::ActorSource};
use radroots_transport_nostr::{RadrootsMockRelayPublishAdapter, RadrootsNostrTransport};
use sqlx::sqlite::{SqliteConnectOptions, SqlitePoolOptions};
@@ -52,12 +52,22 @@ fn other_pubkey() -> &'static str {
fixture_bob_pubkey()
}
-fn farmer_actor() -> RadrootsActorContext {
- RadrootsActorContext::test(farmer_pubkey(), [AuthorRole::Farmer]).expect("actor")
+fn farmer_actor() -> Actor {
+ Actor::from_public_key_hex(
+ farmer_pubkey(),
+ ActorSource::ExplicitPublicKey,
+ [AuthorRole::Farmer],
+ )
+ .expect("actor")
}
-fn non_farmer_actor() -> RadrootsActorContext {
- RadrootsActorContext::test(farmer_pubkey(), [AuthorRole::Buyer]).expect("actor")
+fn non_farmer_actor() -> Actor {
+ Actor::from_public_key_hex(
+ farmer_pubkey(),
+ ActorSource::ExplicitPublicKey,
+ [AuthorRole::Buyer],
+ )
+ .expect("actor")
}
fn farm(d_tag: &str, name: &str) -> Farm {
@@ -73,8 +83,8 @@ fn farm(d_tag: &str, name: &str) -> Farm {
}
}
-fn farm_addr(actor: &RadrootsActorContext, d_tag: &str) -> AddressableCoordinate {
- AddressableCoordinate::parse(format!("{KIND_FARM}:{}:{d_tag}", actor.pubkey()))
+fn farm_addr(actor: &Actor, d_tag: &str) -> AddressableCoordinate {
+ AddressableCoordinate::parse(format!("{KIND_FARM}:{}:{d_tag}", actor.public_key()))
.expect("farm addr")
}
@@ -658,10 +668,7 @@ async fn farm_enqueue_publish_returns_sanitized_signer_errors_before_mutation()
.expect_err("signer error");
let message = error.to_string();
- assert!(matches!(
- error,
- RadrootsSdkError::SignerPubkeyMismatch { .. }
- ));
+ assert!(matches!(error, RadrootsSdkError::UnauthorizedActor { .. }));
assert!(!message.contains("raw"));
assert!(!message.contains("ffff"));
@@ -986,7 +993,7 @@ async fn farm_runtime_dtos_serialize_deterministically() {
"pubkey": farmer_pubkey(),
"roles": ["farmer"],
"account_id": null,
- "source": "test"
+ "source": "explicit_public_key"
},
"farm": {
"d_tag": FARM_A_D_TAG,
@@ -1023,7 +1030,7 @@ async fn farm_runtime_dtos_serialize_deterministically() {
"pubkey": farmer_pubkey(),
"roles": ["farmer"],
"account_id": null,
- "source": "test"
+ "source": "explicit_public_key"
},
"farm": {
"d_tag": FARM_B_D_TAG,
@@ -1094,7 +1101,7 @@ async fn farm_runtime_dtos_serialize_deterministically() {
"pubkey": farmer_pubkey(),
"roles": ["farmer"],
"account_id": null,
- "source": "test"
+ "source": "explicit_public_key"
},
"farm_d_tag": FARM_C_D_TAG,
"exact_location": {
@@ -1121,7 +1128,7 @@ async fn farm_runtime_dtos_serialize_deterministically() {
"pubkey": farmer_pubkey(),
"roles": ["farmer"],
"account_id": null,
- "source": "test"
+ "source": "explicit_public_key"
},
"farm_d_tag": FARM_D_D_TAG,
"input": {
@@ -1180,7 +1187,7 @@ async fn farm_runtime_dtos_serialize_deterministically() {
"pubkey": farmer_pubkey(),
"roles": ["farmer"],
"account_id": null,
- "source": "test"
+ "source": "explicit_public_key"
},
"farm_d_tag": FARM_E_D_TAG
})
diff --git a/crates/sdk/tests/listings_runtime.rs b/crates/sdk/tests/listings_runtime.rs
@@ -1,6 +1,5 @@
#![cfg(feature = "runtime")]
-use radroots_authority::RadrootsActorContext;
use radroots_core::{Currency, Decimal, Money, Quantity, QuantityPrice, Unit};
use radroots_event::{
contract::AuthorRole,
@@ -25,6 +24,7 @@ use radroots_sdk::{
RadrootsSdkTimestamp, ReticulumProfile, SdkIdempotencyKey, SdkMutationState, TargetPolicy,
TargetSet, TransportProfile,
};
+use radroots_signing::{Actor, actor::ActorSource};
use radroots_trade::operational_listing::RadrootsOperationalListingEditDocumentV1;
use radroots_transport_nostr::{RadrootsMockRelayPublishAdapter, RadrootsNostrTransport};
use sqlx::Row;
@@ -60,12 +60,22 @@ fn other_pubkey() -> &'static str {
fixture_bob_pubkey()
}
-fn actor() -> RadrootsActorContext {
- RadrootsActorContext::test(seller_pubkey(), [AuthorRole::Seller]).expect("actor")
+fn actor() -> Actor {
+ Actor::from_public_key_hex(
+ seller_pubkey(),
+ ActorSource::ExplicitPublicKey,
+ [AuthorRole::Seller],
+ )
+ .expect("actor")
}
-fn non_seller_actor() -> RadrootsActorContext {
- RadrootsActorContext::test(seller_pubkey(), [AuthorRole::Buyer]).expect("actor")
+fn non_seller_actor() -> Actor {
+ Actor::from_public_key_hex(
+ seller_pubkey(),
+ ActorSource::ExplicitPublicKey,
+ [AuthorRole::Buyer],
+ )
+ .expect("actor")
}
fn listing(d_tag: &str, title: &str) -> OperationalListing {
@@ -403,7 +413,7 @@ async fn listing_runtime_dtos_serialize_deterministically() {
prepare_json["actor"]["roles"],
serde_json::json!(["seller"])
);
- assert_eq!(prepare_json["actor"]["source"], "test");
+ assert_eq!(prepare_json["actor"]["source"], "explicit_public_key");
assert_eq!(prepare_json["created_at"], 1_700_000_123);
assert_eq!(
prepare_json["document"]["listing"]["product"]["title"],
@@ -586,10 +596,7 @@ async fn enqueue_prepared_publish_returns_sanitized_signer_errors() {
.expect_err("signer error");
let message = error.to_string();
- assert!(matches!(
- error,
- RadrootsSdkError::SignerPubkeyMismatch { .. }
- ));
+ assert!(matches!(error, RadrootsSdkError::UnauthorizedActor { .. }));
assert!(!message.contains("raw"));
assert!(!message.contains("ffff"));
}
@@ -662,10 +669,7 @@ async fn enqueue_publish_returns_sanitized_signer_errors() {
.expect_err("signer error");
let message = error.to_string();
- assert!(matches!(
- error,
- RadrootsSdkError::SignerPubkeyMismatch { .. }
- ));
+ assert!(matches!(error, RadrootsSdkError::UnauthorizedActor { .. }));
assert!(!message.contains("raw"));
assert!(!message.contains("ffff"));
}
diff --git a/crates/sdk/tests/source_boundary.rs b/crates/sdk/tests/source_boundary.rs
@@ -148,3 +148,59 @@ fn sdk_does_not_expose_generic_wire_part_signing() {
assert!(!lib.contains("feature = \"signing\",\n"));
assert!(!adapters.contains("pub mod signing"));
}
+
+#[test]
+fn sdk_consumes_only_the_final_signing_boundary() {
+ let manifest = manifest_dir();
+ let cargo_manifest = read_source(&manifest.join("Cargo.toml"));
+ let workspace = manifest
+ .parent()
+ .and_then(Path::parent)
+ .expect("SDK crate belongs to the workspace root");
+ let workspace_manifest = read_source(&workspace.join("Cargo.toml"));
+ let cargo_config = read_source(&workspace.join(".cargo/config.toml"));
+ assert!(cargo_manifest.contains("radroots_signing = { workspace = true"));
+ assert!(workspace_manifest.contains("radroots_signing = { package = \"radroots_signing\""));
+ assert!(cargo_config.contains("radroots_signing = { path = \"../lib/crates/signing\" }"));
+ for source in [&cargo_manifest, &workspace_manifest, &cargo_config] {
+ assert!(!source.contains("radroots_authority"));
+ }
+
+ let retired_signing_surface = [
+ "radroots_authority",
+ "RadrootsActorContext",
+ "RadrootsEventSigner",
+ "RadrootsLocalEventSigner",
+ ];
+
+ for root in ["src", "tests", "examples"] {
+ let directory = manifest.join(root);
+ let mut files = Vec::new();
+ if directory.exists() {
+ fn collect(directory: &Path, files: &mut Vec<PathBuf>) {
+ for entry in fs::read_dir(directory).expect("read SDK source tree") {
+ let path = entry.expect("SDK source entry").path();
+ if path.is_dir() {
+ collect(&path, files);
+ } else if path.extension().is_some_and(|extension| extension == "rs") {
+ files.push(path);
+ }
+ }
+ }
+ collect(&directory, &mut files);
+ }
+ for path in files {
+ if path.ends_with("source_boundary.rs") {
+ continue;
+ }
+ let source = read_source(&path);
+ for retired in retired_signing_surface {
+ assert!(
+ !source.contains(retired),
+ "{} must use radroots_signing instead of retired `{retired}`",
+ path.display()
+ );
+ }
+ }
+ }
+}
diff --git a/crates/sdk/tests/support/fixture_signer.rs b/crates/sdk/tests/support/fixture_signer.rs
@@ -1,7 +1,7 @@
-use radroots_authority::{RadrootsEventSigner, RadrootsSignerError, RadrootsSignerIdentity};
-use radroots_event::draft::{EventDraft, SignedEvent};
-use radroots_identity::PublicKey;
use radroots_nostr::prelude::{RadrootsNostrKeys, radroots_nostr_sign_frozen_draft};
+use radroots_signing::{
+ Error, SignReceipt, SignRequest, Signer, SignerStatus, error::Kind, signer::BoxFuture,
+};
use std::sync::LazyLock;
struct FixtureKeyMaterial {
@@ -28,9 +28,7 @@ pub(crate) fn fixture_bob_pubkey() -> &'static str {
FIXTURE_BOB.pubkey.as_str()
}
-#[derive(Clone)]
pub struct FixtureSigner {
- identity: RadrootsSignerIdentity,
keys: RadrootsNostrKeys,
}
@@ -42,27 +40,29 @@ impl FixtureSigner {
_ => panic!("unsupported fixture signer public key"),
};
Self {
- identity: RadrootsSignerIdentity::new(pubkey).expect("identity"),
keys: material.keys.clone(),
}
}
+
+ #[allow(dead_code)]
+ pub fn sign_frozen_draft(
+ &self,
+ draft: &radroots_event::EventDraft,
+ ) -> Result<radroots_event::SignedEvent, radroots_nostr::error::RadrootsNostrError> {
+ radroots_nostr_sign_frozen_draft(&self.keys, draft)
+ }
}
-impl RadrootsEventSigner for FixtureSigner {
- fn pubkey(&self) -> &PublicKey {
- self.identity.pubkey()
+impl Signer for FixtureSigner {
+ fn status(&self) -> BoxFuture<'_, Result<SignerStatus, Error>> {
+ Box::pin(async { Ok(SignerStatus::unavailable()) })
}
- fn sign_frozen_draft(&self, draft: &EventDraft) -> Result<SignedEvent, RadrootsSignerError> {
- if self.pubkey() != draft.expected_pubkey() {
- return Err(RadrootsSignerError::SigningFailed {
- message: "wrong fixture signer".to_owned(),
- });
- }
- radroots_nostr_sign_frozen_draft(&self.keys, draft).map_err(|error| {
- RadrootsSignerError::SigningFailed {
- message: error.to_string(),
- }
+ fn sign(&self, request: SignRequest) -> BoxFuture<'_, Result<SignReceipt, Error>> {
+ Box::pin(async move {
+ let signed_event = radroots_nostr_sign_frozen_draft(&self.keys, request.draft())
+ .map_err(|source| Error::with_source(Kind::AuthorizationDenied, source))?;
+ SignReceipt::from_signed_event(&request, signed_event, 1_700_000_001)
})
}
}
diff --git a/crates/sdk/tests/sync_runtime.rs b/crates/sdk/tests/sync_runtime.rs
@@ -1,7 +1,6 @@
#![cfg(feature = "runtime")]
use futures::future::BoxFuture;
-use radroots_authority::{RadrootsActorContext, RadrootsEventSigner};
use radroots_core::{Currency, Decimal, Money, Quantity, QuantityPrice, Unit};
use radroots_event::{
contract::AuthorRole,
@@ -31,6 +30,7 @@ use radroots_sdk::{
ReticulumTryNowRequest, SdkBackupManifestKind, SdkRelayAuthPolicy, SdkRestoreState,
StorageStatusRequest, SyncStatusRequest, SyncStatusSource, TargetPolicy, TransportProfile,
};
+use radroots_signing::{Actor, actor::ActorSource};
use radroots_transport::{
RADROOTS_RETICULUM_UNAVAILABLE_MESSAGE, RadrootsTransportMeshScopeId,
RadrootsTransportSatisfactionPolicy, RadrootsTransportTarget, RadrootsTransportTargetLabel,
@@ -383,8 +383,13 @@ impl RadrootsRelayPublishAdapter for RecordingPublishAdapter {
}
}
-fn actor() -> RadrootsActorContext {
- RadrootsActorContext::test(seller_pubkey(), [AuthorRole::Seller]).expect("actor")
+fn actor() -> Actor {
+ Actor::from_public_key_hex(
+ seller_pubkey(),
+ ActorSource::ExplicitPublicKey,
+ [AuthorRole::Seller],
+ )
+ .expect("actor")
}
fn listing(d_tag: &str, title: &str) -> OperationalListing {
diff --git a/crates/sdk/tests/unit/actor_json_tests.rs b/crates/sdk/tests/unit/actor_json_tests.rs
@@ -1,6 +1,7 @@
use super::{SdkActorContextJson, actor_role_code, actor_source_code};
-use radroots_authority::{RadrootsActorContext, RadrootsActorSource};
use radroots_event::contract::AuthorRole;
+use radroots_identity::AccountId;
+use radroots_signing::{Actor, actor::ActorSource};
use crate::serializer_failure::assert_struct_serialize_error_paths;
@@ -19,26 +20,28 @@ fn actor_role_and_source_codes_cover_public_actor_taxonomy() {
assert_eq!(actor_role_code(&AuthorRole::Service), "service");
assert_eq!(
- actor_source_code(RadrootsActorSource::LocalAccount),
+ actor_source_code(ActorSource::LocalAccount(account_id())),
"local_account"
);
assert_eq!(
- actor_source_code(RadrootsActorSource::ExplicitPubkey),
- "explicit_pubkey"
+ actor_source_code(ActorSource::ExplicitPublicKey),
+ "explicit_public_key"
);
assert_eq!(
- actor_source_code(RadrootsActorSource::RemoteSigner),
+ actor_source_code(ActorSource::RemoteSigner(account_id())),
"remote_signer"
);
- assert_eq!(actor_source_code(RadrootsActorSource::Service), "service");
- assert_eq!(actor_source_code(RadrootsActorSource::Test), "test");
+ assert_eq!(
+ actor_source_code(ActorSource::Service(account_id())),
+ "service"
+ );
}
#[test]
fn actor_context_json_preserves_source_roles_and_account_id() {
- let actor = RadrootsActorContext::local_account(
+ let actor = Actor::from_public_key_hex(
PUBKEY,
- "acct-1",
+ ActorSource::LocalAccount(account_id()),
[AuthorRole::Buyer, AuthorRole::Seller],
)
.expect("actor");
@@ -50,7 +53,7 @@ fn actor_context_json_preserves_source_roles_and_account_id() {
serde_json::json!({
"pubkey": PUBKEY,
"roles": ["buyer", "seller"],
- "account_id": "acct-1",
+ "account_id": PUBKEY,
"source": "local_account"
})
);
@@ -58,12 +61,16 @@ fn actor_context_json_preserves_source_roles_and_account_id() {
#[test]
fn actor_context_json_reports_serializer_failures() {
- let actor = RadrootsActorContext::local_account(
+ let actor = Actor::from_public_key_hex(
PUBKEY,
- "acct-1",
+ ActorSource::LocalAccount(account_id()),
[AuthorRole::Buyer, AuthorRole::Seller],
)
.expect("actor");
assert_struct_serialize_error_paths(&SdkActorContextJson(&actor), 4);
}
+
+fn account_id() -> AccountId {
+ AccountId::from_hex(PUBKEY).expect("account ID")
+}
diff --git a/crates/sdk/tests/unit/error_tests.rs b/crates/sdk/tests/unit/error_tests.rs
@@ -4,28 +4,24 @@ use super::{
};
use crate::privacy::{PrivacyPreflightStatus, ProductSensitivityField};
use crate::transport::ReticulumBehavior;
-use radroots_authority::RadrootsAuthorityError;
use radroots_geocoder::{GeoNamesAssetFetcher, GeoNamesBlockingHttpFetcher, GeocoderError};
+use radroots_signing::{Error as SigningError, error::Kind as SigningErrorKind};
use std::collections::BTreeSet;
#[test]
-fn authority_error_conversion_redacts_pubkey_mismatches_and_falls_back() {
- let actor_error = RadrootsSdkError::from(RadrootsAuthorityError::ActorPubkeyMismatch {
- expected_pubkey: "a".repeat(64),
- actor_pubkey: "b".repeat(64),
- });
+fn signing_error_conversion_preserves_normalized_failures() {
+ let actor_error =
+ RadrootsSdkError::from(SigningError::new(SigningErrorKind::AuthorizationDenied));
assert!(matches!(
actor_error,
RadrootsSdkError::UnauthorizedActor { ref reason, .. }
- if reason == "actor_pubkey_prefix=bbbbbbbbbbbb expected_pubkey_prefix=aaaaaaaaaaaa"
+ if reason == "actor or signer is not authorized for the frozen draft"
));
- let fallback = RadrootsSdkError::from(RadrootsAuthorityError::UnknownContract {
- contract_id: "contract-x".to_owned(),
- });
+ let fallback = RadrootsSdkError::from(SigningError::new(SigningErrorKind::InvalidArgument));
assert!(matches!(
fallback,
- RadrootsSdkError::Authority { ref message } if message.contains("contract-x")
+ RadrootsSdkError::Authority { ref message } if message == "signing request is invalid"
));
}
diff --git a/crates/sdk/tests/unit/farms_runtime_tests.rs b/crates/sdk/tests/unit/farms_runtime_tests.rs
@@ -4,6 +4,7 @@ use sqlx::sqlite::{SqliteConnectOptions, SqlitePoolOptions};
use crate::fixture_signer::{FixtureSigner, fixture_alice_pubkey, fixture_bob_pubkey};
use crate::serializer_failure::assert_struct_serialize_error_paths;
+use radroots_signing::actor::ActorSource;
const FARM_A_D_TAG: &str = "AAAAAAAAAAAAAAAAAAAAAA";
const FARM_B_D_TAG: &str = "AAAAAAAAAAAAAAAAAAAAAQ";
@@ -15,8 +16,13 @@ fn farmer_pubkey() -> &'static str {
fixture_alice_pubkey()
}
-fn farmer_actor() -> RadrootsActorContext {
- RadrootsActorContext::test(farmer_pubkey(), [AuthorRole::Farmer]).expect("actor")
+fn farmer_actor() -> Actor {
+ Actor::from_public_key_hex(
+ farmer_pubkey(),
+ ActorSource::ExplicitPublicKey,
+ [AuthorRole::Farmer],
+ )
+ .expect("actor")
}
fn farm(d_tag: &str, name: &str) -> Farm {
@@ -117,8 +123,9 @@ async fn fixture_geocoder(tempdir: &tempfile::TempDir, feature_name: Option<&str
#[test]
fn farm_publish_plan_rejects_invalid_draft_tags() {
- let actor = RadrootsActorContext::test(
+ let actor = Actor::from_public_key_hex(
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+ ActorSource::ExplicitPublicKey,
[AuthorRole::Farmer],
)
.expect("actor");
@@ -748,13 +755,21 @@ async fn farm_configured_local_signer_enqueues_publish_without_explicit_signer()
let sdk = crate::RadrootsClient::builder()
.fixed_clock(RadrootsSdkTimestamp::from_unix_seconds(1_700_000_500))
.signer_provider(RadrootsSdkSignerProvider::LocalKey(
- RadrootsSdkLocalKeySigner::from_event_signer(FixtureSigner::new(farmer_pubkey()))
- .expect("signer"),
+ RadrootsSdkLocalKeySigner::from_signer(
+ FixtureSigner::new(farmer_pubkey()),
+ farmer_pubkey(),
+ )
+ .expect("signer"),
))
.build()
.await
.expect("sdk");
- let actor = RadrootsActorContext::test(farmer_pubkey(), [AuthorRole::Farmer]).expect("actor");
+ let actor = Actor::from_public_key_hex(
+ farmer_pubkey(),
+ ActorSource::ExplicitPublicKey,
+ [AuthorRole::Farmer],
+ )
+ .expect("actor");
let receipt = sdk
.farms()
@@ -780,13 +795,21 @@ async fn farm_configured_enqueue_reports_prepare_and_signer_errors() {
let configured_sdk = crate::RadrootsClient::builder()
.fixed_clock(RadrootsSdkTimestamp::from_unix_seconds(1_700_000_500))
.signer_provider(RadrootsSdkSignerProvider::LocalKey(
- RadrootsSdkLocalKeySigner::from_event_signer(FixtureSigner::new(farmer_pubkey()))
- .expect("signer"),
+ RadrootsSdkLocalKeySigner::from_signer(
+ FixtureSigner::new(farmer_pubkey()),
+ farmer_pubkey(),
+ )
+ .expect("signer"),
))
.build()
.await
.expect("configured sdk");
- let actor = RadrootsActorContext::test(farmer_pubkey(), [AuthorRole::Farmer]).expect("actor");
+ let actor = Actor::from_public_key_hex(
+ farmer_pubkey(),
+ ActorSource::ExplicitPublicKey,
+ [AuthorRole::Farmer],
+ )
+ .expect("actor");
assert!(matches!(
configured_sdk
@@ -918,8 +941,12 @@ async fn farm_private_location_default_client_and_lookup_report_store_edges() {
farm_addr(&actor, FARM_B_D_TAG).expect("farm b addr")
);
- let non_farmer_actor =
- RadrootsActorContext::test(farmer_pubkey(), [AuthorRole::Buyer]).expect("buyer actor");
+ let non_farmer_actor = Actor::from_public_key_hex(
+ farmer_pubkey(),
+ ActorSource::ExplicitPublicKey,
+ [AuthorRole::Buyer],
+ )
+ .expect("buyer actor");
assert!(matches!(
sdk.farms()
.clear_private_location(FarmPrivateLocationClearRequest::new(
diff --git a/crates/sdk/tests/unit/listings_runtime_tests.rs b/crates/sdk/tests/unit/listings_runtime_tests.rs
@@ -14,6 +14,7 @@ use radroots_event::{
use crate::fixture_signer::{FixtureSigner, fixture_alice_pubkey, fixture_bob_pubkey};
use crate::serializer_failure::assert_struct_serialize_error_paths;
+use radroots_signing::actor::ActorSource;
const FARM_D_TAG: &str = "AAAAAAAAAAAAAAAAAAAAAA";
const LISTING_A_D_TAG: &str = "AAAAAAAAAAAAAAAAAAAAAQ";
@@ -26,8 +27,13 @@ fn seller_pubkey() -> &'static str {
fixture_alice_pubkey()
}
-fn actor() -> RadrootsActorContext {
- RadrootsActorContext::test(seller_pubkey(), [AuthorRole::Seller]).expect("actor")
+fn actor() -> Actor {
+ Actor::from_public_key_hex(
+ seller_pubkey(),
+ ActorSource::ExplicitPublicKey,
+ [AuthorRole::Seller],
+ )
+ .expect("actor")
}
fn listing(d_tag: &str, title: &str) -> OperationalListing {
@@ -511,13 +517,21 @@ async fn listing_configured_local_signer_enqueues_publish_without_explicit_signe
let sdk = crate::RadrootsClient::builder()
.fixed_clock(RadrootsSdkTimestamp::from_unix_seconds(1_700_000_500))
.signer_provider(RadrootsSdkSignerProvider::LocalKey(
- RadrootsSdkLocalKeySigner::from_event_signer(FixtureSigner::new(seller_pubkey()))
- .expect("signer"),
+ RadrootsSdkLocalKeySigner::from_signer(
+ FixtureSigner::new(seller_pubkey()),
+ seller_pubkey(),
+ )
+ .expect("signer"),
))
.build()
.await
.expect("sdk");
- let actor = RadrootsActorContext::test(seller_pubkey(), [AuthorRole::Seller]).expect("actor");
+ let actor = Actor::from_public_key_hex(
+ seller_pubkey(),
+ ActorSource::ExplicitPublicKey,
+ [AuthorRole::Seller],
+ )
+ .expect("actor");
let receipt = sdk
.listings()
diff --git a/crates/sdk/tests/unit/signer_provider_tests.rs b/crates/sdk/tests/unit/signer_provider_tests.rs
@@ -1,17 +1,17 @@
use super::*;
use nostr::nips::nip44::{self, Version};
use nostr::{EventBuilder, JsonUtil, Kind, Tag};
-use radroots_authority::RadrootsLocalEventSigner;
use radroots_event::contract::AuthorRole;
use radroots_event::draft::EventDraft;
use radroots_event::envelope::kind::{
KIND_CLASSIFIED_LISTING, KIND_COOP, KIND_FARM, TRADE_MUTATION_EVENT_KINDS,
};
-use radroots_nostr::prelude::RadrootsNostrEvent;
+use radroots_nostr::{prelude::RadrootsNostrEvent, signing::LocalSigner};
use radroots_nostr_connect::prelude::{
RADROOTS_NOSTR_CONNECT_RPC_KIND, RadrootsNostrConnectClientTarget, RadrootsNostrConnectError,
RadrootsNostrConnectRequest, RadrootsNostrConnectRequestMessage, RadrootsNostrConnectResponse,
};
+use radroots_signing::actor::ActorSource;
use std::collections::VecDeque;
use std::future;
use std::sync::{Arc, LazyLock, Mutex};
@@ -32,10 +32,9 @@ fn user_pubkey() -> &'static str {
}
fn local_sdk_signer(keys: RadrootsNostrKeys) -> RadrootsSdkLocalKeySigner {
- RadrootsSdkLocalKeySigner::from_event_signer(
- RadrootsLocalEventSigner::new(keys).expect("local event signer"),
- )
- .expect("sdk local signer")
+ let signer_pubkey = keys.public_key().to_hex();
+ RadrootsSdkLocalKeySigner::from_signer(LocalSigner::new(keys), signer_pubkey)
+ .expect("sdk local signer")
}
fn remote_keys() -> RadrootsNostrKeys {
@@ -46,8 +45,13 @@ fn client_keys() -> RadrootsNostrKeys {
CLIENT_KEYS.clone()
}
-fn actor() -> RadrootsActorContext {
- RadrootsActorContext::test(user_pubkey(), [AuthorRole::Farmer]).expect("actor")
+fn actor() -> Actor {
+ Actor::from_public_key_hex(
+ user_pubkey(),
+ ActorSource::ExplicitPublicKey,
+ [AuthorRole::Farmer],
+ )
+ .expect("actor")
}
fn frozen_draft() -> EventDraft {
@@ -273,8 +277,12 @@ async fn local_key_provider_returns_progress_sink_errors_without_transport_state
let signer = local_sdk_signer(user_keys());
let draft = frozen_draft();
let actor = actor();
- let wrong_actor =
- RadrootsActorContext::test("a".repeat(64), [AuthorRole::Farmer]).expect("wrong actor");
+ let wrong_actor = Actor::from_public_key_hex(
+ &"a".repeat(64),
+ ActorSource::ExplicitPublicKey,
+ [AuthorRole::Farmer],
+ )
+ .expect("wrong actor");
assert!(matches!(
signer
@@ -409,14 +417,6 @@ fn signer_provider_reports_myc_status_capability_and_constructor_errors() {
#[test]
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().to_hex(), user_pubkey());
- assert!(matches!(
- identity.sign_frozen_draft(&frozen_draft()),
- Err(RadrootsSignerError::Unavailable)
- ));
-
assert!(matches!(
signed_event_from_nip46_response(
"farm.publish",
@@ -645,8 +645,12 @@ async fn myc_nip46_provider_reports_preflight_and_progress_sink_edges() {
));
assert!(transport.published().is_empty());
- let wrong_actor =
- RadrootsActorContext::test("a".repeat(64), [AuthorRole::Farmer]).expect("wrong actor");
+ let wrong_actor = Actor::from_public_key_hex(
+ &"a".repeat(64),
+ ActorSource::ExplicitPublicKey,
+ [AuthorRole::Farmer],
+ )
+ .expect("wrong actor");
let actor_error = signer
.sign(RadrootsSdkSignRequest::new(
"farm.publish",
@@ -678,7 +682,7 @@ async fn myc_nip46_provider_reports_preflight_and_progress_sink_edges() {
.expect_err("signer mismatch");
assert!(matches!(
signer_error,
- RadrootsSdkError::SignerPubkeyMismatch { .. }
+ RadrootsSdkError::UnauthorizedActor { .. }
));
assert!(mismatch_transport.published().is_empty());
}
diff --git a/crates/sdk/tests/unit/sync_runtime_tests.rs b/crates/sdk/tests/unit/sync_runtime_tests.rs
@@ -18,20 +18,14 @@ use crate::adapters::radrootsd::{RadrootsdError, RadrootsdPublishAdapter, Radroo
use crate::workflow_runtime::{SdkWorkflowEnqueueRequest, enqueue_signed_workflow};
use futures::future::BoxFuture;
#[cfg(feature = "radrootsd-execution")]
-use radroots_authority::{
- RadrootsActorContext, RadrootsEventSigner, RadrootsSignerError, RadrootsSignerIdentity,
-};
-#[cfg(feature = "radrootsd-execution")]
use radroots_event::contract::AuthorRole;
#[cfg(feature = "radrootsd-execution")]
-use radroots_event::draft::{EventDraft, SignedEvent};
+use radroots_event::draft::EventDraft;
#[cfg(feature = "radrootsd-execution")]
use radroots_event::envelope::kind::KIND_FARM;
use radroots_event::id::EventId;
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::{
@@ -52,6 +46,11 @@ use radroots_protocol::radrootsd::transport_publish::v5::{
TargetOutcome as TransportPublishTargetOutcome, TargetPolicy as TransportPublishTargetPolicy,
TargetSource as TransportPublishTargetSource,
};
+#[cfg(feature = "radrootsd-execution")]
+use radroots_signing::{
+ Actor, Error as SigningError, SignReceipt, SignRequest, Signer, SignerStatus,
+ actor::ActorSource, error::Kind as SigningErrorKind, signer::BoxFuture as SigningFuture,
+};
use radroots_transport::{
RadrootsTransportDeliveryTargetStatus, RadrootsTransportMeshScopeId, RadrootsTransportTarget,
RadrootsTransportTargetLabel,
@@ -89,7 +88,6 @@ struct UnusedPublishAdapter;
#[cfg(feature = "radrootsd-execution")]
struct RadrootsdFixtureSigner {
- identity: RadrootsSignerIdentity,
keys: RadrootsNostrKeys,
}
@@ -97,24 +95,31 @@ struct RadrootsdFixtureSigner {
impl RadrootsdFixtureSigner {
fn new() -> Self {
Self {
- identity: RadrootsSignerIdentity::new(radrootsd_fixture_signer_pubkey())
- .expect("identity"),
keys: RADROOTSD_FIXTURE_SIGNER_KEYS.clone(),
}
}
+
+ fn sign_frozen_draft(
+ &self,
+ draft: &EventDraft,
+ ) -> Result<radroots_event::SignedEvent, radroots_nostr::error::RadrootsNostrError> {
+ radroots_nostr_sign_frozen_draft(&self.keys, draft)
+ }
}
#[cfg(feature = "radrootsd-execution")]
-impl RadrootsEventSigner for RadrootsdFixtureSigner {
- fn pubkey(&self) -> &PublicKey {
- self.identity.pubkey()
+impl Signer for RadrootsdFixtureSigner {
+ fn status(&self) -> SigningFuture<'_, Result<SignerStatus, SigningError>> {
+ Box::pin(async { Ok(SignerStatus::unavailable()) })
}
- fn sign_frozen_draft(&self, draft: &EventDraft) -> Result<SignedEvent, RadrootsSignerError> {
- radroots_nostr_sign_frozen_draft(&self.keys, draft).map_err(|error| {
- RadrootsSignerError::SigningFailed {
- message: error.to_string(),
- }
+ fn sign(&self, request: SignRequest) -> SigningFuture<'_, Result<SignReceipt, SigningError>> {
+ Box::pin(async move {
+ let signed_event = radroots_nostr_sign_frozen_draft(&self.keys, request.draft())
+ .map_err(|source| {
+ SigningError::with_source(SigningErrorKind::InternalError, source)
+ })?;
+ SignReceipt::from_signed_event(&request, signed_event, 1_700_000_001)
})
}
}
@@ -130,9 +135,13 @@ impl RadrootsRelayPublishAdapter for UnusedPublishAdapter {
}
#[cfg(feature = "radrootsd-execution")]
-fn radrootsd_actor() -> RadrootsActorContext {
- RadrootsActorContext::test(radrootsd_fixture_signer_pubkey(), [AuthorRole::Farmer])
- .expect("actor")
+fn radrootsd_actor() -> Actor {
+ Actor::from_public_key_hex(
+ radrootsd_fixture_signer_pubkey(),
+ ActorSource::ExplicitPublicKey,
+ [AuthorRole::Farmer],
+ )
+ .expect("actor")
}
#[cfg(feature = "radrootsd-execution")]
@@ -164,7 +173,7 @@ async fn claimed_radrootsd_event(
enqueue_signed_workflow(
&sdk,
SdkWorkflowEnqueueRequest {
- operation_kind: "sync.radrootsd.unit.v1",
+ operation_kind: "sync.push.v1",
actor: &actor,
frozen_draft: &draft,
target_policy: crate::TargetPolicy::try_nostr_relays(
diff --git a/crates/sdk/tests/unit/trade_runtime_tests.rs b/crates/sdk/tests/unit/trade_runtime_tests.rs
@@ -3,7 +3,6 @@ use crate::{
RadrootsClient, RadrootsSdkError, RadrootsSdkTimestamp, RadrootsSdkTradeErrorKind,
SatisfactionPolicy, SdkIdempotencyKey, TargetPolicy,
};
-use radroots_authority::{RadrootsActorContext, RadrootsLocalEventSigner};
use radroots_event::{
contract::AuthorRole,
envelope::kind::TRADE_MUTATION_EVENT_KINDS,
@@ -20,6 +19,8 @@ use radroots_event::{
};
use radroots_identity::PublicKey;
use radroots_nostr::prelude::RadrootsNostrKeys;
+use radroots_nostr::signing::LocalSigner;
+use radroots_signing::{Actor, actor::ActorSource};
use radroots_trade::model::RadrootsTradePrivateTermsStateV1;
use radroots_trade::reducer::{RADROOTS_TRADE_REDUCER_CONTRACT_ID, RADROOTS_TRADE_REDUCER_VERSION};
@@ -35,13 +36,10 @@ fn trade_id() -> TradeId {
TradeId::parse("11111111111111111111111111111111").expect("trade id")
}
-fn local_signer() -> (String, RadrootsLocalEventSigner) {
+fn local_signer() -> (String, LocalSigner) {
let keys = RadrootsNostrKeys::generate();
let pubkey = keys.public_key().to_hex();
- (
- pubkey,
- RadrootsLocalEventSigner::new(keys).expect("local event signer"),
- )
+ (pubkey, LocalSigner::new(keys))
}
#[tokio::test]
@@ -105,12 +103,22 @@ async fn trade_capabilities_report_canonical_release_product_surface() {
assert!(!capabilities.optional_integrations.reticulum_transport);
}
-fn buyer_actor(buyer_pubkey: &str) -> RadrootsActorContext {
- RadrootsActorContext::test(buyer_pubkey, [AuthorRole::Buyer]).expect("buyer")
+fn buyer_actor(buyer_pubkey: &str) -> Actor {
+ Actor::from_public_key_hex(
+ buyer_pubkey,
+ ActorSource::ExplicitPublicKey,
+ [AuthorRole::Buyer],
+ )
+ .expect("buyer")
}
-fn seller_actor(seller_pubkey: &str) -> RadrootsActorContext {
- RadrootsActorContext::test(seller_pubkey, [AuthorRole::Seller]).expect("seller")
+fn seller_actor(seller_pubkey: &str) -> Actor {
+ Actor::from_public_key_hex(
+ seller_pubkey,
+ ActorSource::ExplicitPublicKey,
+ [AuthorRole::Seller],
+ )
+ .expect("seller")
}
fn candidate(buyer_pubkey: &str, seller_pubkey: &str) -> TradeCandidateTermsV1 {
diff --git a/crates/sdk/tests/unit/workflow_runtime_tests.rs b/crates/sdk/tests/unit/workflow_runtime_tests.rs
@@ -1,12 +1,14 @@
use super::*;
#[cfg(feature = "signer-adapters")]
use crate::{RadrootsSdkLocalKeySigner, RadrootsSdkSignerProvider};
-use radroots_authority::{RadrootsSignerError, RadrootsSignerIdentity};
use radroots_event::contract::AuthorRole;
use radroots_event::draft::{EventDraft, SignedEvent, SignedEventParts};
use radroots_event::envelope::kind::{KIND_FARM, KIND_GEOCHAT};
-use radroots_identity::PublicKey;
use radroots_nostr::prelude::{RadrootsNostrKeys, radroots_nostr_sign_frozen_draft};
+use radroots_signing::{
+ Error as SigningError, SignReceipt, SignRequest, SignerStatus, actor::ActorSource,
+ error::Kind as SigningErrorKind, signer::BoxFuture,
+};
use std::sync::LazyLock;
struct WorkflowKeyMaterial {
@@ -30,37 +32,31 @@ fn workflow_idempotency_key(index: u16) -> SdkIdempotencyKey {
}
struct WorkflowSigner {
- identity: RadrootsSignerIdentity,
keys: RadrootsNostrKeys,
}
impl WorkflowSigner {
fn new() -> Self {
Self {
- identity: RadrootsSignerIdentity::new(farmer_pubkey()).expect("identity"),
keys: WORKFLOW_KEY_MATERIAL.keys.clone(),
}
}
}
-struct FailIfCalledSigner {
- identity: RadrootsSignerIdentity,
-}
+struct FailIfCalledSigner;
impl FailIfCalledSigner {
fn new() -> Self {
- Self {
- identity: RadrootsSignerIdentity::new(farmer_pubkey()).expect("identity"),
- }
+ Self
}
}
-impl RadrootsEventSigner for FailIfCalledSigner {
- fn pubkey(&self) -> &PublicKey {
- self.identity.pubkey()
+impl Signer for FailIfCalledSigner {
+ fn status(&self) -> BoxFuture<'_, Result<SignerStatus, SigningError>> {
+ Box::pin(async { Ok(SignerStatus::unavailable()) })
}
- fn sign_frozen_draft(&self, _draft: &EventDraft) -> Result<SignedEvent, RadrootsSignerError> {
+ fn sign(&self, _request: SignRequest) -> BoxFuture<'_, Result<SignReceipt, SigningError>> {
panic!("ephemeral workflow preflight must not invoke the signer")
}
}
@@ -73,35 +69,39 @@ impl InvalidSignatureSigner {
}
}
-impl RadrootsEventSigner for InvalidSignatureSigner {
- fn pubkey(&self) -> &PublicKey {
- self.0.pubkey()
+impl Signer for InvalidSignatureSigner {
+ fn status(&self) -> BoxFuture<'_, Result<SignerStatus, SigningError>> {
+ Box::pin(async { Ok(SignerStatus::unavailable()) })
}
- fn sign_frozen_draft(&self, draft: &EventDraft) -> Result<SignedEvent, RadrootsSignerError> {
- let signed = self.0.sign_frozen_draft(draft)?;
- let mut wire = signed.wire().clone();
- wire.sig = "0".repeat(128);
- let raw_json =
- serde_json::to_string(&wire).expect("invalid-signature fixture must serialize");
- SignedEvent::from_wire_verified_id(wire, raw_json).map_err(|error| {
- RadrootsSignerError::SigningFailed {
- message: error.to_string(),
- }
+ fn sign(&self, request: SignRequest) -> BoxFuture<'_, Result<SignReceipt, SigningError>> {
+ Box::pin(async move {
+ let receipt = self.0.sign(request.clone()).await?;
+ let mut wire = receipt.signed_event().wire().clone();
+ wire.sig = "0".repeat(128);
+ let raw_json =
+ serde_json::to_string(&wire).expect("invalid-signature fixture must serialize");
+ let signed_event =
+ SignedEvent::from_wire_verified_id(wire, raw_json).map_err(|source| {
+ SigningError::with_source(SigningErrorKind::InternalError, source)
+ })?;
+ SignReceipt::from_signed_event(&request, signed_event, 1_700_000_001)
})
}
}
-impl RadrootsEventSigner for WorkflowSigner {
- fn pubkey(&self) -> &PublicKey {
- self.identity.pubkey()
+impl Signer for WorkflowSigner {
+ fn status(&self) -> BoxFuture<'_, Result<SignerStatus, SigningError>> {
+ Box::pin(async { Ok(SignerStatus::unavailable()) })
}
- fn sign_frozen_draft(&self, draft: &EventDraft) -> Result<SignedEvent, RadrootsSignerError> {
- radroots_nostr_sign_frozen_draft(&self.keys, draft).map_err(|error| {
- RadrootsSignerError::SigningFailed {
- message: error.to_string(),
- }
+ fn sign(&self, request: SignRequest) -> BoxFuture<'_, Result<SignReceipt, SigningError>> {
+ Box::pin(async move {
+ let signed_event = radroots_nostr_sign_frozen_draft(&self.keys, request.draft())
+ .map_err(|source| {
+ SigningError::with_source(SigningErrorKind::InternalError, source)
+ })?;
+ SignReceipt::from_signed_event(&request, signed_event, 1_700_000_001)
})
}
}
@@ -208,7 +208,7 @@ fn workflow_digest_and_event_helpers_cover_error_and_input_paths() {
let idempotency_key =
SdkIdempotencyKey::new("01890f0e-6c00-7000-8000-000000000237").expect("idempotency");
let input = signed_outbox_input(
- "workflow.test.v1",
+ "farm.publish.v1",
&draft,
signed_event(),
workflow_delivery_plan(),
@@ -216,7 +216,7 @@ fn workflow_digest_and_event_helpers_cover_error_and_input_paths() {
true,
1_700_000_000_000,
);
- assert_eq!(input.operation_kind, "workflow.test.v1");
+ assert_eq!(input.operation_kind, "farm.publish.v1");
assert_eq!(
input.delivery_plan.targets[0].uri().as_str(),
"wss://relay.example.com"
@@ -272,7 +272,12 @@ async fn enqueue_signed_workflow_rejects_ephemeral_event_before_durable_commit()
.build()
.await
.expect("sdk");
- let actor = RadrootsActorContext::test(farmer_pubkey(), [AuthorRole::Farmer]).expect("actor");
+ let actor = Actor::from_public_key_hex(
+ farmer_pubkey(),
+ ActorSource::ExplicitPublicKey,
+ [AuthorRole::Farmer],
+ )
+ .expect("actor");
let draft = ephemeral_draft_for(farmer_pubkey());
let error = enqueue_signed_workflow(
&sdk,
@@ -330,9 +335,14 @@ async fn enqueue_signed_workflow_rejects_invalid_signer_signature_without_storag
.build()
.await
.expect("sdk");
- let actor = RadrootsActorContext::test(farmer_pubkey(), [AuthorRole::Farmer]).expect("actor");
+ let actor = Actor::from_public_key_hex(
+ farmer_pubkey(),
+ ActorSource::ExplicitPublicKey,
+ [AuthorRole::Farmer],
+ )
+ .expect("actor");
let draft = frozen_draft_for_d_tag(farmer_pubkey(), "workflow-invalid-signature");
- let operation_kind = "workflow.invalid-signature.test.v1";
+ let operation_kind = "farm.publish.v1";
let error = enqueue_signed_workflow(
&sdk,
@@ -397,7 +407,12 @@ async fn workflow_idempotency_replays_original_receipt_and_conflicts_on_new_comm
.build()
.await
.expect("sdk");
- let actor = RadrootsActorContext::test(farmer_pubkey(), [AuthorRole::Farmer]).expect("actor");
+ let actor = Actor::from_public_key_hex(
+ farmer_pubkey(),
+ ActorSource::ExplicitPublicKey,
+ [AuthorRole::Farmer],
+ )
+ .expect("actor");
let signer = WorkflowSigner::new();
let draft = frozen_draft_for_d_tag(farmer_pubkey(), "workflow-target-policy");
let first_target_policy = TargetPolicy::try_nostr_relays(
@@ -414,7 +429,7 @@ async fn workflow_idempotency_replays_original_receipt_and_conflicts_on_new_comm
let first = enqueue_signed_workflow(
&sdk,
SdkWorkflowEnqueueRequest {
- operation_kind: "workflow.test.v1",
+ operation_kind: "farm.publish.v1",
actor: &actor,
frozen_draft: &draft,
target_policy: first_target_policy.clone(),
@@ -428,7 +443,7 @@ async fn workflow_idempotency_replays_original_receipt_and_conflicts_on_new_comm
let replay = enqueue_signed_workflow(
&sdk,
SdkWorkflowEnqueueRequest {
- operation_kind: "workflow.test.v1",
+ operation_kind: "farm.publish.v1",
actor: &actor,
frozen_draft: &draft,
target_policy: first_target_policy.clone(),
@@ -442,7 +457,7 @@ async fn workflow_idempotency_replays_original_receipt_and_conflicts_on_new_comm
let conflict = enqueue_signed_workflow(
&sdk,
SdkWorkflowEnqueueRequest {
- operation_kind: "workflow.test.v1",
+ operation_kind: "farm.publish.v1",
actor: &actor,
frozen_draft: &draft,
target_policy: second_target_policy,
@@ -511,14 +526,19 @@ async fn enqueue_signed_workflow_maps_no_wait_directly_and_allows_local_only_pro
.build()
.await
.expect("sdk");
- let actor = RadrootsActorContext::test(farmer_pubkey(), [AuthorRole::Farmer]).expect("actor");
+ let actor = Actor::from_public_key_hex(
+ farmer_pubkey(),
+ ActorSource::ExplicitPublicKey,
+ [AuthorRole::Farmer],
+ )
+ .expect("actor");
let signer = WorkflowSigner::new();
let draft = frozen_draft_for_d_tag(farmer_pubkey(), "workflow-no-wait");
let receipt = enqueue_signed_workflow(
&sdk,
SdkWorkflowEnqueueRequest {
- operation_kind: "workflow.test.v1",
+ operation_kind: "farm.publish.v1",
actor: &actor,
frozen_draft: &draft,
target_policy: TargetPolicy::default_profile(),
@@ -584,13 +604,18 @@ async fn enqueue_signed_workflow_rejects_missing_explicit_idempotency_key_withou
.build()
.await
.expect("sdk");
- let actor = RadrootsActorContext::test(farmer_pubkey(), [AuthorRole::Farmer]).expect("actor");
+ let actor = Actor::from_public_key_hex(
+ farmer_pubkey(),
+ ActorSource::ExplicitPublicKey,
+ [AuthorRole::Farmer],
+ )
+ .expect("actor");
let draft = frozen_draft_for_d_tag(farmer_pubkey(), "workflow-missing-idempotency");
let error = match enqueue_signed_workflow(
&sdk,
SdkWorkflowEnqueueRequest {
- operation_kind: "workflow.test.v1",
+ operation_kind: "farm.publish.v1",
actor: &actor,
frozen_draft: &draft,
target_policy: TargetPolicy::default_profile(),
@@ -634,7 +659,12 @@ async fn enqueue_signed_workflow_stores_signed_event_and_reports_idempotency_con
.build()
.await
.expect("sdk");
- let actor = RadrootsActorContext::test(farmer_pubkey(), [AuthorRole::Farmer]).expect("actor");
+ let actor = Actor::from_public_key_hex(
+ farmer_pubkey(),
+ ActorSource::ExplicitPublicKey,
+ [AuthorRole::Farmer],
+ )
+ .expect("actor");
let signer = WorkflowSigner::new();
let first_draft = frozen_draft_for_d_tag(farmer_pubkey(), "workflow-success");
let idempotency_key =
@@ -642,7 +672,7 @@ async fn enqueue_signed_workflow_stores_signed_event_and_reports_idempotency_con
let receipt = enqueue_signed_workflow(
&sdk,
SdkWorkflowEnqueueRequest {
- operation_kind: "workflow.test.v1",
+ operation_kind: "farm.publish.v1",
actor: &actor,
frozen_draft: &first_draft,
target_policy: TargetPolicy::default_profile(),
@@ -683,7 +713,7 @@ async fn enqueue_signed_workflow_stores_signed_event_and_reports_idempotency_con
let error = match enqueue_signed_workflow(
&sdk,
SdkWorkflowEnqueueRequest {
- operation_kind: "workflow.test.v1",
+ operation_kind: "farm.publish.v1",
actor: &actor,
frozen_draft: &second_draft,
target_policy: TargetPolicy::default_profile(),
@@ -703,7 +733,7 @@ async fn enqueue_signed_workflow_stores_signed_event_and_reports_idempotency_con
RadrootsSdkError::IdempotencyConflict {
operation_kind,
..
- } if operation_kind == "workflow.test.v1"
+ } if operation_kind == "farm.publish.v1"
));
assert_eq!(
sdk._event_store
@@ -732,19 +762,24 @@ async fn enqueue_configured_signed_workflow_uses_sdk_signer_provider() {
1_700_000_011,
))
.signer_provider(RadrootsSdkSignerProvider::LocalKey(
- RadrootsSdkLocalKeySigner::from_event_signer(WorkflowSigner::new())
+ RadrootsSdkLocalKeySigner::from_signer(WorkflowSigner::new(), farmer_pubkey())
.expect("local signer"),
))
.build()
.await
.expect("sdk");
- let actor = RadrootsActorContext::test(farmer_pubkey(), [AuthorRole::Farmer]).expect("actor");
+ let actor = Actor::from_public_key_hex(
+ farmer_pubkey(),
+ ActorSource::ExplicitPublicKey,
+ [AuthorRole::Farmer],
+ )
+ .expect("actor");
let draft = frozen_draft_for_d_tag(farmer_pubkey(), "workflow-configured");
let receipt = enqueue_configured_signed_workflow(
&sdk,
SdkWorkflowEnqueueRequest {
- operation_kind: "workflow.test.v1",
+ operation_kind: "farm.publish.v1",
actor: &actor,
frozen_draft: &draft,
target_policy: TargetPolicy::default_profile(),
@@ -778,10 +813,15 @@ async fn enqueue_signed_workflow_reports_runtime_pool_failure_before_mutation()
0
);
sdk._outbox.pool().close().await;
- let actor = RadrootsActorContext::test(farmer_pubkey(), [AuthorRole::Farmer]).expect("actor");
+ let actor = Actor::from_public_key_hex(
+ farmer_pubkey(),
+ ActorSource::ExplicitPublicKey,
+ [AuthorRole::Farmer],
+ )
+ .expect("actor");
let draft = frozen_draft_for(farmer_pubkey());
let request = SdkWorkflowEnqueueRequest {
- operation_kind: "workflow.test.v1",
+ operation_kind: "farm.publish.v1",
actor: &actor,
frozen_draft: &draft,
target_policy: TargetPolicy::default_profile(),
@@ -799,7 +839,12 @@ async fn enqueue_signed_workflow_reports_runtime_pool_failure_before_mutation()
#[tokio::test]
async fn enqueue_signed_workflow_reports_store_failures() {
- let actor = RadrootsActorContext::test(farmer_pubkey(), [AuthorRole::Farmer]).expect("actor");
+ let actor = Actor::from_public_key_hex(
+ farmer_pubkey(),
+ ActorSource::ExplicitPublicKey,
+ [AuthorRole::Farmer],
+ )
+ .expect("actor");
let draft = frozen_draft_for(farmer_pubkey());
let closed_store_sdk = crate::RadrootsClient::builder()
.transport_profile(nostr_profile("wss://relay.example.com"))
@@ -808,7 +853,7 @@ async fn enqueue_signed_workflow_reports_store_failures() {
.expect("sdk");
closed_store_sdk._event_store.pool().close().await;
let store_failure_request = SdkWorkflowEnqueueRequest {
- operation_kind: "workflow.test.v1",
+ operation_kind: "farm.publish.v1",
actor: &actor,
frozen_draft: &draft,
target_policy: TargetPolicy::default_profile(),
@@ -834,10 +879,15 @@ async fn enqueue_signed_workflow_reports_clock_failures() {
.build()
.await
.expect("sdk");
- let actor = RadrootsActorContext::test(farmer_pubkey(), [AuthorRole::Farmer]).expect("actor");
+ let actor = Actor::from_public_key_hex(
+ farmer_pubkey(),
+ ActorSource::ExplicitPublicKey,
+ [AuthorRole::Farmer],
+ )
+ .expect("actor");
let draft = frozen_draft_for(farmer_pubkey());
let request = SdkWorkflowEnqueueRequest {
- operation_kind: "workflow.test.v1",
+ operation_kind: "farm.publish.v1",
actor: &actor,
frozen_draft: &draft,
target_policy: TargetPolicy::default_profile(),
@@ -853,10 +903,15 @@ async fn enqueue_signed_workflow_reports_clock_failures() {
#[tokio::test]
async fn enqueue_signed_workflow_rejects_transport_profile_targets_without_radrootsd_execution() {
let sdk = crate::RadrootsClient::builder().build().await.expect("sdk");
- let actor = RadrootsActorContext::test(farmer_pubkey(), [AuthorRole::Farmer]).expect("actor");
+ let actor = Actor::from_public_key_hex(
+ farmer_pubkey(),
+ ActorSource::ExplicitPublicKey,
+ [AuthorRole::Farmer],
+ )
+ .expect("actor");
let draft = frozen_draft_for(farmer_pubkey());
let request = SdkWorkflowEnqueueRequest {
- operation_kind: "workflow.test.v1",
+ operation_kind: "farm.publish.v1",
actor: &actor,
frozen_draft: &draft,
target_policy: TargetPolicy::DefaultProfile,