radrootsd

JSON-RPC bridge for Radroots event publishing
git clone https://radroots.dev/git/radrootsd.git
Log | Files | Refs | README | LICENSE

commit 6b66c168cb617b4184fa81ad1aeb8432be7c9659
parent 8331eb5bbfbd21e4de06f2673235fd1559ac7f07
Author: triesap <tyson@radroots.org>
Date:   Fri, 27 Mar 2026 20:48:23 +0000

bridge: route listing publish through rr-rs signer backend

Diffstat:
MCargo.lock | 30++++++++++++++++++++++++++++++
MCargo.toml | 2++
Msrc/app/runtime.rs | 14++++++++------
Msrc/core/state.rs | 34+++++++++++++++++++++++++---------
Msrc/transport/jsonrpc/methods/bridge/listing_publish.rs | 20++++++++++++++++----
5 files changed, 81 insertions(+), 19 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock @@ -1772,6 +1772,7 @@ dependencies = [ name = "radroots-log" version = "0.1.0-alpha.1" dependencies = [ + "chrono", "thiserror 1.0.69", "tracing", "tracing-appender", @@ -1794,6 +1795,34 @@ dependencies = [ ] [[package]] +name = "radroots-nostr-connect" +version = "0.1.0-alpha.1" +dependencies = [ + "nostr", + "serde", + "serde_json", + "thiserror 1.0.69", + "url", +] + +[[package]] +name = "radroots-nostr-signer" +version = "0.1.0-alpha.1" +dependencies = [ + "hex", + "nostr", + "radroots-identity", + "radroots-nostr-connect", + "radroots-runtime", + "serde", + "serde_json", + "sha2", + "thiserror 1.0.69", + "url", + "uuid", +] + +[[package]] name = "radroots-runtime" version = "0.1.0-alpha.1" dependencies = [ @@ -1835,6 +1864,7 @@ dependencies = [ "radroots-events-codec", "radroots-identity", "radroots-nostr", + "radroots-nostr-signer", "radroots-runtime", "radroots-trade", "reqwest", diff --git a/Cargo.toml b/Cargo.toml @@ -16,6 +16,7 @@ radroots-events = { path = "../lib/crates/events" } radroots-events-codec = { path = "../lib/crates/events-codec" } radroots-identity = { path = "../lib/crates/identity" } radroots-nostr = { path = "../lib/crates/nostr" } +radroots-nostr-signer = { path = "../lib/crates/nostr-signer" } radroots-runtime = { path = "../lib/crates/runtime" } radroots-trade = { path = "../lib/crates/trade" } @@ -28,6 +29,7 @@ radroots-events = { workspace = true, features = ["serde"] } radroots-events-codec = { workspace = true, features = ["nostr", "serde_json"] } radroots-identity = { workspace = true } radroots-nostr = { workspace = true, features = ["client", "codec", "events", "http"] } +radroots-nostr-signer = { workspace = true } radroots-runtime = { workspace = true, features = ["cli"] } radroots-trade = { workspace = true } nostr = { version = "0.44.2", features = ["nip46"] } diff --git a/src/app/runtime.rs b/src/app/runtime.rs @@ -269,13 +269,13 @@ pub async fn run() -> Result<()> { args.service.identity.as_ref(), args.service.allow_generate_identity, )?; - let keys = identity.keys().clone(); let radrootsd = Radrootsd::new( - keys, + identity.clone(), settings.metadata.clone(), settings.config.bridge.clone(), settings.config.nip46.clone(), ); + let radrootsd = radrootsd?; for relay in settings.config.service.relays.iter() { radrootsd.client.add_relay(relay).await?; @@ -332,7 +332,8 @@ mod tests { use crate::core::Radrootsd; use crate::transport::jsonrpc; use radroots_events::kinds::KIND_LISTING; - use radroots_nostr::prelude::{RadrootsNostrKeys, RadrootsNostrMetadata}; + use radroots_identity::RadrootsIdentity; + use radroots_nostr::prelude::RadrootsNostrMetadata; use std::path::PathBuf; use std::sync::{Mutex, MutexGuard}; @@ -399,13 +400,14 @@ mod tests { } async fn make_handle(settings: &config::Settings) -> jsonrpsee::server::ServerHandle { - let keys = RadrootsNostrKeys::generate(); + let identity = RadrootsIdentity::generate(); let state = Radrootsd::new( - keys, + identity, settings.metadata.clone(), settings.config.bridge.clone(), settings.config.nip46.clone(), - ); + ) + .expect("state"); jsonrpc::start_rpc( state, "127.0.0.1:0".parse().expect("addr"), diff --git a/src/core/state.rs b/src/core/state.rs @@ -1,6 +1,9 @@ +use anyhow::Result; +use radroots_identity::RadrootsIdentity; use radroots_nostr::prelude::{ RadrootsNostrClient, RadrootsNostrKeys, RadrootsNostrMetadata, RadrootsNostrPublicKey, }; +use radroots_nostr_signer::prelude::RadrootsNostrEmbeddedSignerBackend; use crate::app::config::{BridgeConfig, Nip46Config}; @@ -11,6 +14,7 @@ pub struct Radrootsd { pub pubkey: RadrootsNostrPublicKey, pub metadata: RadrootsNostrMetadata, pub info: serde_json::Value, + pub bridge_signer: RadrootsNostrEmbeddedSignerBackend, pub(crate) bridge_jobs: crate::core::bridge::store::BridgeJobStore, pub bridge_config: BridgeConfig, pub(crate) nip46_sessions: crate::core::nip46::session::Nip46SessionStore, @@ -19,32 +23,35 @@ pub struct Radrootsd { impl Radrootsd { pub fn new( - keys: RadrootsNostrKeys, + identity: RadrootsIdentity, metadata: RadrootsNostrMetadata, bridge_config: BridgeConfig, nip46_config: Nip46Config, - ) -> Self { + ) -> Result<Self> { + let keys: RadrootsNostrKeys = identity.keys().clone(); let pubkey = keys.public_key(); let client = RadrootsNostrClient::new(keys.clone()); let info = serde_json::json!({ "version": env!("CARGO_PKG_VERSION"), "build": option_env!("GIT_HASH").unwrap_or("unknown"), }); + let bridge_signer = RadrootsNostrEmbeddedSignerBackend::new_in_memory(identity)?; let bridge_jobs = crate::core::bridge::store::BridgeJobStore::new(bridge_config.job_status_retention); let nip46_sessions = crate::core::nip46::session::Nip46SessionStore::new(); - Self { + Ok(Self { client, keys, pubkey, metadata, info, + bridge_signer, bridge_jobs, bridge_config, nip46_sessions, nip46_config, - } + }) } } @@ -52,23 +59,26 @@ impl Radrootsd { mod tests { use super::Radrootsd; use crate::app::config::{BridgeConfig, Nip46Config}; - use radroots_nostr::prelude::{RadrootsNostrKeys, RadrootsNostrMetadata}; + use radroots_identity::RadrootsIdentity; + use radroots_nostr::prelude::RadrootsNostrMetadata; + use radroots_nostr_signer::prelude::RadrootsNostrSignerBackend; #[test] fn new_sets_core_fields() { - let keys = RadrootsNostrKeys::generate(); + let identity = RadrootsIdentity::generate(); let metadata: RadrootsNostrMetadata = serde_json::from_str(r#"{"name":"radrootsd-test"}"#).expect("metadata"); let bridge_cfg = BridgeConfig::default(); let cfg = Nip46Config::default(); let state = Radrootsd::new( - keys.clone(), + identity.clone(), metadata.clone(), bridge_cfg.clone(), cfg.clone(), - ); + ) + .expect("state"); - assert_eq!(state.pubkey, keys.public_key()); + assert_eq!(state.pubkey, identity.public_key()); assert_eq!(state.metadata, metadata); assert_eq!(state.bridge_config.enabled, bridge_cfg.enabled); assert_eq!( @@ -79,5 +89,11 @@ mod tests { assert_eq!(state.nip46_config.perms, cfg.perms); assert_eq!(state.info["version"], env!("CARGO_PKG_VERSION")); assert_eq!(state.info["build"], "unknown"); + let signer_identity = state + .bridge_signer + .signer_identity() + .expect("bridge signer identity") + .expect("present"); + assert_eq!(signer_identity.public_key_hex, state.pubkey.to_hex()); } } diff --git a/src/transport/jsonrpc/methods/bridge/listing_publish.rs b/src/transport/jsonrpc/methods/bridge/listing_publish.rs @@ -3,6 +3,7 @@ use jsonrpsee::server::RpcModule; use radroots_events::listing::RadrootsListing; use radroots_events_codec::listing::encode::to_wire_parts; use radroots_nostr::prelude::{radroots_event_from_nostr, radroots_nostr_build_event}; +use radroots_nostr_signer::prelude::RadrootsNostrSignerBackend; use radroots_trade::listing::validation::validate_listing_event; use serde::{Deserialize, Serialize}; use uuid::Uuid; @@ -45,15 +46,26 @@ async fn publish_listing( } let idempotency_key = normalize_idempotency_key(params.idempotency_key)?; - let listing = - canonicalize_listing_for_embedded_signer(params.listing, &ctx.state.pubkey.to_hex()); + let signer_identity = ctx + .state + .bridge_signer + .signer_identity() + .map_err(|error| RpcError::Other(format!("bridge signer unavailable: {error}")))? + .ok_or_else(|| RpcError::Other("bridge signer identity is missing".to_string()))?; + let listing = canonicalize_listing_for_embedded_signer( + params.listing, + signer_identity.public_key_hex.as_str(), + ); let parts = to_wire_parts(&listing) .map_err(|error| RpcError::InvalidParams(format!("invalid listing contract: {error}")))?; let builder = radroots_nostr_build_event(parts.kind, parts.content, parts.tags) .map_err(|error| RpcError::Other(format!("failed to build listing event: {error}")))?; - let event = builder - .sign_with_keys(&ctx.state.keys) + let signed = ctx + .state + .bridge_signer + .sign_event_builder(builder) .map_err(|error| RpcError::Other(format!("failed to sign listing event: {error}")))?; + let event = signed.event; let canonical = radroots_event_from_nostr(&event); let validated = validate_listing_event(&canonical) .map_err(|error| RpcError::InvalidParams(format!("invalid listing contract: {error}")))?;