aboutsummaryrefslogtreecommitdiffstats
path: root/tools/dnssec-test.js
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-08-01 20:46:12 -0700
committerAdam Soltys <[email protected]>2026-08-01 20:46:12 -0700
commited1d996f9dbd6cedc18ec293daba0cfcd745ae2b (patch)
tree6d552796e899314d402dc92f6f80886405e296b5 /tools/dnssec-test.js
parent5986cbf5337593eb11fd984d284bd41da7719a87 (diff)
downloadcoinosv3-ed1d996f9dbd6cedc18ec293daba0cfcd745ae2b.tar.gz
coinosv3-ed1d996f9dbd6cedc18ec293daba0cfcd745ae2b.zip
Zappable offer codes and per-name Lightning offers
CLINK offers (noffer): every wallet gets a static offer keypair (synced across devices, first-minted wins) answering kind-21001 invoice requests — from open tabs and from the service worker while closed, which mints against the mirrored state (the record now carries chain-4 and chain-5 key windows) and raises an open-to-receive notification. Zap requests ride along and their kind-9735 receipts publish once the payment is claimed. The code is shown in the payment-address card for pasting into a Nostr profile; the notifier watches and relays kind 21001 alongside NWC. Payment names also gain a Lightning offer: the registrar creates a BOLT 12 offer per name on the node and appends lno= to the DNS record; a settle-forwarder pushes each received payment on to the name's ark destination from a small float wallet (custody window: seconds between Lightning settle and the ark send). Name resolution now runs full client-side DNSSEC validation (src/dnssec.js): binary DoH, root-anchor chain walk, RSA + ECDSA via WebCrypto — the resolver is untrusted transport, verified against live signed zones including the canonical BIP-353 test name. Co-Authored-By: Claude Fable 5 <[email protected]>
Diffstat (limited to 'tools/dnssec-test.js')
-rw-r--r--tools/dnssec-test.js39
1 files changed, 39 insertions, 0 deletions
diff --git a/tools/dnssec-test.js b/tools/dnssec-test.js
new file mode 100644
index 0000000..71ebcee
--- /dev/null
+++ b/tools/dnssec-test.js
@@ -0,0 +1,39 @@
+// DNSSEC chain validation against LIVE DNS. Needs network.
+// Run: bun tools/dnssec-test.js
+import { resolveTxtValidated } from '../src/dnssec.js';
+
+let fails = 0;
+const check = (name, ok, detail = '') => {
+ console.log(` ${ok ? '✓' : '✗'} ${name}${detail ? ' — ' + String(detail).slice(0, 140) : ''}`);
+ if (!ok) fails++;
+};
+
+// The canonical BIP-353 record: ₿[email protected] (alg 8 chain).
+try {
+ const { txts } = await resolveTxtValidated('matt.user._bitcoin-payment.mattcorallo.com');
+ const uri = txts.find((t) => t.toLowerCase().startsWith('bitcoin:'));
+ check('validates [email protected] (RSA chain)', !!uri, (uri || '').slice(0, 60) + '…');
+} catch (e) { check('validates [email protected] (RSA chain)', false, e.message); }
+
+// A Cloudflare-signed TXT (alg 13 chain): cloudflare.com SPF record.
+try {
+ const { txts } = await resolveTxtValidated('cloudflare.com');
+ check('validates cloudflare.com TXT (ECDSA chain)', txts.length > 0, txts[0]);
+} catch (e) { check('validates cloudflare.com TXT (ECDSA chain)', false, e.message); }
+
+// An unsigned zone must FAIL closed, not pass quietly.
+try {
+ await resolveTxtValidated('google.com'); // google.com is famously unsigned
+ check('unsigned zone fails closed', false, 'validated?!');
+} catch (e) {
+ check('unsigned zone fails closed', /not in a signed zone|no DNSKEY|fails/.test(e.message), e.message);
+}
+
+// A missing record must throw, not return empty-and-valid.
+try {
+ await resolveTxtValidated('definitely-not-real.user._bitcoin-payment.mattcorallo.com');
+ check('missing record throws', false);
+} catch (e) { check('missing record throws', true, e.message); }
+
+console.log(fails ? `\n❌ ${fails} failure(s)` : '\n✅ DNSSEC validation behaves');
+process.exit(fails ? 1 : 0);