refactor: update dependencies and remove unused code

- Added new dependencies: `adler2`, `crc32fast`, `flate2`, `miniz_oxide`, and `libredox`.
- Updated existing dependencies: `tokio-rustls` to version 0.26.4 and `filetime` to version 0.2.27.
- Removed the `backup.rs` file as it is no longer needed.
- Introduced tests for configuration and credential management.
- Enhanced the `identity` module to generate W3C compliant DID documents.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Dorian
2026-03-12 00:19:30 +00:00
co-authored by Claude Opus 4.6
parent fd2a837bea
commit f07ce10b1a
347 changed files with 18703 additions and 46785 deletions
+173 -1
View File
@@ -2,7 +2,7 @@
use super::RpcHandler;
use crate::identity_manager::{IdentityManager, IdentityPurpose};
use anyhow::Result;
use anyhow::{Context, Result};
impl RpcHandler {
/// List all identities with their default status.
@@ -180,6 +180,101 @@ impl RpcHandler {
Ok(serde_json::json!({ "valid": valid }))
}
/// Resolve a DID to its W3C DID Document.
/// If no DID is provided, returns the node's own DID Document.
pub(super) async fn handle_identity_resolve_did(
&self,
params: Option<serde_json::Value>,
) -> Result<serde_json::Value> {
let params = params.unwrap_or_default();
// If a DID is provided, resolve it; otherwise use the node's DID
let pubkey_hex = if let Some(did) = params.get("did").and_then(|v| v.as_str()) {
// Extract pubkey from did:key format
let pubkey_bytes = crate::identity::pubkey_bytes_from_did_key(did)?;
hex::encode(pubkey_bytes)
} else {
// Use node's own pubkey
let (data, _) = self.state_manager.get_snapshot().await;
data.server_info.pubkey.clone()
};
let document = crate::identity::did_document_from_pubkey_hex(&pubkey_hex)?;
Ok(document)
}
/// Verify a DID Document: validate structure, check key material matches DID.
pub(super) async fn handle_identity_verify_did_document(
&self,
params: Option<serde_json::Value>,
) -> Result<serde_json::Value> {
let params = params.ok_or_else(|| anyhow::anyhow!("Missing params"))?;
let document = params
.get("document")
.ok_or_else(|| anyhow::anyhow!("Missing required parameter: document"))?;
// Validate required fields
let did = document["id"]
.as_str()
.ok_or_else(|| anyhow::anyhow!("DID Document missing 'id' field"))?;
let context = document["@context"]
.as_array()
.ok_or_else(|| anyhow::anyhow!("DID Document missing '@context' array"))?;
let has_did_context = context.iter().any(|c| c.as_str() == Some("https://www.w3.org/ns/did/v1"));
if !has_did_context {
return Ok(serde_json::json!({
"valid": false,
"errors": ["Missing required @context: https://www.w3.org/ns/did/v1"]
}));
}
let verification_methods = document["verificationMethod"]
.as_array()
.ok_or_else(|| anyhow::anyhow!("DID Document missing 'verificationMethod' array"))?;
if verification_methods.is_empty() {
return Ok(serde_json::json!({
"valid": false,
"errors": ["verificationMethod array is empty"]
}));
}
// Verify the DID matches the key material (for did:key method)
let mut errors: Vec<String> = Vec::new();
if did.starts_with("did:key:") {
match crate::identity::pubkey_bytes_from_did_key(did) {
Ok(pubkey_bytes) => {
// Check that at least one verification method has matching key
let pubkey_multibase = format!("z{}", bs58::encode(&pubkey_bytes).into_string());
let has_matching_key = verification_methods.iter().any(|vm| {
vm["publicKeyMultibase"].as_str() == Some(&pubkey_multibase)
});
if !has_matching_key {
errors.push("No verificationMethod matches the DID's public key".to_string());
}
}
Err(e) => {
errors.push(format!("Failed to extract pubkey from DID: {}", e));
}
}
}
// Check authentication is present
if document["authentication"].as_array().map_or(true, |a| a.is_empty()) {
errors.push("Missing or empty 'authentication' field".to_string());
}
Ok(serde_json::json!({
"valid": errors.is_empty(),
"did": did,
"errors": errors,
"verification_methods": verification_methods.len(),
}))
}
/// Create a Nostr keypair linked to an identity.
pub(super) async fn handle_identity_create_nostr_key(
&self,
@@ -221,4 +316,81 @@ impl RpcHandler {
"signature": signature,
}))
}
/// 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(
&self,
params: Option<serde_json::Value>,
) -> Result<serde_json::Value> {
let params = params.ok_or_else(|| anyhow::anyhow!("Missing params"))?;
let onion = params
.get("onion")
.and_then(|v| v.as_str())
.ok_or_else(|| anyhow::anyhow!("Missing required parameter: onion"))?;
// Build URL for peer's RPC endpoint over Tor
let host = if onion.ends_with(".onion") {
onion.to_string()
} else {
format!("{}.onion", onion)
};
let url = format!("http://{}/rpc/", host);
// Use SOCKS5 proxy to reach .onion address
let proxy = reqwest::Proxy::all("socks5h://127.0.0.1:9050")
.context("Failed to create Tor proxy")?;
let client = reqwest::Client::builder()
.proxy(proxy)
.timeout(std::time::Duration::from_secs(30))
.build()
.context("Failed to build HTTP client")?;
let rpc_body = serde_json::json!({
"jsonrpc": "2.0",
"id": 1,
"method": "identity.resolve-did",
"params": {}
});
let resp = client
.post(&url)
.json(&rpc_body)
.send()
.await
.context("Failed to connect to peer over Tor")?;
let body: serde_json::Value = resp
.json()
.await
.context("Failed to parse peer response")?;
// Extract the DID Document from the RPC response
let document = body
.get("result")
.ok_or_else(|| anyhow::anyhow!("Peer returned error or missing result"))?;
// Cache the resolved DID locally
let did = document["id"]
.as_str()
.unwrap_or("unknown");
let cache_dir = self.config.data_dir.join("did-cache");
tokio::fs::create_dir_all(&cache_dir).await.ok();
let cache_file = cache_dir.join(format!("{}.json", onion.replace('.', "_")));
let cache_entry = serde_json::json!({
"document": document,
"resolved_at": chrono::Utc::now().to_rfc3339(),
"onion": onion,
});
tokio::fs::write(&cache_file, serde_json::to_string_pretty(&cache_entry).unwrap_or_default())
.await
.ok();
Ok(serde_json::json!({
"document": document,
"did": did,
"resolved_from": onion,
"cached": true,
}))
}
}