Skip to content

fix(pairing-cli): send the JSON envelope the apps decode, and stop panicking on wss:// - #6848

Open
lodar wants to merge 1 commit into
block:mainfrom
5dive-ai:upstream-pr/pairing-envelope-tls
Open

fix(pairing-cli): send the JSON envelope the apps decode, and stop panicking on wss://#6848
lodar wants to merge 1 commit into
block:mainfrom
5dive-ai:upstream-pr/pairing-envelope-tls

Conversation

@lodar

@lodar lodar commented Aug 26, 2026

Copy link
Copy Markdown

Summary

Two defects that together made it impossible to pair a handset with the mobile app against an HTTPS relay. Both were measured against a live relay.

1. The payload the CLI sends is not the payload the apps decode.

resolve_payload returned a bare bech32 nsec as PayloadType::Nsec in both arms. Mobile _processPayload begins with jsonDecode(payload) as Map<String, dynamic>, and nothing in mobile/lib branches on payload_type, so a bare nsec dies on the leading n with FormatException: Unexpected character (at character 1) — verbatim what a real store build produced on a real handset.

--envelope-relay <https url> now emits the same shape the desktop client sends (desktop/src-tauri/src/commands/pairing.rs:145-148, PayloadType::Custom at :215): {"relayUrl","pubkey","nsec"}. pubkey is derived from the transferred nsec so the two cannot drift. Without the flag the payload is unchanged, so CLI-to-CLI interop testing keeps working exactly as before.

2. wss:// panicked before any pairing could start.

thread 'main' panicked at rustls-0.23.42/src/crypto/mod.rs:249:14
Could not automatically determine the process-level CryptoProvider

Both ring and aws-lc-rs are reachable in the workspace, so rustls refuses to choose. Plain ws:// never reaches that code path — which is why interop testing done entirely against a plaintext relay was structurally blind to it: the defect exists only past the TLS an HTTPS deployment adds. This pins the ring provider and installs it at the top of main(). Cargo.lock records the new direct rustls dependency; without that one hunk the tree does not build under --locked.

Related issue

No issue opened for this. There are open PRs for each half, and I would rather point at them than pretend otherwise:

This PR differs in fixing both halves inside the pairing CLI only — no mobile-side change — with the envelope behind an opt-in flag so existing CLI-to-CLI flows are untouched, the pubkey derived rather than passed in, and unit coverage for the decode gate. If a maintainer would rather land #4153/#2839 plus #4757, close this — the important thing is that a handset can pair over HTTPS.

Testing

  • 7 new unit tests in crates/buzz-pairing-cli/src/main.rs: that a bare nsec fails the apps' first jsonDecode gate and the envelope clears it, that the envelope pubkey is derived from the transferred nsec, that a generated key stays internally consistent, that the absent flag leaves current behaviour unchanged, that an invalid nsec is rejected before an envelope is built, and that odd relay URLs stay well-formed JSON.
  • cargo test -p buzz-pairing-cli --locked — 7/7 green on the pinned 1.95.0 toolchain. Worth flagging for the maintainers: just test-unit enumerates packages by hand and does not include buzz-pairing-cli, so these tests are compiled by clippy --all-targets but never run by any job. That is a gap in this repo's CI, not in this PR, and I am happy to open a separate one adding the package to the recipe.
  • End-to-end pairing of a handset against a live HTTPS relay, which is where both defects were found.
  • rustfmt --check clean on the changed files; lint, unit tests, the Windows build and the cross-compile matrix ran green on this exact tree in my own CI before opening.

No UI surface is touched.

The branch is based on f88cda9eb, which is behind main. Neither pairing-cli file has been touched upstream since, and the Cargo.lock hunk is a single line inside an otherwise unchanged buzz-pairing-cli block, so it applies cleanly. Happy to rebase on request.

…nicking on wss://

