Layer 7 · The DIG Node peer network
Canonical references:
dig-gossip(the peer transport, discovery, and gossip layer — TLS-WebSocket peers,peer_id = SHA-256(TLS SPKI DER), address manager, introducer + peer-exchange),dig-relay(the rendezvous / hole-punch coordinator / circuit relay serving theRelayMessagewire),dig-nat(theconnect(peer)NAT-traversal ladder),dig-dht(the Kademlia DHT with provider records that locate which peers hold content), anddig-constants(DIG_RELAY_URL). This layer is how DIG Nodes find and reach each other; the dig RPC is what they speak once connected.
This is the normative anchor for DIG Node ↔ Node communication. Every peer-facing crate — dig-nat, dig-relay, dig-gossip, dig-dht, and dig-node — conforms to the contracts below. Where a statement is a wire contract it is fixed at the field/byte level; a conforming reimplementation must reproduce it exactly.
The thesis: authenticated peers, direct when possible, relay only as a last resort
- Every peer link is mutually authenticated. All node↔node traffic runs over mutual-TLS (mTLS). A peer's identity is the hash of its TLS public key; there is no unauthenticated peer channel.
- Direct paths are preferred. A node tries, in a fixed order, to open a direct connection (already reachable → UPnP → NAT-PMP → PCP → relay-coordinated hole-punch) before it ever relays. When any direct path succeeds, no relay is used.
- The relay is the last resort, never the trust anchor.
relay.dig.netbridges bytes only when every direct strategy fails. It forwards opaque, end-to-end-authenticated payloads by peer id and can read none of them.
The relay has four distinct roles
relay.dig.net is not a single service — it fills four separate roles, three of which are low-bandwidth signalling. Keeping them distinct matters because only the fourth carries a peer's data stream:
| # | Role | Bandwidth | What the relay does | Where |
|---|---|---|---|---|
| 1 | STUN server | tiny | Answers a Binding request so a node learns its public reflexive IP:port (RFC 5389). | §3 |
| 2 | Introducer | small | Registers a node's presence and returns known-peer lists for rendezvous/discovery. | §4a |
| 3 | Hole-punch signalling | small | Brokers a hole punch between two NAT'd peers — relays their candidate-address exchange and coordinates the simultaneous-open timing — after which the peers connect directly. The relay carries only the coordination messages, never the data. | §5 |
| 4 | Relayed (TURN-like) transport | full | Proxies all of a peer connection's data when no direct path exists. High-bandwidth, last resort only. | §6 |
Roles 1–3 are how the relay helps two peers connect directly; role 4 is the only one where the peer's stream flows through the relay. A node always prefers role 3 (broker a direct link) over role 4 (proxy the whole stream), because role 3 costs the relay almost nothing while role 4 consumes real bandwidth. Whatever the tier, once connected the link is mTLS with peer_id = SHA-256(SPKI) (§1) — the relay is never the trust anchor.
0 · The two RPC tiers — mTLS peer/control vs anonymous public read
A DIG Node's RPC is served on two distinct tiers with two distinct authentication models, because two different callers need it: other nodes (which present a client certificate) and browsers/agents (which cannot present a client certificate but must still be able to READ content). One dig-node process serves both; the tier a method lives on is a frozen contract every serve-layer and every RPC client conforms to.
| PEER / CONTROL tier | PUBLIC READ tier | |
|---|---|---|
| Purpose | node↔node: discovery, DHT, PEX, availability-for-sync, PUSH/WRITE, config/control | browser/agent content retrieval only |
| Auth | mutual TLS — client cert REQUIRED; peer_id = SHA-256(TLS SPKI DER) (§1); write routes additionally per-request BLS-signed (§21.9) | none — anonymous, no client cert, no bearer for reads |
| Transport | dig-nat mTLS mux (peer RPC + DHT + PEX streams) on the P2P port DIG_PEER_PORT (default 9444); the §21 authenticated HTTPS write routes | plain HTTPS JSON-RPC, CORS-enabled, on the public read listener (network-wide: rpc.dig.net) |
| Browser-reachable | No — an anonymous caller cannot open it | Yes — this is the browser/agent read path |
| Mutation / identity | write, peer, config, control all live here | read-only — no mutation, no peer/config method reachable |
| Integrity trust | mTLS peer identity | client-side self-verification (merkle inclusion proof + chain-anchored root pin) — the server is untrusted (Verification & provenance) |
| Miss behavior | method-specific error | decoy-on-miss, never 404 (blind host) |
The boundary invariant
Two rules, jointly, are the contract:
- No peer / write / config / control method is reachable without mTLS. Every method that mutates state, exchanges peer information, moves the DHT/PEX, or reconfigures the node is served only on the mTLS peer/control tier. On the anonymous public-read listener these methods do not exist — an anonymous caller that names one receives
-32601(method not found), the same as any unimplemented method. A node MUST NOT honor a write/peer/control method on a connection that did not complete the mTLS handshake. - Content read requires no mTLS. The read methods —
dig.getContentand the read side ofdig.getProof/dig.getCapsule/dig.getManifest/dig.getMetadata, plus the §21 GET routes (content/proof/roots/descriptor) — are served anonymously so a browser works. They are read-only: they never mutate, never reveal peer/config state, and never accept a write.
Which tier every method sits on is enumerated in §7a.
Why this is secure
Splitting authentication by tier does not weaken the network, because the public tier is read-only and client-verified:
- The read server is already untrusted. Every read is verified client-side against the CHIP-0035 chain-anchored root (
dig.getContentstreaming contract) — a merkle inclusion proof under the on-chain root plus per-chunk AES-256-GCM-SIV authentication. A malicious or anonymous read server can only serve bytes that either verify (correct content) or fail the proof/tag (detected, discarded). Anonymity of the reader changes nothing: the content is public ciphertext, keyed byretrieval_key = SHA-256(URN), and the AES key is derived client-side and never sent. There is nothing to authenticate a reader for. - A miss is indistinguishable. A content miss returns a deterministic decoy, byte-shaped like a hit, never a 404 — so anonymous access leaks no presence/absence oracle.
- Everything that could harm the network stays mTLS-gated. Advancing a root (PUSH/WRITE), announcing a provider record, injecting peers, or reconfiguring the node all require the client certificate (and, for writes, a per-request BLS signature). An attacker with no certificate can read public ciphertext and nothing more — exactly the capability a browser needs and no more.
- The two never blur. Because the anonymous listener simply does not implement the peer/write/control methods, there is no "forgot to check auth" path: an unauthenticated write is not a rejected request, it is a nonexistent method.
How one process serves both
A dig-node runs two listeners:
- The mTLS peer/control listener binds the P2P port (
DIG_PEER_PORT, default 9444) with rustlsCERT_REQUIRED; it carries the dig-nat mux (peer RPC, DHT, PEX). The §21 authenticated write/push routes (transport & push) are the control tier's HTTPS face (per-request BLS auth, middleware order auth-THEN-rate-limit). - The public read listener serves the anonymous JSON-RPC read subset over plain HTTPS with
Access-Control-Allow-Origin: *, no credentials,OPTIONS→ 204. On the public network this listener isrpc.dig.net. A dig-node run purely for a local consumer keeps this listener on loopback (127.0.0.1, default read port 9778); a node that wants to serve the wider network exposes the read listener on its public interface (TLS-fronted, asrpc.dig.netis).
The recommended endpoint split (design-first call): the public read tier is the same JSON-RPC endpoint shape as the network profile — one POST endpoint speaking JSON-RPC 2.0 — but the anonymous serve layer answers only the read subset; the peer/write/control methods return -32601 there. This reuses the existing node-profile / dig.methods gating (an agent already gates on dig.methods rather than assuming one uniform surface) instead of inventing a parallel protocol, and it is exactly what a browser already speaks to rpc.dig.net. The peer/control tier is not a JSON-RPC-over-HTTPS surface at all — it is the dig-nat mTLS mux — so it cannot be reached by an anonymous HTTP client even by accident.
rpc.dig.net is dual-mode: the public read tier plus an mTLS gatewayrpc.dig.net (the dig RPC network profile) is the anonymous, CORS-enabled, decoy-on-miss, client-verified public read tier — the tier a browser has always used, and this section states the invariant that keeps the peer/write/control surface off it. It is also the public mTLS gateway: node-class clients, and anonymous readers holding an ephemeral certificate, reach the very same hostname over the mTLS peer/control transport instead. §0a is the two-front contract.
Browser specifics — fetch + verify without mTLS
A browser (or an agent with no client certificate) reads content like this, needing no mTLS at any step:
- CORS. The public read listener answers a cross-origin
POSTwithAccess-Control-Allow-Origin: *and no credentials (Access-Control-Allow-Credentialsis never set — the content is public ciphertext, there is nothing to send credentials for), and answers theOPTIONSpreflight with204. So a page on any origin canfetch()the read endpoint directly. - Read.
POSTJSON-RPCdig.getContent(windowed byoffset/length) to the public read endpoint; reassemble the ciphertext bytotal_length(streaming contract). No client certificate, no bearer token. - Verify — the server is untrusted. Verify the first window's
inclusion_proofagainst the caller-supplied chain-anchored root (the CHIP-0035 singleton's current on-chainmetadata.root_hash, resolved independently of the serving node), then split bychunk_lensand AES-256-GCM-SIV-open each chunk with the client-derived key. A wrong/forged byte fails the proof or the authentication tag and is rejected. This is the same read-crypto every DIG read client runs; the anonymity of the transport does not relax it.
Because the read tier is CORS-* + anonymous, the exact same fetch works from a web page, a service worker, the extension, the SDK, or a headless agent — none of which can present a client certificate.