fix: accept NIP-46 connect responses from signers that echo the URI secret#2947
fix: accept NIP-46 connect responses from signers that echo the URI secret#2947DocNR wants to merge 2 commits into
Conversation
…ecret NDK's NDKNip46Signer.blockUntilReady has two NIP-46 spec violations present in every released NDK from 2.12.2 through 3.0.3: 1. It sends connect([userPubkey ?? "", secret]). For bunker URIs without ?pubkey= (the typical single-user signer case), this passes an empty first param. Per NIP-46 the first param is the remote-signer-pubkey, not the user-pubkey. 2. It only accepts response.result === "ack" and rejects everything else with response.error -- which is undefined when a signer returns the spec-allowed alternative (the URI secret echoed back). Per NIP-46, valid `connect` responses are "ack" OR the URI secret echoed back. Spec-compliant signers like Clave (https://github.com/DocNR/clave) and any signer matching nostr-tools' BunkerSigner.fromURI return the secret-echo form, which made blockUntilReady() reject with undefined and our setError(e) later throw on e.message -- leaving the login spinner stuck on "Waiting for authorization" forever. Extends the existing NDKNip46SignerURLPatch (originally for the non-special-scheme URL parsing issue, PR stackernews#1636) with a corrected blockUntilReady that: - Sends bunkerPubkey as the first connect param when userPubkey is null (matches the spec) - Accepts response.result === "ack" OR === the URI secret - Defers to super.blockUntilReady() for the NIP-05 path (unchanged) Verified with a Clave-emulating mock bunker against NDK 2.12.2 (the version pinned in package.json): - Unpatched: rejects in 39ms with `undefined` (TypeError on setError(e)) - Patched (Clave-style secret-echo response): resolves in 112ms - Patched (nsec.app-style "ack" response): resolves in 110ms The override can be removed once we bump to an NDK that fixes both bugs upstream. Tracking issue/PR to be filed on nostr-dev-kit/ndk in parallel.
|
so i was not tripping |
|
lol nah... there are a few other clients that use NDK that I've noticed have this issue. |
|
The only one seems good now for login is |
|
I copied the dart code and got my app to work again |
|
https://github.com/Kampouse/nip46-connect-demo |
Vinayak1337
left a comment
There was a problem hiding this comment.
I think this still leaves one spec-compliant bunker token shape broken.
The new override says it fixes the first connect parameter by sending the remote-signer pubkey, but the implementation still does this:
const connectParams = [this.userPubkey || this.bunkerPubkey]That means any bunker URI that has both a bunker pubkey in the host and ?pubkey=<user pubkey> will still send the user pubkey as params[0]. In NDK's parser, userPubkey comes from the query string, while bunkerPubkey comes from the token host. Current NIP-46 defines connect as [<remote-signer-pubkey>, <optional_secret>, ...], and nostr-tools' BunkerSigner.connect() also sends [this.bp.pubkey, this.bp.secret || ""].
So for a multi-user signer, or any signer that includes the expected user key in ?pubkey=... but validates connect.params[0] against its own signer pubkey, this PR will still reject or hang even though the token is otherwise valid. The narrow fix is to always send this.bunkerPubkey as the first connect param, then keep the existing get_public_key call after a successful ack/secret echo to resolve and store this.userPubkey.
A regression test with distinct bunkerPubkey and userPubkey would lock this down and also document why this override differs from NDK's current this.userPubkey ?? "" behavior.
Review feedback (stackernews#2947): the previous `[this.userPubkey || this.bunkerPubkey]` still sent the user pubkey as connect.params[0] whenever the bunker URI carried `?pubkey=`. Per NIP-46, connect.params[0] is the remote-signer-pubkey — always bunkerPubkey (the URI host). The `?pubkey=` value is the user identity, resolved separately via the get_public_key call after the handshake. Sending userPubkey breaks multi-user signers (and any signer that validates params[0] against its own signer pubkey). nostr-tools' BunkerSigner sends `this.bp.pubkey` likewise.
|
Confirmed. Verified against the NIP-46 spec (
Fixed in a007b45 — On the regression test: this repo has no existing tests that touch |
Summary
Fixes the NIP-46 bunker login hanging forever on the "Waiting for authorization" spinner when paired with spec-compliant signers like Clave. The root cause is two NIP-46 spec violations in NDK's
NDKNip46Signer.blockUntilReady— present in every released NDK from 2.12.2 (this repo's pinned version) through 3.0.3.Per the NIP-46 spec:
NDK violates both:
connect([userPubkey ?? "", secret]). Forbunker://...URIs without?pubkey=(the typical single-user signer case — Clave, etc.)userPubkeyis null, so an empty string lands as the first param.response.result === "ack", rejecting the spec-allowed alternative (the URI secret echoed back) withresponse.error— which isundefinedwhen the bunker returned a successful-shaped response. OursetError(e)in components/nostr-auth.js then throws one.message || e.toString(), leaving the spinner stuck.This PR extends the existing
NDKNip46SignerURLPatch(from #1636) with an overriddenblockUntilReadythat:bunkerPubkeyas the first connect param whenuserPubkeyis null (per spec)"ack"and the URI secret echoed back (per spec)super.blockUntilReady()for the NIP-05 path (unchanged)Errorso the catch handler doesn'tTypeErroron undefinedThe override can be deleted once we bump to an NDK that fixes both bugs upstream — companion PR filed at nostr-dev-kit/ndk#390.
Test plan
Verified end-to-end against
@nostr-dev-kit/ndk@2.12.2(the version pinned here) using a Clave-emulating mock NIP-46 bunker on a localnak serverelay, plus a real Clave bunker URI from a TestFlight device:undefined(the bug)"ack"(nsec.app-style)relay.powr.build"ack"responses (regression — no breakage for nsec.app etc.)relay.powr.buildsuper.blockUntilReady()explicitly🤖 Generated with Claude Code