Local *.dig name resolution for the DIG Network. dig-dns is a standalone OS service that
lets any browser open http://<storeId>.dig/<path> on the machine: it resolves the store's
latest chain-anchored root and serves its resources (SPA bundle — /index.html + assets),
fetching content from a dig-node over the node's public JSON-RPC read surface. dig-dns
is a client of the node, exactly as digstore is.
Reliability-first: two independent resolution paths — an OS split-DNS responder AND a PAC
proxy the browser can be pointed at — plus a doctor diagnostic, so a .dig URL still loads
when one path is blocked (e.g. a browser forcing DNS-over-HTTPS).
SPEC.md is the normative contract. dig-dns registers itself as an OS service via
dig-dns install — service id net.dignetwork.dig-dns, Windows display name "DIG NETWORK:
DNS", with a clean-reinstall (stop → delete → recreate) so a re-run never hits CreateService 1073 (SPEC §13). dig-installer invokes dig-dns install and arranges the OS split-DNS +
loopback alias + browser PAC policy around it.
http://<label>.dig/<path>
│ <label> = lowercase RFC 4648 base32 (no padding, 52 chars) of the 32-byte store id
▼
127.0.0.5 (Path A: DNS responder answers *.dig → A 127.0.0.5; Path B: browser proxies via PAC)
▼
HTTP gateway on 127.0.0.5:80 → decode label → store id (64-hex)
│ path → resource_key ("/" ⇒ index.html; SPA catch-all)
│ retrieval_key = SHA-256("urn:dig:chia:<store_id>/<key>")
▼
dig-node (dig.local → localhost:9778 → rpc.dig.net)
dig.getAnchoredRoot → latest root (a <root>.<store>.dig host pins that exact root)
dig.getContent → ciphertext + inclusion proof + chunk_lens
▼
verify (merkle inclusion vs the anchored root) → decrypt (AES-256-GCM-SIV) → serve plaintext
The store id is 64 hex characters — too long for a 63-char DNS label — so the .dig label is
its base32 form. See SPEC.md §2 for the codec and §8 for the read-crypto.
Host forms. <store>.dig serves the store's LATEST anchored root; <root>.<store>.dig
(two base32 labels) pins that EXACT root — a capsule (SPEC §2.1).
A browser can bypass the OS resolver (DNS-over-HTTPS, its own built-in resolver), so dig-dns
offers two independent paths; either alone makes a .dig URL load:
- Path A — OS split-DNS. The installer points the OS resolver for
.digat the responder (127.0.0.5:53), which answersA 127.0.0.5; the browser then makes an ordinary origin-form request to the gateway. Carries traffic whenever the browser honours the OS resolver. - Path B — PAC proxy. The browser is pointed at the PAC (
/.dig/proxy.pac, or a file the installer writes) that routes*.digthrough the gateway as an HTTP proxy (absolute-form), needing no DNS at all. Carries traffic when the browser bypasses OS DNS (forced DoH) — the reliable fallback.
Run dig-dns doctor to see which path(s) are live.
The runtime only binds sockets + answers; the installer (elevated, idempotent, reversible)
wires the OS. It never edits /etc/hosts, never URL-rewrites, never intercepts TLS.
- macOS —
127.0.0.5alias onlo0(boot-persistent);/etc/resolver/dig→127.0.0.5; a LaunchDaemon runsdig-dns serve; a Chrome managed pref points at the PAC. - Ubuntu/Linux — a systemd unit runs
dig-dns serveas a dedicated user withCAP_NET_BIND_SERVICE; split-DNS via the detected resolv.conf owner (systemd-resolved~digdrop-in / NetworkManager-dnsmasq); Chrome/Chromium policy JSON points at the PAC. (127.0.0.0/8is already up.) - Windows — a Windows Service runs
dig-dns serve; an NRPT rule (Add-DnsClientNrptRule -Namespace .dig -NameServers 127.0.0.5) routes.dig; Chrome + Edge HKLM policy points at the PAC. (127.0.0.0/8loopback answers already.)
Binding :80 can fail (e.g. Windows http.sys, or another server). dig-dns then binds the
deterministic fallback 127.0.0.5:8053, logs it loudly, and reports it in /.dig/health
(bound_port, using_fallback). Path A (origin-form) reaches a browser's default :80, so on a
fallback you rely on Path B — and the PAC (/.dig/proxy.pac, dig-dns pac) always advertises
the ACTUAL bound port. dig-dns never scans random ports.
http://<label>.dig/ is a plain-HTTP origin (no TLS interception — by design). Browsers do
NOT treat a http://…dig origin as a secure context
(only https:// and http://localhost are), so a .dig page cannot use secure-context-gated
web APIs: Service Workers, crypto.subtle (Web Crypto), navigator.geolocation, camera/mic,
etc. A .dig SPA must not depend on those. (This is a browser policy, not a dig-dns limitation;
dig-dns never terminates TLS on loopback.)
# Base32 label codec
dig-dns label encode <64-hex-store-id> [--json] # -> <52-char-base32>.dig
dig-dns label decode <label|host> [--json] # -> 64-hex store id
# Config (defaults + environment overrides)
dig-dns config [--json]
# Run the service (HTTP gateway + DNS responder) until Ctrl-C
dig-dns serve [--node <URL>]
# One-shot: resolve a single .dig resource through the pipeline and print it
dig-dns fetch <host|url> [path] [--json]
# Diagnose both paths (exit non-zero if a .dig URL cannot load)
dig-dns doctor [--json]
# Generate the PAC file (uses the actual bound port: --port, else the running gateway, else config)
dig-dns pac [--port <PORT>] [--json]digd — the short alias. Every install also ships digd, a first-class alias binary for
dig-dns: digd <args> behaves identically to dig-dns <args> (the same subcommands — including
the service verbs install/uninstall/start/stop/status/serve — flags, --json, and
help). It is a real binary, not a shell alias, so digd doctor, digd install, etc. all work. Each
binary reports its own name in --help/--version. (This mirrors digs for digstore.)
All settings have defaults and are environment-overridable (SPEC.md §7):
| Setting | Env var | Default |
|---|---|---|
loopback bind IP (must be 127.0.0.0/8) |
DIG_DNS_IP |
127.0.0.5 |
| DNS port (UDP+TCP) | DIG_DNS_DNS_PORT |
53 |
| HTTP port | DIG_DNS_HTTP_PORT |
80 |
| HTTP fallback port | DIG_DNS_HTTP_FALLBACK_PORT |
8053 |
| browsable TLD | DIG_DNS_TLD |
dig |
| DNS answer TTL (s) | DIG_DNS_TTL |
2 |
| dig-node endpoint override | DIG_NODE_URL (or --node) |
ladder |
encrypted upstream resolution (SPEC.md §6.4) |
DIG_DNS_SECURE_UPSTREAM |
on |
The rpc.dig.net lookup — the ONE public name dig-dns ever resolves — goes through an
encrypted Mullvad DoH → Mullvad DoT → Quad9 DoT chain by default, falling back to the OS
resolver only if all three are unreachable. Nothing else changes: dig.local/localhost/the
.dig responder are untouched. Set DIG_DNS_SECURE_UPSTREAM=off to use the OS resolver
unconditionally, as before this feature shipped.
# Example: run unprivileged on 127.0.0.1 with a custom TLD + node
DIG_DNS_IP=127.0.0.1 DIG_DNS_HTTP_PORT=8080 DIG_DNS_DNS_PORT=5353 DIG_DNS_TLD=web3 \
dig-dns serve --node http://localhost:9778dig-dns doctor reports one line per check (id in brackets). Fixes:
doctor check |
Meaning when it FAILs/WARNs | Fix |
|---|---|---|
loopback_ip |
the bind IP is not up locally | installer aliases 127.0.0.5 (ifconfig lo0 alias on macOS; 127/8 is usually up on Linux/Windows) |
dns_direct |
the DNS responder does not answer on :53 |
start dig-dns serve (needs privilege for :53); check DIG_DNS_IP/DIG_DNS_DNS_PORT |
os_routing |
the OS does not send .dig to the responder (Path A off) |
installer configures split-DNS (/etc/resolver/dig, NRPT, systemd-resolved) — or rely on Path B |
gateway_port |
no gateway on :80/:8053 |
start dig-dns serve; if it bound the fallback, use the PAC (advertises the bound port) |
node_reachable |
gateway is up but no dig-node reachable → content 502s | start your dig-node (localhost:9778) or set --node/DIG_NODE_URL |
browser_doh |
browser may auto-enable DoH, bypassing Path A | point the browser at the PAC (Path B) — the installer sets the managed policy |
port80_holder |
another process holds :80 |
free :80, or keep the :8053 fallback + PAC |
secure_upstream |
every encrypted DNS tier failed for the rpc.dig.net lookup (degraded — the OS resolver answered instead) |
check whether this network blocks DoH/DoT (common on some corporate/hotel networks) |
A .dig URL loads iff loopback_ip is up AND at least one of Path A (os_routing) or Path B
(gateway_port) is live. doctor exits non-zero otherwise.
scripts/acceptance-unix.sh— macOS/Linux: startsdig-dns serve(unprivileged high ports), runsdoctor, the/.dig/control endpoints, the open-proxy403+ bad-host404checks, and the DNS-direct probe; optional content + pinned-vs-latest checks with a live node.scripts/acceptance-windows.ps1— the Windows (PowerShell) equivalent.
cargo build
cargo test
cargo clippy --all-targets -- -D warnings
cargo fmt --all -- --check
cargo llvm-cov --fail-under-lines 80 --summary-onlyLoopback-only binds (127.0.0.0/8), never 0.0.0.0. Never an open proxy (only .dig
authorities are served; CONNECT is refused). No TLS interception. No hosts-file / OS-resolver
edits at runtime (those are the installer's job). Content is verified against the chain-anchored
root before it is decrypted and served (fail-closed). See SPEC.md §5.
GPL-2.0-only.