block / block/buzz

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

Open
#4,932 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
Rust
Stars
32.7k
Forks
4.3k
Avg merge
1d 13h
Merged PRs (30d)
253

Description

## 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:///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 `/pair` (`PairingRelay::LegacyPath`), and `handle_nip42_auth` signs the AUTH event with that full URL:

```rust
let relay_url_parsed = nostr::RelayUrl::parse(relay_url)...; // wss:///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:

```rust
/// 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:///pair` vs `wss://` 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 `/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.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.