radrootsd

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

commit 4e83e5953f54cbb2927dd126a60d31ed87568021
parent 21401de46188514f1b15772f36524a6a589f982f
Author: triesap <triesap@radroots.dev>
Date:   Tue,  6 Jan 2026 01:13:14 +0000

nip46: add get_public_key rpc

- add nip46.get_public_key rpc handler
- send nip46 request and validate response
- decrypt and parse responses via nip44
- return user pubkey from stored session

Diffstat:
Asrc/api/jsonrpc/methods/nip46/get_public_key.rs | 127+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/api/jsonrpc/methods/nip46/mod.rs | 2++
2 files changed, 129 insertions(+), 0 deletions(-)

diff --git a/src/api/jsonrpc/methods/nip46/get_public_key.rs b/src/api/jsonrpc/methods/nip46/get_public_key.rs @@ -0,0 +1,127 @@ +use std::time::Duration; + +use anyhow::Result; +use jsonrpsee::server::RpcModule; +use serde::{Deserialize, Serialize}; + +use crate::api::jsonrpc::params::DEFAULT_TIMEOUT_SECS; +use crate::api::jsonrpc::{MethodRegistry, RpcContext, RpcError}; +use crate::nip46::session::Nip46Session; +use radroots_nostr::prelude::{ + radroots_nostr_filter_tag, + RadrootsNostrEventBuilder, + RadrootsNostrFilter, + RadrootsNostrKind, + RadrootsNostrPublicKey, +}; +use nostr::nips::{ + nip44, + nip46::{NostrConnectMessage, NostrConnectMethod, NostrConnectRequest, ResponseResult}, +}; +use nostr::JsonUtil; + +#[derive(Debug, Deserialize)] +struct Nip46GetPublicKeyParams { + session_id: String, +} + +#[derive(Clone, Debug, Serialize)] +struct Nip46GetPublicKeyResponse { + pubkey: String, +} + +pub fn register(m: &mut RpcModule<RpcContext>, registry: &MethodRegistry) -> Result<()> { + registry.track("nip46.get_public_key"); + m.register_async_method("nip46.get_public_key", |params, ctx, _| async move { + let Nip46GetPublicKeyParams { session_id } = params + .parse() + .map_err(|e| RpcError::InvalidParams(e.to_string()))?; + let session = ctx + .state + .nip46_sessions + .get(&session_id) + .await + .ok_or_else(|| RpcError::InvalidParams("unknown session".to_string()))?; + let pubkey = request_public_key(&session).await?; + Ok::<Nip46GetPublicKeyResponse, RpcError>(Nip46GetPublicKeyResponse { + pubkey: pubkey.to_hex(), + }) + })?; + Ok(()) +} + +async fn request_public_key( + session: &Nip46Session, +) -> Result<RadrootsNostrPublicKey, RpcError> { + session.client.connect().await; + + let request = NostrConnectRequest::GetPublicKey; + let message = NostrConnectMessage::request(&request); + let request_id = message.id().to_string(); + let event = RadrootsNostrEventBuilder::nostr_connect( + &session.client_keys, + session.remote_signer_pubkey.clone(), + message, + ) + .map_err(|e| RpcError::Other(format!("nip46 get_public_key failed: {e}")))?; + + session + .client + .send_event_builder(event) + .await + .map_err(|e| RpcError::Other(format!("nip46 get_public_key failed: {e}")))?; + + let response = wait_for_response(session, &request_id).await?; + let response = response + .to_response(NostrConnectMethod::GetPublicKey) + .map_err(|e| RpcError::Other(format!("nip46 get_public_key failed: {e}")))?; + + if let Some(error) = response.error { + return Err(RpcError::Other(format!("nip46 get_public_key error: {error}"))); + } + + match response.result { + Some(ResponseResult::GetPublicKey(pubkey)) => Ok(pubkey), + Some(_) => Err(RpcError::Other( + "nip46 get_public_key unexpected response".to_string(), + )), + None => Err(RpcError::Other( + "nip46 get_public_key missing response".to_string(), + )), + } +} + +async fn wait_for_response( + session: &Nip46Session, + request_id: &str, +) -> Result<NostrConnectMessage, RpcError> { + let filter = RadrootsNostrFilter::new() + .kind(RadrootsNostrKind::NostrConnect) + .author(session.remote_signer_pubkey.clone()); + let filter = radroots_nostr_filter_tag(filter, "p", vec![session.client_pubkey.to_hex()]) + .map_err(|e| RpcError::Other(format!("nip46 get_public_key failed: {e}")))?; + + let events = session + .client + .fetch_events(filter, Duration::from_secs(DEFAULT_TIMEOUT_SECS)) + .await + .map_err(|e| RpcError::Other(format!("nip46 get_public_key failed: {e}")))?; + + for event in events { + let decrypted = nip44::decrypt( + session.client_keys.secret_key(), + &session.remote_signer_pubkey, + &event.content, + ) + .map_err(|e| RpcError::Other(format!("nip46 get_public_key failed: {e}")))?; + let message = NostrConnectMessage::from_json(&decrypted) + .map_err(|e| RpcError::Other(format!("nip46 get_public_key failed: {e}")))?; + if message.is_response() && message.id() == request_id { + return Ok(message); + } + } + + Err(RpcError::Other( + "nip46 get_public_key response not found".to_string(), + )) +} diff --git a/src/api/jsonrpc/methods/nip46/mod.rs b/src/api/jsonrpc/methods/nip46/mod.rs @@ -7,10 +7,12 @@ use crate::api::jsonrpc::{MethodRegistry, RpcContext}; pub mod status; pub mod connect; +pub mod get_public_key; pub fn module(ctx: RpcContext, registry: MethodRegistry) -> Result<RpcModule<RpcContext>> { let mut m = RpcModule::new(ctx); status::register(&mut m, &registry)?; connect::register(&mut m, &registry)?; + get_public_key::register(&mut m, &registry)?; Ok(m) }