Summary
On relays whose NIP-11 lacks pairing_relay_url (which includes both tagged relay releases, relay-v0.1.1 and relay-v0.2.0), Buzz Desktop's device pairing falls back to the legacy convention wss://<relay-host>/pair — and then cannot authenticate, because the desktop signs its NIP-42 AUTH event with that URL including the path, while the relay expects the bare origin. The mismatch is unconditional, so legacy-path pairing fails for every operator on the tagged releases.
The two sides of the contract
Desktop (desktop/src-tauri/src/commands/pairing.rs): when NIP-11 has no pairing_relay_url but advertises NIP-43, resolve_pairing_relay_url builds <main relay>/pair (PairingRelay::LegacyPath), and handle_nip42_auth signs the AUTH event with that full URL:
let relay_url_parsed = nostr::RelayUrl::parse(relay_url)...; // wss://<host>/pair
... s.sign_event(nostr::EventBuilder::auth(challenge, relay_url_parsed))
Relay (crates/buzz-relay/src/api/bridge.rs, nip42_expected_relay_url): the expected value is derived from the tenant host, explicitly pathless — the comment states the contract the desktop is violating:
/// Path is empty — clients put the bare WS origin (`ws://host[:port]`) in the
/// `relay` tag, matching how `EventBuilder::auth` accepts a [`nostr::RelayUrl`].
format!("{scheme}://{}", tenant.host())
normalize_relay_url in crates/buzz-auth/src/nip42.rs preserves paths (it only trims a trailing slash), so wss://<host>/pair vs wss://<host> never match. Since every WS connection must complete NIP-42 within the fixed 5s window, the connection is then closed.
Observed behaviour
Desktop v0.5.4 against relay-v0.2.0, with an open pairing relay served behind a path-stripping proxy at <relay-host>/pair (so the transport itself works — the WS upgrade succeeds and the QR renders briefly):
Relay log, every attempt:
WARN NIP-42 auth failed error="relay url mismatch"
WARN NIP-42 auth timeout — closing connection timeout_secs=5
INFO WebSocket connection closed
Desktop surfaces this as relay closed waiting for EOSE a few seconds after the QR appears.
Suggested fix (either side resolves it)
- Desktop: sign the bare origin of the pairing URL in the AUTH event — strip the path from
relay_url before EventBuilder::auth in handle_nip42_auth. One line, and it matches the relay's documented expectation.
- Or relay: make
nip42_expected_relay_url (or the comparison) tolerant of the client's connect path.
Operator workaround meanwhile
Run a relay recent enough to support BUZZ_PAIRING_RELAY_URL, and advertise the open pairing relay at a dedicated hostname (path /). The desktop then takes the PairingRelay::Configured route, dials a pathless URL, signs the bare origin, and auth succeeds. Verified working end-to-end; only the legacy path convention is broken.
Happy to provide fuller logs or test against a patched build.
Summary
On relays whose NIP-11 lacks
pairing_relay_url(which includes both tagged relay releases,relay-v0.1.1andrelay-v0.2.0), Buzz Desktop's device pairing falls back to the legacy conventionwss://<relay-host>/pair— and then cannot authenticate, because the desktop signs its NIP-42 AUTH event with that URL including the path, while the relay expects the bare origin. The mismatch is unconditional, so legacy-path pairing fails for every operator on the tagged releases.The two sides of the contract
Desktop (
desktop/src-tauri/src/commands/pairing.rs): when NIP-11 has nopairing_relay_urlbut advertises NIP-43,resolve_pairing_relay_urlbuilds<main relay>/pair(PairingRelay::LegacyPath), andhandle_nip42_authsigns the AUTH event with that full URL:Relay (
crates/buzz-relay/src/api/bridge.rs,nip42_expected_relay_url): the expected value is derived from the tenant host, explicitly pathless — the comment states the contract the desktop is violating:normalize_relay_urlincrates/buzz-auth/src/nip42.rspreserves paths (it only trims a trailing slash), sowss://<host>/pairvswss://<host>never match. Since every WS connection must complete NIP-42 within the fixed 5s window, the connection is then closed.Observed behaviour
Desktop v0.5.4 against
relay-v0.2.0, with an open pairing relay served behind a path-stripping proxy at<relay-host>/pair(so the transport itself works — the WS upgrade succeeds and the QR renders briefly):Relay log, every attempt:
Desktop surfaces this as
relay closed waiting for EOSEa few seconds after the QR appears.Suggested fix (either side resolves it)
relay_urlbeforeEventBuilder::authinhandle_nip42_auth. One line, and it matches the relay's documented expectation.nip42_expected_relay_url(or the comparison) tolerant of the client's connect path.Operator workaround meanwhile
Run a relay recent enough to support
BUZZ_PAIRING_RELAY_URL, and advertise the open pairing relay at a dedicated hostname (path/). The desktop then takes thePairingRelay::Configuredroute, dials a pathless URL, signs the bare origin, and auth succeeds. Verified working end-to-end; only the legacy path convention is broken.Happy to provide fuller logs or test against a patched build.