feat: add NIP-04 and NIP-44 encrypt/decrypt RPC endpoints for iframe apps

Backend: identity.nostr-encrypt-nip04, identity.nostr-decrypt-nip04,
identity.nostr-encrypt-nip44, identity.nostr-decrypt-nip44 endpoints
with auto-resolve to default identity. Frontend: appLauncher routes
nip04.* and nip44.* postMessage calls to backend RPC.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Dorian
2026-03-12 23:50:56 +00:00
parent 398e94b5d3
commit 3383b43a75
6 changed files with 170 additions and 2 deletions

View File

@@ -340,6 +340,97 @@ impl RpcHandler {
}))
}
/// Resolve the identity ID from params, falling back to the default identity.
async fn resolve_identity_id(&self, params: &serde_json::Value) -> Result<String> {
if let Some(id) = params.get("id").and_then(|v| v.as_str()) {
return Ok(id.to_string());
}
let manager = IdentityManager::new(&self.config.data_dir).await?;
let (records, default_id) = manager.list().await?;
// Prefer the default identity
if let Some(default_id) = default_id {
return Ok(default_id);
}
// Fall back to first identity with a Nostr key, or just the first identity
records.iter()
.find(|i| i.nostr_pubkey.is_some())
.or(records.first())
.map(|i| i.id.clone())
.ok_or_else(|| anyhow::anyhow!("No identity found"))
}
/// NIP-04 encrypt plaintext for a peer.
pub(super) async fn handle_identity_nostr_encrypt_nip04(
&self,
params: Option<serde_json::Value>,
) -> Result<serde_json::Value> {
let params = params.unwrap_or_default();
let id = self.resolve_identity_id(&params).await?;
let pubkey = params.get("pubkey").and_then(|v| v.as_str())
.ok_or_else(|| anyhow::anyhow!("Missing required parameter: pubkey"))?;
let plaintext = params.get("plaintext").and_then(|v| v.as_str())
.ok_or_else(|| anyhow::anyhow!("Missing required parameter: plaintext"))?;
let manager = IdentityManager::new(&self.config.data_dir).await?;
let ciphertext = manager.nostr_encrypt_nip04(&id, pubkey, plaintext).await?;
Ok(serde_json::json!({ "ciphertext": ciphertext }))
}
/// NIP-04 decrypt ciphertext from a peer.
pub(super) async fn handle_identity_nostr_decrypt_nip04(
&self,
params: Option<serde_json::Value>,
) -> Result<serde_json::Value> {
let params = params.unwrap_or_default();
let id = self.resolve_identity_id(&params).await?;
let pubkey = params.get("pubkey").and_then(|v| v.as_str())
.ok_or_else(|| anyhow::anyhow!("Missing required parameter: pubkey"))?;
let ciphertext = params.get("ciphertext").and_then(|v| v.as_str())
.ok_or_else(|| anyhow::anyhow!("Missing required parameter: ciphertext"))?;
let manager = IdentityManager::new(&self.config.data_dir).await?;
let plaintext = manager.nostr_decrypt_nip04(&id, pubkey, ciphertext).await?;
Ok(serde_json::json!({ "plaintext": plaintext }))
}
/// NIP-44 encrypt plaintext for a peer.
pub(super) async fn handle_identity_nostr_encrypt_nip44(
&self,
params: Option<serde_json::Value>,
) -> Result<serde_json::Value> {
let params = params.unwrap_or_default();
let id = self.resolve_identity_id(&params).await?;
let pubkey = params.get("pubkey").and_then(|v| v.as_str())
.ok_or_else(|| anyhow::anyhow!("Missing required parameter: pubkey"))?;
let plaintext = params.get("plaintext").and_then(|v| v.as_str())
.ok_or_else(|| anyhow::anyhow!("Missing required parameter: plaintext"))?;
let manager = IdentityManager::new(&self.config.data_dir).await?;
let ciphertext = manager.nostr_encrypt_nip44(&id, pubkey, plaintext).await?;
Ok(serde_json::json!({ "ciphertext": ciphertext }))
}
/// NIP-44 decrypt ciphertext from a peer.
pub(super) async fn handle_identity_nostr_decrypt_nip44(
&self,
params: Option<serde_json::Value>,
) -> Result<serde_json::Value> {
let params = params.unwrap_or_default();
let id = self.resolve_identity_id(&params).await?;
let pubkey = params.get("pubkey").and_then(|v| v.as_str())
.ok_or_else(|| anyhow::anyhow!("Missing required parameter: pubkey"))?;
let ciphertext = params.get("ciphertext").and_then(|v| v.as_str())
.ok_or_else(|| anyhow::anyhow!("Missing required parameter: ciphertext"))?;
let manager = IdentityManager::new(&self.config.data_dir).await?;
let plaintext = manager.nostr_decrypt_nip44(&id, pubkey, ciphertext).await?;
Ok(serde_json::json!({ "plaintext": plaintext }))
}
/// Resolve a remote peer's DID Document over Tor.
/// Queries the peer's /rpc/ endpoint for identity.resolve-did.
pub(super) async fn handle_identity_resolve_remote_did(

View File

@@ -337,6 +337,10 @@ impl RpcHandler {
"identity.verify-did-document" => self.handle_identity_verify_did_document(params).await,
"identity.create-nostr-key" => self.handle_identity_create_nostr_key(params).await,
"identity.nostr-sign" => self.handle_identity_nostr_sign(params).await,
"identity.nostr-encrypt-nip04" => self.handle_identity_nostr_encrypt_nip04(params).await,
"identity.nostr-decrypt-nip04" => self.handle_identity_nostr_decrypt_nip04(params).await,
"identity.nostr-encrypt-nip44" => self.handle_identity_nostr_encrypt_nip44(params).await,
"identity.nostr-decrypt-nip44" => self.handle_identity_nostr_decrypt_nip44(params).await,
// Bitcoin domain names (NIP-05)
"identity.register-name" => self.handle_identity_register_name(params).await,