Skip to content

Legacy /pair pairing fallback cannot pass NIP-42 — desktop signs the URL with path, relay expects bare origin #4932

Description

@depre-dev

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions