feat(messaging,dwn,mesh): route peer messaging + DWN sync + blob fetch via FIPS first

Migrates the remaining Tor-direct peer call sites to PeerRequest so
FIPS is the default when the peer is federated and running the daemon:

- node_message::send_to_peer / check_peer_reachable: gain a
  fips_npub parameter. Error messages updated to reference both
  transports.
- Callers (api/rpc/network.rs, api/rpc/peers.rs, server health
  loop): look up fips_npub from federation storage by onion and
  pass it.
- mesh::send_typed_wire_via_federation: the spawned background POST
  for the /archipelago/mesh-typed endpoint now uses PeerRequest with
  federation-resolved fips_npub. Signature domain unchanged.
- api/rpc/mesh/typed_messages.rs fetch_blob_from_peer: blob URL
  rebuilt as (base_url, path_with_query) so PeerRequest can append
  the query string after swapping the host. Cap/exp/peer
  parameters are still signed over the content ref itself, so
  transport choice is invisible to the signature.
- network/dwn_sync.rs sync_with_peers: per-peer fips_npub lookup
  before sync_single_peer; health/pull/push each dial through
  PeerRequest, so any DWN peer known to federation gets FIPS.

Left Tor-only on purpose:
- api/rpc/identity/handlers.rs handle_identity_resolve_peer_onion —
  resolving TO a DID, no anchor yet.
- content.browse / preview calls to non-federated peers fall
  through to Tor naturally inside PeerRequest (no fips_npub → skip
  FIPS branch).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Dorian
2026-04-19 01:36:04 -04:00
co-authored by Claude Opus 4.7
parent ba825c13a5
commit dbd19006f2
7 changed files with 112 additions and 137 deletions
+21 -31
View File
@@ -827,16 +827,10 @@ impl MeshService {
use base64::{engine::general_purpose::STANDARD as BASE64, Engine as _};
use ed25519_dalek::Signer;
let host = if peer_onion.ends_with(".onion") {
peer_onion.to_string()
} else {
format!("{}.onion", peer_onion.trim_end_matches('/'))
};
let url = format!("http://{}/archipelago/mesh-typed", host);
// Sign the raw wire bytes so the receiver can attribute the envelope
// to our pubkey even when it arrives over federation/Tor rather than
// the radio. Signature covers the wire only — the receiver re-hashes.
// to our pubkey even when it arrives over federation/FIPS/Tor rather
// than the radio. Signature covers the wire only — the receiver
// re-hashes.
let signature = hex::encode(self.signing_key.sign(&wire).to_bytes());
let wire_b64 = BASE64.encode(&wire);
let body = serde_json::json!({
@@ -857,33 +851,29 @@ impl MeshService {
)
.await;
// Fire the Tor POST in the background. Failures are logged but do
// not propagate — the caller has already been handed the Sent
// Fire the send in the background. FIPS is preferred when the peer
// is federated and running fips; Tor is the fallback. Failures are
// logged but do not propagate — caller already has the Sent
// MeshMessage and the UI's delivery indicator tracks the receipt.
let peer_onion_owned = peer_onion.to_string();
let data_dir_owned = self.data_dir.clone();
tokio::spawn(async move {
let proxy = match reqwest::Proxy::all(crate::constants::TOR_SOCKS_PROXY) {
Ok(p) => p,
Err(e) => {
warn!(contact_id, "Invalid Tor proxy: {}", e);
return;
let fips_npub =
crate::federation::fips_npub_for_onion(&data_dir_owned, &peer_onion_owned).await;
let req = crate::fips::dial::PeerRequest::new(
fips_npub.as_deref(),
&peer_onion_owned,
"/archipelago/mesh-typed",
)
.timeout(std::time::Duration::from_secs(120));
match req.send_json(&body).await {
Ok((resp, transport)) if resp.status().is_success() => {
tracing::debug!(contact_id, transport = %transport, "Federation envelope delivered");
}
};
let client = match reqwest::Client::builder()
.proxy(proxy)
.timeout(std::time::Duration::from_secs(120))
.build()
{
Ok(c) => c,
Err(e) => {
warn!(contact_id, "HTTP client build failed: {}", e);
return;
}
};
match client.post(&url).json(&body).send().await {
Ok(resp) if resp.status().is_success() => {}
Ok(resp) => warn!(
Ok((resp, transport)) => warn!(
contact_id,
status = %resp.status(),
transport = %transport,
"Peer rejected federation-routed envelope"
),
Err(e) => warn!(contact_id, "Federation POST failed: {}", e),