feat: implement did:dht creation and resolution via Mainline DHT
DHT-02: did:dht creation - network/did_dht.rs: z-base-32 encoding, DNS packet encoding, BEP-44 mutable item publication via mainline crate - identity.create-dht-did RPC endpoint - dht_did field added to IdentityRecord - get_signing_key() exposed on IdentityManager DHT-03: did:dht resolution - did_dht::resolve() queries DHT, parses DNS → DID Document - DhtDidCache with 1-hour TTL - identity.resolve-dht-did, identity.refresh-dht-did, identity.dht-status New dependencies: mainline 2, zbase32 0.1, simple-dns 0.7 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
|
||||
use super::RpcHandler;
|
||||
use crate::identity_manager::{IdentityManager, IdentityPurpose};
|
||||
use crate::network::did_dht;
|
||||
use anyhow::{Context, Result};
|
||||
use nostr_sdk::ToBech32;
|
||||
|
||||
@@ -571,4 +572,113 @@ impl RpcHandler {
|
||||
"cached": true,
|
||||
}))
|
||||
}
|
||||
|
||||
/// identity.create-dht-did — Publish an identity's DID to the Mainline DHT.
|
||||
pub(super) async fn handle_identity_create_dht_did(
|
||||
&self,
|
||||
params: Option<serde_json::Value>,
|
||||
) -> Result<serde_json::Value> {
|
||||
let params = params.ok_or_else(|| anyhow::anyhow!("Missing params"))?;
|
||||
let identity_id = params
|
||||
.get("identity_id")
|
||||
.and_then(|v| v.as_str())
|
||||
.ok_or_else(|| anyhow::anyhow!("Missing identity_id"))?;
|
||||
validate_identity_id(identity_id)?;
|
||||
|
||||
let manager = IdentityManager::new(&self.config.data_dir).await?;
|
||||
let signing_key = manager.get_signing_key(identity_id).await?;
|
||||
|
||||
let dht_did = did_dht::create_and_publish(&signing_key, &[]).await?;
|
||||
|
||||
// Save the dht_did back to the identity record
|
||||
did_dht::save_dht_did(&self.config.data_dir, identity_id, &dht_did).await?;
|
||||
|
||||
Ok(serde_json::json!({
|
||||
"dht_did": dht_did,
|
||||
"published": true,
|
||||
}))
|
||||
}
|
||||
|
||||
/// identity.resolve-dht-did — Resolve a did:dht from the DHT.
|
||||
pub(super) async fn handle_identity_resolve_dht_did(
|
||||
&self,
|
||||
params: Option<serde_json::Value>,
|
||||
) -> Result<serde_json::Value> {
|
||||
let params = params.ok_or_else(|| anyhow::anyhow!("Missing params"))?;
|
||||
let did = params
|
||||
.get("did")
|
||||
.and_then(|v| v.as_str())
|
||||
.ok_or_else(|| anyhow::anyhow!("Missing did"))?;
|
||||
|
||||
if !did.starts_with("did:dht:") {
|
||||
anyhow::bail!("Not a did:dht identifier");
|
||||
}
|
||||
|
||||
let doc = did_dht::resolve(did, None).await?;
|
||||
|
||||
Ok(serde_json::json!({
|
||||
"did": did,
|
||||
"document": doc,
|
||||
}))
|
||||
}
|
||||
|
||||
/// identity.refresh-dht-did — Re-publish an identity's did:dht to keep it alive in the DHT.
|
||||
pub(super) async fn handle_identity_refresh_dht_did(
|
||||
&self,
|
||||
params: Option<serde_json::Value>,
|
||||
) -> Result<serde_json::Value> {
|
||||
let params = params.ok_or_else(|| anyhow::anyhow!("Missing params"))?;
|
||||
let identity_id = params
|
||||
.get("identity_id")
|
||||
.and_then(|v| v.as_str())
|
||||
.ok_or_else(|| anyhow::anyhow!("Missing identity_id"))?;
|
||||
validate_identity_id(identity_id)?;
|
||||
|
||||
let manager = IdentityManager::new(&self.config.data_dir).await?;
|
||||
let record = manager.get(identity_id).await?;
|
||||
|
||||
if record.dht_did.is_none() {
|
||||
anyhow::bail!("Identity has no did:dht — create one first with identity.create-dht-did");
|
||||
}
|
||||
|
||||
let signing_key = manager.get_signing_key(identity_id).await?;
|
||||
let dht_did = did_dht::create_and_publish(&signing_key, &[]).await?;
|
||||
|
||||
Ok(serde_json::json!({
|
||||
"dht_did": dht_did,
|
||||
"refreshed": true,
|
||||
}))
|
||||
}
|
||||
|
||||
/// identity.dht-status — Check if an identity's did:dht is published and resolvable.
|
||||
pub(super) async fn handle_identity_dht_status(
|
||||
&self,
|
||||
params: Option<serde_json::Value>,
|
||||
) -> Result<serde_json::Value> {
|
||||
let params = params.ok_or_else(|| anyhow::anyhow!("Missing params"))?;
|
||||
let identity_id = params
|
||||
.get("identity_id")
|
||||
.and_then(|v| v.as_str())
|
||||
.ok_or_else(|| anyhow::anyhow!("Missing identity_id"))?;
|
||||
validate_identity_id(identity_id)?;
|
||||
|
||||
let manager = IdentityManager::new(&self.config.data_dir).await?;
|
||||
let record = manager.get(identity_id).await?;
|
||||
|
||||
let (published, resolvable) = match &record.dht_did {
|
||||
Some(dht_did) => {
|
||||
let resolvable = did_dht::resolve(dht_did, None).await.is_ok();
|
||||
(true, resolvable)
|
||||
}
|
||||
None => (false, false),
|
||||
};
|
||||
|
||||
Ok(serde_json::json!({
|
||||
"identity_id": identity_id,
|
||||
"did_key": record.did,
|
||||
"dht_did": record.dht_did,
|
||||
"published": published,
|
||||
"resolvable": resolvable,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -430,6 +430,10 @@ impl RpcHandler {
|
||||
"identity.resolve-did" => self.handle_identity_resolve_did(params).await,
|
||||
"identity.resolve-remote-did" => self.handle_identity_resolve_remote_did(params).await,
|
||||
"identity.verify-did-document" => self.handle_identity_verify_did_document(params).await,
|
||||
"identity.create-dht-did" => self.handle_identity_create_dht_did(params).await,
|
||||
"identity.resolve-dht-did" => self.handle_identity_resolve_dht_did(params).await,
|
||||
"identity.refresh-dht-did" => self.handle_identity_refresh_dht_did(params).await,
|
||||
"identity.dht-status" => self.handle_identity_dht_status(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,
|
||||
|
||||
Reference in New Issue
Block a user