dgb-wallet/ops/nginx-digibyte-x.conf
Digibyte.X 690959a983 p2p: BIP158 matching in WebAssembly (gcs.c), Number-based JS fallback; Blockbook upstream → Atomic
The daemon's wallet scan hashed every watched script against every block
filter with a BigInt SipHash: ~3 filters/s on the VPS once the gap limits
went to 200/100 (~3,300 scripts), i.e. days for a one-year window. The
matcher now lives in dgbpow.wasm next to the PoW hashes — scripts are
uploaded once per scan, each filter is one call, the set is decoded once
and each target binary-searched — ~5,500 filters/s locally, ~2,000/s on
the VPS with block fetches. The JS path was rewritten with uint32 lanes
and exact 32×32 partial products as a fallback (module missing or filter
beyond its buffers). scripts/gcs-check.ts cross-checks both against a
BigInt reference on random filters; `npm run gcs:test -w @dgb-wallet/p2p`.

NOWNodes stopped accepting the proxy's API key ("Unknown API_key" on every
endpoint), which is why the wallet showed "Blockbook HTTP 401" for every
path. The vhost mirror now proxies /blockbook/ to Atomic Wallet's public
Blockbook; ops/blockbook-upstream.sh switches the live vhost either way.
2026-10-11 23:37:28 +02:00

162 lines
8 KiB
Text