Two defects that together made it impossible to pair a handset with the Buzz
mobile app against an HTTPS relay. Both measured against a live relay.

1. THE ENVELOPE

`resolve_payload` returned a bare bech32 nsec as `PayloadType::Nsec` in both
arms. Mobile `_processPayload` begins with
`jsonDecode(payload) as Map<String, dynamic>` and nothing in `mobile/lib`
branches on `payload_type`, so a bare nsec dies on the leading `n` with
`FormatException: Unexpected character (at character 1)`. That is verbatim what
a real store build produced on a real handset.

`--envelope-relay <https url>` now emits the same shape the desktop client
sends (`desktop/src-tauri/src/commands/pairing.rs:145-148`,
`PayloadType::Custom` at :215): `{"relayUrl","pubkey","nsec"}`. `pubkey` is
DERIVED from the transferred nsec so the two cannot drift. Without the flag the
payload is unchanged, so CLI-to-CLI interop testing keeps working exactly as
before.

2. wss:// PANICKED BEFORE ANY PAIRING COULD START

    thread 'main' panicked at rustls-0.23.42/src/crypto/mod.rs:249:14
    Could not automatically determine the process-level CryptoProvider

Both `ring` and `aws-lc-rs` are reachable in the workspace, so rustls refuses to
choose. Plain `ws://` never reaches that code path — which is why interop
testing done entirely against a plaintext relay was structurally blind to it:
the defect exists only past the TLS an HTTPS deployment adds. Pin the `ring`
provider and install it at the top of `main()`.

`Cargo.lock` records the new direct `rustls` dependency; without that hunk the
tree does not build under `--locked`.

Signed-off-by: lodar <markounik@gmail.com>
@lodar
lodar requested a review from a team as a code owner August 26, 2026 03:07

@Chessing234 Chessing234 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

both diagnoses are convincing, and the rustls one especially — "both ring and aws-lc-rs are in the workspace tree, so rustls cannot select a process-level provider" with the note that ws:// never reaches that path, which is why plaintext interop testing couldn't have found it, is the kind of explanation that stops the fix being reverted later. deriving pubkey from the transferred nsec so the two can't drift is right.

the default is still the broken payload. --envelope-relay is opt-in, so buzz-pair source --relay wss://… with no extra flag continues to emit a bare nsec that the mobile app cannot decode — the exact failure the pr documents (FormatException: Unexpected character (at character 1)). the stated reason is preserving CLI-to-CLI interop testing, which is a real need, but it leaves the tool's default aimed at the case that doesn't work and the flag aimed at the case that does. inverting it — envelope by default, --bare-nsec for the CLI-to-CLI path — puts the correct behaviour on the path someone reaches for first, and the interop tests are the caller that can afford to be explicit.

if it stays opt-in, the doc comment should say what to expect on a real handset without the flag, because right now # Payload shape explains the failure and then presents the fix as an option rather than as the thing you almost certainly want.

is --envelope-relay validated? the value_name = "HTTPS_URL" and the help text both promise an https url, but i don't see anything rejecting a wss:// value, a bare hostname, or a typo. this is the one field whose wrongness surfaces on the handset after a successful pairing — the QR scans, the key transfers, and then the device joins nothing — which is the most expensive place to discover a typo. parsing it and requiring https (or http for local) at argument time would move that failure to the terminal where the user can fix it in a second.

smaller: --relay and --envelope-relay differ in meaning (ephemeral pairing relay vs the buzz relay to join) but not in name, and the doc comment carries the whole distinction. after this lands they'll both appear in --help next to each other; a name like --join-relay or --target-relay would carry it without the reader needing the module docs.

and one thing i'd flag as a question rather than a defect: the envelope shape is being reproduced from desktop/src-tauri/src/commands/pairing.rs:145-148, i.e. two implementations of one wire format with nothing binding them together. if that struct is serializable from a shared crate, using it here would make a future field addition a compile error instead of a second FormatException on a handset.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants