feat: NIP-AB device pairing — Phase 2 (desktop + mobile UI)#343
Merged
wesbillman merged 9 commits intomainfrom Apr 16, 2026
Merged
feat: NIP-AB device pairing — Phase 2 (desktop + mobile UI)#343wesbillman merged 9 commits intomainfrom
wesbillman merged 9 commits intomainfrom
Conversation
Integrate the NIP-AB pairing protocol from PR #333 into the desktop (source) and mobile (target) apps, replacing the insecure sprout:// QR code that embedded raw credentials directly. Desktop (source side): - New pairing.rs with 3 Tauri commands (start_pairing, confirm_pairing_sas, cancel_pairing) and a background WebSocket actor that drives the protocol - Multi-step PairingDialog: QR display → SAS verification → transfer → done - Token minting refactored to expose mint_token_internal for reuse - Uses nostr-compat (0.36) alias for sprout-core type compatibility Mobile (target side): - NIP-44 v2 encrypt/decrypt implementation using pointycastle - HKDF-SHA256 and secp256k1 ECDH crypto modules - NIP-AB protocol: QR URI parsing, session ID/SAS/transcript derivations - Ephemeral WebSocket with NIP-42 auth for pairing relay - SAS verification UI with large code display and confirm/deny - Backward-compatible: still accepts legacy sprout:// URIs Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Remove extra author p-tag from sent messages and match desktop tag order (h-tag first). The mobile was prepending ['p', authorPubkey] before the channel h-tag, which differs from the desktop format and caused agents to not respond to mobile messages. - Add pubkey display with copy button to Settings page so users can verify their identity matches the desktop. - Fix logout routing: pop all navigator routes back to root before signing out so the app returns to the pairing/welcome page instead of staying on the settings screen. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The mobile's text-based @mention resolver was matching against the global user cache, which picked the wrong user when multiple users share a display name (e.g. two "rick"s). This caused agents to not respond to mobile messages — the p-tag pointed to the wrong pubkey. Now fetches channel members from the relay API and only matches @mentions against members of the current channel. Falls back to the full cache if the member fetch fails. Also adds pubkey display to Settings and fixes logout routing (popUntil root before sign-out). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ChannelMessagesNotifier returned AsyncData([]) (empty list) when the WebSocket session wasn't connected yet. After first-time pairing, the user would see channel names but empty message lists until the session connected. Changed to AsyncLoading() so the UI shows a spinner, and the provider rebuilds automatically once the WebSocket connects. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
After first-time pairing, the WebSocket session takes a moment to
connect. Previously the channel messages provider returned an empty
list (or infinite spinner) while waiting. Now it falls back to the
REST API (GET /api/channels/{id}/messages) to show content immediately.
Once the WebSocket connects, the provider rebuilds and switches to
the full live subscription flow.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Reverts 0b93236 and c3ee567. The HTTP fallback and AsyncLoading changes didn't fix the post-pairing channel loading issue — the real cause is likely the WebSocket session not initializing properly after first-time authentication. Needs a proper fix. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Fixes from security review: Critical: - Gate payload acceptance on PairingStatus.transferring only, not confirmingSas. Prevents credentials from being stored before the user explicitly confirms the SAS codes match. (C3) - Wrap desktop payload field in Zeroizing<String> so nsec is cleared from heap memory on drop. (C2) High: - Add NIP-01 event signature verification on mobile via Event.fromJson(verify: true) before processing any pairing event. (C1) - Send abort event with reason "sas_mismatch" when transcript hash verification fails, per NIP-AB spec requirement. (H2) - Add duplicate event ID tracking on mobile to prevent replay. (H3) Medium: - Validate event kind == 24134 before processing. (M4) - Remove dead code branch in _handleSasConfirm. (M8) - Clear _processedEventIds and _sasConfirmReceived in _cleanup. (M6) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The security fix (C3) gated payload acceptance on transferring status, but the desktop sends sas-confirm + payload back-to-back. The payload arrives while the mobile is still in confirmingSas (waiting for user tap), gets silently discarded, and the flow hangs. Now buffers the payload when it arrives before user confirmation and processes it immediately when confirmSas() is called. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Keep main's tts.rs limit bump (1030) and our pairing overrides. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
tlongwell-block
added a commit
that referenced
this pull request
Apr 17, 2026
* origin/main: nip-ab: clarify transcript_hash role and fix protocol diagram (#346) chore: fix deprecation warnings and decompose AgentsView (#347) chore: improve thread panel inline replies and nesting behavior (#339) feat: NIP-AB device pairing — Phase 2 (desktop + mobile UI) (#343) fix(huddle): prevent phantom huddle from late-arriving relay events (#344) perf(tts): reduce Kokoro time-to-first-audio with session warmup and threading (#342)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Integrates the NIP-AB device pairing protocol from #333 into the desktop (source) and mobile (target) apps. Replaces the insecure
sprout://QR code that embedded raw credentials directly with a multi-step encrypted exchange using ephemeral keys, ECDH, NIP-44, and SAS verification.pairing.rsbackend with 3 Tauri commands + background WebSocket actor. Multi-step React dialog: QR display → SAS verification → encrypted transfer → doneKnown issues (not in scope)
Test plan
nostrpair://URI@agentmessage → agent responds (p-tag correct)sprout://URIs still work on mobilecargo test -p sprout-core --lib pairing— 69 tests pass🤖 Generated with Claude Code