Two routes, in the house flat-if style next to the other /api handlers. GET /api/vpn/servers returns the exit list with Reality material only — host, port, sni, fp, pbk, sid — and no client UUID. All of that is already visible to anyone watching the handshake, so the catalogue is safe to serve and to cache. POST /api/vpn/session leases one pre-provisioned UUID from that exit's pool to the caller for BNS_VPN_TTL_MS and returns a built vless:// URL. Asking again inside the TTL returns the same lease instead of burning a second UUID, so a client that reconnects is not counted as a new tenant. Paid tiers require a signature over BNS-VPN1\n<serverId>\n<tier>\n<ts> and the recovered address must equal the address the caller claims — recovery on its own decides nothing. Pro and Max then stop at 501, because the subscription ledger they would check does not exist yet; better an honest refusal than a free lease wearing a paid label. This replaces the shared credential the extension was going to ship. It does not give real revocation: xray keeps honouring a leased UUID until the inbound is rotated, so the TTL frees a slot for re-issue but does not cut a live tunnel. What it does give is that every caller holds a distinct UUID, so one abuser can be dropped from the pool without touching anyone else, and nothing is ever published. Real expiry needs the live xray handler API on each box; noted in the handler comment so the next session does not assume the TTL is enforced. The pool file is the only place those UUIDs exist off the servers. It is read from BNS_VPN_POOL_FILE, created by hand on the VPS, and kept out of the repo the same way oracle.key is. One deployment snag found while writing this: the angie vhost forwards only Host, so every request reaches the gateway as 127.0.0.1 and callers are indistinguishable. Rather than rate-limit the whole internet as one bucket — which would let the first user lock out the rest — clientKey() returns null in that case and the handler answers 503 explaining the proxy needs X-Forwarded-For. Smoke test covers what matters for a credential issuer: the catalogue leaks no UUID, two callers get different ones, one caller asking twice burns only one, exhaustion is a clean 503, tier gating holds, a junk or stale signature is refused, and an unidentifiable caller is refused rather than served. It stages the flat VPS layout so it exercises the real gateway file, not a rewritten copy. 17/17. |
||
|---|---|---|
| contracts | ||
| setup | ||
| src | ||
| test | ||
| .gitignore | ||
| ariadne.config.json | ||
| BROWSER-PROMPT.md | ||
| build-web.mjs | ||
| DESIGN-collision-modes.md | ||
| DESIGN-hosting-pact.md | ||
| DESIGN-root-tld-cert.md | ||
| ENGINE.md | ||
| namespaces.pdf | ||
| OPERATIONS-electrum.md | ||
| package-lock.json | ||
| package.json | ||
| PROTOCOL.md | ||
| README.md | ||
| ROADMAP.md | ||
| sia-test.html | ||
BNS — Namecoin-style decentralized names on Bitcoin Cash (chipnet)
A working prototype of "Option 3": names are CashTokens name certificates on the BCH chain (no fork, no sidechain), records ride in OP_RETURN, discovery via a beacon address, and the resolver is embedded in a wallet app with a built-in browser pane — the Brave/MetaMask-browser architecture in miniature.
See PROTOCOL.md for the on-chain design and its honest limitations.
Layout
src/lib/bns.js— protocol core: payload codec, chain indexer/resolver, tx builderssrc/wallet-app.js— wallet + embedded resolver + browser UI (npm run wallet→ http://localhost:4626)src/register.js/src/update.js/src/resolve.js— CLI equivalentswallets.json— chipnet seed phrases (TESTNET ONLY — never reuse for real funds)src/lib/gen-wallets.js— one-time wallet generation (already run)
Setup
npm install- Fund the wallet with free chipnet tBCH: go to https://tbch.googol.cash/,
paste the wallet address (shown in the app sidebar or by
node src/lib/gen-wallets.js), pick chipnet, solve the captcha. ~0.001 tBCH funds hundreds of registrations. npm run walletand open http://localhost:4626
Standalone resolver daemon (make EVERY browser resolve .bch)
src/daemon/bnsd.js— DNS server (answers*.bchfrom the chain, forwards everything else upstream) + HTTP gateway that serves on-chain sites by Host header. Test unprivileged:node src/daemon/bnsd.js --dns-port 5354 --http-port 8081, thennode src/daemon/dig.js hello.bch.setup/install.ps1— run from an elevated PowerShell. Installs the daemon as a boot-time scheduled task (SYSTEM, auto-restart) and wires the OS to it:- default (
-Mode nrpt): adds an NRPT rule so only.bchqueries reach the daemon; all other DNS is untouched. -Mode full: points every adapter's DNS at 127.0.0.1 (daemon forwards non-.bch upstream, ncdns-style); original servers are saved and restored on uninstall.
- default (
setup/uninstall.ps1— reverses everything (rule/adapters/task).
HTTPS for .bch: the "BNS Local Root CA" (created on demand in ca/, keep
root-ca.key private) signs per-name certs. The daemon's :443 gateway mints
them on the fly for locally-served names; node src/lib/issue-cert.js <name>
issues one for a real server (see the coinspectrum.bch nginx vhost). The
installer trusts the root machine-wide and enables Firefox's
enterprise-roots policy; cert fingerprints are published in "tls" chain
records for pinning.
Caveats: browsers with "secure DNS" (DoH) enabled bypass the OS resolver — disable it or add an exception; ports 53/80/443 must be free.
Try it
- Register
hellowith inline HTML (a website stored entirely on-chain), then typehello.bchin the address bar — the app resolves it from the blockchain (not DNS) and renders it. - Or CLI:
node src/register.js hello '{"h":"<h1>Served from the BCH chain</h1>"}'thennode src/resolve.js hello