aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-08-01 23:08:02 -0700
committerAdam Soltys <[email protected]>2026-08-01 23:08:02 -0700
commitbabac497c1f44892b646b30be4d3ad035b461386 (patch)
tree2ce0adabbdc625edfd5086de35e041af9fbfb5ba
parent83ad00a39863bb6a61e15977202fe2feea2ca696 (diff)
downloadcoinosv3-babac497c1f44892b646b30be4d3ad035b461386.tar.gz
coinosv3-babac497c1f44892b646b30be4d3ad035b461386.zip
Fix bunker (NIP-46) login: the pointer goes through fromBunker
nostr-tools' BunkerSigner constructor takes (clientSecretKey, params) — passing the bunker pointer as the second argument left the signer with no pointer at all, so connecting died on "reading 'pubkey'" before any network call. Use the static fromBunker, and reject an address that lists no relays with something a human can act on. Covered by a test using a real Amethyst-issued address. Co-Authored-By: Claude Fable 5 <[email protected]>
-rw-r--r--src/nostr-login.js5
-rw-r--r--tools/nostr-login-test.js19
2 files changed, 23 insertions, 1 deletions
diff --git a/src/nostr-login.js b/src/nostr-login.js
index 00106c4..7addfc5 100644
--- a/src/nostr-login.js
+++ b/src/nostr-login.js
@@ -90,8 +90,11 @@ export async function extensionSigner() {
export async function bunkerSigner(uri, { onAuth } = {}) {
const bp = await parseBunkerInput(String(uri || '').trim());
if (!bp) throw new Error('not a bunker:// address');
+ if (!bp.relays || !bp.relays.length) throw new Error('that bunker address lists no relays');
const local = generateSecretKey();
- const signer = new BunkerSigner(local, bp, {
+ // NB the pointer goes through fromBunker — the constructor takes params
+ // only, and passing the pointer there leaves the signer without one.
+ const signer = BunkerSigner.fromBunker(local, bp, {
onauth: (url) => { if (onAuth) onAuth(url); else if (typeof window !== 'undefined') window.open(url, '_blank'); },
});
await signer.connect();
diff --git a/tools/nostr-login-test.js b/tools/nostr-login-test.js
index 42580fe..0440333 100644
--- a/tools/nostr-login-test.js
+++ b/tools/nostr-login-test.js
@@ -73,5 +73,24 @@ console.log('\n[NIP-98 auth]');
check('binds the body hash', tag('payload') === hex.encode(sha(new TextEncoder().encode(body))));
}
+console.log('\n[bunker (NIP-46) wiring]');
+{
+ const { bunkerSigner } = await import('../src/nostr-login.js');
+ const { parseBunkerInput } = await import('nostr-tools/nip46');
+ // a real Amethyst-issued address shape
+ const uri = 'bunker://81029479a85c3f2f0c8d7ca9fbd92923942e2254e873d93c1c7095f068c83a85'
+ + '?relay=wss%3A%2F%2Frelay.damus.io%2F&relay=wss%3A%2F%2Fnos.lol%2F&secret=4wdWEsG7RDzCaBt6p9ovvk0uMX6ENh2J';
+ const bp = await parseBunkerInput(uri);
+ check('parses an Amethyst bunker address', !!bp && bp.relays.length === 2 && !!bp.secret);
+ // Constructing must not throw synchronously: the failure this guards against
+ // is passing the pointer to the constructor, which leaves signer.bp unset
+ // and dies with "reading 'pubkey'" before any network call.
+ let err = null;
+ const p = bunkerSigner(uri, { onAuth: () => {} }).catch((e) => { err = e; });
+ await Promise.race([p, new Promise((r) => setTimeout(r, 1500))]);
+ check('constructs without a pubkey error', !/reading 'pubkey'|undefined/.test(String(err && err.message)), err && err.message);
+ check('rejects a non-bunker string', await bunkerSigner('nope').then(() => false, (e) => /bunker/.test(e.message)));
+}
+
console.log(fails ? `\n❌ ${fails} failure(s)` : '\n✅ nostr login behaves');
process.exit(fails ? 1 : 0);