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 the public read tierToday rpc.dig.net (the dig RPC network profile) is exactly this anonymous, CORS-enabled, decoy-on-miss, client-verified public read tier. This section names the tier the browser has always used and states the invariant that keeps the peer/write/control surface off it.
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.
1 · Peer identity + mTLS
A peer is identified by the SHA-256 of the DER-encoded SubjectPublicKeyInfo of the certificate it presents in the TLS handshake:
peer_id = SHA-256( SubjectPublicKeyInfo DER ) // 32 bytes
peer_idis aBytes32— 32 raw bytes, rendered as 64 lower-case hex on any text surface (dig-gossiptypes/peer.rs:pub type PeerId = Bytes32,peer_id_from_tls_spki_der).- The hashed input is the full
SubjectPublicKeyInfoASN.1 sequence (algorithm identifier + subject public-key bit string) lifted from the peer's X.509 leaf certificate — not the bare public-key bit string. Both sides recover the other'speer_idfrom the certificate exchanged during the handshake, so identity is bound to key material: impersonation requires the private key.
mTLS is mandatory
All peer-to-peer connections use mutual TLS over a WebSocket (wss://). This is a hard requirement — plaintext and server-only TLS are never accepted for a peer link.
- Both endpoints present a certificate. The dialing peer presents its cert to the listener; the listener presents its cert to the dialer. Each derives the other's
peer_idfrom the presented cert. - Self-signed node certificates are expected. Peer identity is verified by the
peer_idhash, not by a certificate authority — the CA chain is not the trust root here, the key hash is. A node generates its certificate on first run and reuses it thereafter. - The listener requires a client certificate (
CERT_REQUIRED). A peer that presents no certificate, or whose TLS handshake fails, is dropped — there is no fallback to a weaker transport. - After the TLS handshake, peers exchange a
Handshakecarrying thenetwork_id(the network genesis challenge as lower-case hex), the protocol version, the node's declared listen port, and its node type + capabilities. Anetwork_idmismatch, or a protocol version below the minimum-compatible floor, ends the connection. TheHandshakeis the Chia-streamable message (big-endian) — this layer speaks the Chia peer protocol, not a bespoke framing. - Unauthenticated peer traffic is rejected. A message received before a completed mTLS handshake +
Handshakeexchange is not processed.
A node's link to the relay is a standard server-authenticated wss:// connection (the relay presents a TLS certificate; the node does not present one to the relay). Peer↔peer identity is never delegated to the relay — end-to-end payloads carried over the relay remain authenticated by the peer protocol itself, so a relay cannot forge a peer.
Identity rotation
A node MAY rotate its network identity (regenerate its certificate, hence its peer_id) on an interval to reduce long-term linkability. Rotation is a network-layer concern only — it is independent of any consensus/validator identity and does not disturb address-book entries, which are keyed by IP:port, not by peer_id.
2 · Connection establishment — the NAT-traversal ladder
A node reaches a peer through a single abstract operation — connect(peer) — which attempts the strategies below in order and returns the first that yields a working, mTLS-authenticated link. This ordered ladder is the contract dig-nat implements; every strategy above "relayed" produces a direct peer link (no relay in the data path).
| # | Strategy | What it is | Relay's role | Result |
|---|---|---|---|---|
| a | DIRECT | The peer is already reachable — publicly routable, or a port is forwarded to it. Dial its advertised address. | none | Direct link |
| b | UPnP / IGD | Ask the local gateway (Internet Gateway Device) to map an external port to the node via UPnP, then advertise the mapped address. | none | Direct link |
| c | NAT-PMP | Request a port mapping from the gateway via NAT Port Mapping Protocol. | none | Direct link |
| d | PCP | Request a mapping via the Port Control Protocol (RFC 6887), NAT-PMP's successor. | none | Direct link |
| e | RELAY-COORDINATED HOLE PUNCH | Neither peer is directly reachable: the relay signals only — it relays the candidate-address exchange and coordinates a simultaneous open so both sides punch through their NATs. The data stream then flows peer-to-peer (see §5). | signalling only (low bandwidth) | Direct link |
| f | RELAYED / TURN transport | Every direct strategy failed: the relay proxies all of the connection's data as an untrusted bridge (see §6). | carries the data (high bandwidth) | Relayed link |
Rules:
- Attempt in order; stop at the first success. A node does not skip ahead to the relay while an earlier, cheaper strategy can still yield a direct path.
- Prefer hole-punch signalling (e) over full relaying (f). Both involve the relay, but they are not the same tier: in (e) the relay only brokers the introduction and the stream goes peer-to-peer, whereas in (f) the relay carries every byte. A node falls to (f) only when the hole punch of (e) fails — this saves relay bandwidth, since a brokered direct link costs the relay almost nothing. A successful (e) is a direct link (strategy result "Direct"), authenticated by the same mTLS
peer_idas every other tier. - Strategies (b)–(d) run once at startup / on address change, not per connection: they establish inbound reachability so future dials land as (a) DIRECT for peers dialing this node. A node that obtains a stable external mapping via UPnP/NAT-PMP/PCP advertises it as a candidate address so peers dial it directly.
- Candidate addresses. A node advertises the set of addresses at which it may be reachable — its configured/observed listen address, any UPnP/NAT-PMP/PCP-mapped external address, and its STUN-derived reflexive address (§3). Peers dial candidates most-direct-first and IPv6-first (§2a).
- Reflexive discovery precedes hole-punch. Before requesting a hole-punch a node learns its public reflexive
IP:portvia STUN (§3) and supplies it as theexternal_addrin the coordination exchange.