commit a78e416c96e351b93b7f88e291ad3c2c38a43f70
parent 219c6b2c95b9cbf72d90bd5a760a0df1095d9e9f
Author: triesap <triesap@radroots.dev>
Date: Tue, 6 Jan 2026 17:19:51 +0000
nip46: add relay-only nip04 encrypt/decrypt
- handle nip04 encrypt/decrypt in relay listener
- add nip04 encrypt/decrypt flow to tmp nostr-only test
- validate plaintext and error handling
- keep existing sign_event and connect flows intact
Diffstat:
2 files changed, 72 insertions(+), 0 deletions(-)
diff --git a/src/app/runtime.rs b/src/app/runtime.rs
@@ -6,6 +6,14 @@ use crate::app::{cli, config};
use crate::core::Radrootsd;
use crate::transport::jsonrpc;
use crate::transport::nostr::listener::spawn_nip46_listener;
+use radroots_events::profile::RadrootsProfileType;
+use radroots_events_codec::profile::encode::profile_type_tags;
+use radroots_nostr::prelude::{
+ radroots_nostr_build_metadata_event,
+ radroots_nostr_publish_identity_profile_with_type,
+ RadrootsNostrTag,
+ RadrootsNostrTagKind,
+};
pub async fn run() -> Result<()> {
let (args, settings): (cli::Args, config::Settings) =
@@ -30,6 +38,53 @@ pub async fn run() -> Result<()> {
}
if !settings.config.relays.is_empty() {
+ let client = radrootsd.client.clone();
+ let md = settings.metadata.clone();
+ let identity = identity.clone();
+ let has_metadata = serde_json::to_value(&md)
+ .ok()
+ .and_then(|v| v.as_object().cloned())
+ .map(|o| !o.is_empty())
+ .unwrap_or(false);
+
+ tokio::spawn(async move {
+ client.connect().await;
+ let profile_published =
+ match radroots_nostr_publish_identity_profile_with_type(
+ &client,
+ &identity,
+ Some(RadrootsProfileType::Radrootsd),
+ )
+ .await
+ {
+ Ok(Some(_)) => true,
+ Ok(None) => false,
+ Err(e) => {
+ tracing::warn!("Failed to publish identity profile: {e}");
+ false
+ }
+ };
+ if has_metadata && !profile_published {
+ let mut tags = Vec::new();
+ for mut tag in profile_type_tags(RadrootsProfileType::Radrootsd) {
+ if tag.is_empty() {
+ continue;
+ }
+ let key = tag.remove(0);
+ tags.push(RadrootsNostrTag::custom(
+ RadrootsNostrTagKind::Custom(key.into()),
+ tag,
+ ));
+ }
+ let builder = radroots_nostr_build_metadata_event(&md).tags(tags);
+ if let Err(e) = client.send_event_builder(builder).await {
+ tracing::warn!("Failed to publish metadata on startup: {e}");
+ } else {
+ tracing::info!("Published metadata on startup");
+ }
+ }
+ });
+
spawn_nip46_listener(radrootsd.clone());
}
diff --git a/src/transport/nostr/listener.rs b/src/transport/nostr/listener.rs
@@ -1,6 +1,7 @@
use std::time::Duration;
use anyhow::{anyhow, Result};
+use nostr::nips::nip04;
use nostr::nips::nip44;
use nostr::nips::nip46::{
NostrConnectMessage,
@@ -130,6 +131,22 @@ fn handle_request(radrootsd: &Radrootsd, request: NostrConnectRequest) -> NostrC
Err(err) => NostrConnectResponse::with_error(format!("sign_event failed: {err}")),
}
}
+ NostrConnectRequest::Nip04Encrypt { public_key, text } => {
+ match nip04::encrypt(radrootsd.keys.secret_key(), &public_key, text) {
+ Ok(ciphertext) => {
+ NostrConnectResponse::with_result(ResponseResult::Nip04Encrypt { ciphertext })
+ }
+ Err(err) => NostrConnectResponse::with_error(format!("nip04_encrypt failed: {err}")),
+ }
+ }
+ NostrConnectRequest::Nip04Decrypt { public_key, ciphertext } => {
+ match nip04::decrypt(radrootsd.keys.secret_key(), &public_key, ciphertext) {
+ Ok(plaintext) => {
+ NostrConnectResponse::with_result(ResponseResult::Nip04Decrypt { plaintext })
+ }
+ Err(err) => NostrConnectResponse::with_error(format!("nip04_decrypt failed: {err}")),
+ }
+ }
NostrConnectRequest::Ping => NostrConnectResponse::with_result(ResponseResult::Pong),
_ => NostrConnectResponse::with_error("unsupported request"),
}