# nginx vhost for digibyte.x on the coinspectrum VPS — the LIVE layout.
# Mirror of /etc/nginx/sites-enabled/digibyte-x (synced 2026-09-09).
#
# Deploy: render the one secret, then install + reload:
# . /etc/dgb-core/nownodes.env # NOWNODES_API_KEY=…
# sed "s|@NOWNODES_API_KEY@|$NOWNODES_API_KEY|" ops/nginx-digibyte-x.conf # > /etc/nginx/sites-enabled/digibyte-x && nginx -t && systemctl reload nginx
#
# The Content-Security-Policy hash below pins the inline <base>-fix
# script in the built index.html. Recompute after any change to
# packages/web-ui/index.html: node packages/web-ui/scripts/csp-hash.mjs
#
# nginx vhost for digibyte.x on coinspectrum.
#
# Trust model: SELF-SIGNED cert (10y), pinned on-chain via the digibyte.x
# BNS `tls` record. Wallet clients resolve digibyte.x → {ip, tls}, dial
# the IP on :443 with SNI=digibyte.x, and verify by comparing the observed
# cert's DER SHA-256 to the on-chain pin. No CA, no ACME.
#
# nginx selects this cert only for SNI=digibyte.x — every other vhost on
# this box (navigate.st, silentmode.st, mail.silentmode.st, etc.) keeps
# its Let's Encrypt cert untouched.
#
# Layout:
# / → 302 to /web/ (wallet is the only thing here)
# /web/* → static bundle from /var/www/dgb-wallet/web/
# /p2p/* → reverse-proxy to dgb-daemon on 127.0.0.1:5174
# (strip /p2p prefix so daemon sees /health, /balance, etc.)
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name digibyte.x;
# Self-signed cert. Fingerprint is pinned on-chain — rotating the cert
# is a single BNS UPD, no client update.
ssl_certificate /etc/ssl/dgb-wallet/server.crt;
ssl_certificate_key /etc/ssl/dgb-wallet/server.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
# No HSTS: HSTS presupposes CA-issued trust. We use fingerprint pinning
# instead — the wallet client rejects any cert whose DER SHA-256 does
# not match the on-chain record, regardless of browser trust store.
# Belt-and-suspenders: keep this vhost strictly for digibyte.x. If
# SNI doesn't match the server_name above, nginx falls back to the
# default_server (coinspectrum), which has its own LE cert — so a
# wrong-SNI connection never sees our self-signed cert anyway.
# /web/* — static wallet bundle. Vite emits absolute URLs like
# /web/assets/index-<hash>.js so we keep the /web/ prefix in the
# filesystem path (root, not alias).
location /web/ {
root /var/www/dgb-wallet;
try_files $uri $uri/ /web/index.html;
# index.html must never be cached — new deploys change the asset
# filename hashes, and the entry HTML has to fetch the new ones.
add_header Cache-Control "no-store" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "no-referrer" always;
# Strict CSP (docs/SECURITY-ROADMAP.md item 1). The sha256 is the
# inline <base>-fix script in the built index.html — recompute
# whenever packages/web-ui/index.html changes.
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'sha256-MYCXjyaeuO4n6g9JktOO1yXmG9rBTuGJ5Wk4GojDwig=' 'wasm-unsafe-eval'; style-src 'self' 'unsafe-inline'; connect-src 'self' https://api.coingecko.com; img-src 'self' data:; font-src 'self'; base-uri 'self'; frame-ancestors 'none'; form-action 'none'; object-src 'none'" always;
}
# Content-hashed assets: cache forever.
location /web/assets/ {
root /var/www/dgb-wallet;
add_header Cache-Control "public, max-age=31536000, immutable" always;
add_header X-Content-Type-Options "nosniff" always;
}
# /p2p/* → daemon. The daemon registered /health, /balance, /broadcast
# at its own roots, so strip the /p2p prefix before proxying.
location /p2p/ {
proxy_pass http://127.0.0.1:5174/;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_read_timeout 120s; # BIP158 scans can take a while
# The daemon already emits CORS headers for '*' — no need to add
# them here. Leave request bodies (POST /broadcast, /balance) unbuffered.
proxy_request_buffering off;
}
# /blockbook/* → same-origin reverse-proxy to a public Blockbook.
# Two reasons this exists:
# 1. Atomic's Blockbook sends no CORS headers, so a direct
# browser fetch from the wallet origin is silently blocked
# and the wallet reads "zero balance" for every xpub.
# 2. Requests go out under this VPS's IP, not the individual
# user's — the upstream can't correlate address queries to
# individual browsers. (This VPS operator still can — the
# wallet's Blockbook backend toggle carries an inline note.)
location /blockbook/ {
# Upstream: Atomic Wallet's public DGB Blockbook. The proxy hides
# individual user IPs from the upstream operator. Every public DGB
# Blockbook rejects raw bech32 address queries; `/xpub` works
# (server-side derivation covers all script kinds).
#
# NOWNodes (dgbbook.nownodes.io, api-key injected here from
# /etc/dgb-core/nownodes.env) was the upstream 2026-09-02..10-11
# until the key stopped being accepted ("Unknown API_key" on every
# endpoint). To switch back with a fresh key:
# proxy_pass https://dgbbook.nownodes.io/api/v2/;
# proxy_ssl_name dgbbook.nownodes.io;
# proxy_set_header Host dgbbook.nownodes.io;
# proxy_set_header api-key "@NOWNODES_API_KEY@";
proxy_pass https://digibyte.atomicwallet.io/api/v2/;
proxy_ssl_server_name on;
proxy_ssl_name digibyte.atomicwallet.io;
proxy_set_header Host digibyte.atomicwallet.io;
proxy_set_header X-Forwarded-For "";
proxy_set_header X-Real-IP "";
proxy_http_version 1.1;
proxy_read_timeout 60s;
proxy_hide_header Access-Control-Allow-Origin;
proxy_hide_header Access-Control-Allow-Methods;
proxy_hide_header Access-Control-Allow-Headers;
add_header Access-Control-Allow-Origin "*" always;
add_header Access-Control-Allow-Methods "GET, POST, OPTIONS" always;
add_header Access-Control-Allow-Headers "Content-Type" always;
if ($request_method = OPTIONS) { return 204; }
}
# Bare / — redirect to /web/. This vhost exists ONLY to host the wallet;
# no reason to leave a hint here for hostile traffic.
# /web (no trailing slash) — redirect so bare /web works. Sits above /web/ (longest-prefix) so it does not shadow the static block.
location = /web {
# BNS gateway strips the Location header, so a plain 302 goes
# nowhere for gateway-loaded users. Return HTML that redirects
# via meta-refresh + JS with a RELATIVE href — resolves against
# the current URL, works under both digibyte.x and navigate.st.
default_type text/html;
return 200 "<!doctype html><html><head><meta http-equiv=\"refresh\" content=\"0;url=web/\"><script>location.replace(\"web/\")</script></head><body>Redirecting to <a href=\"web/\">web/</a></body></html>";
}
# Marketing landing page — served at the apex. Wallet is at /web/.
location = / {
root /var/www/dgb-wallet;
try_files /index.html =404;
add_header Cache-Control "no-store" always;
add_header X-Content-Type-Options "nosniff" always;
}
# Root-level static files (favicon.svg, robots.txt, …). Anything
# not on disk still 404s, and the wallet routes above (which are
# longer-prefix) still win. Kept explicit-only to avoid accidental
# exposure of /var/www/dgb-wallet subdirs.
location = /favicon.svg { root /var/www/dgb-wallet; }
location = /robots.txt { root /var/www/dgb-wallet; }
# Everything else on this vhost — 404. No indexer/status page.
location / {
return 404;
}
}