Files
sovereign_browser_rust/plans/login-dialog-comparison.md
T

226 lines
9.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Login Dialog Comparison: Original (C) vs Rust Implementation
**Date:** 2026-08-17
**Scope:** The Nostr login dialog shown when the browser starts.
**Reference files:**
- Original (C): [`sovereign_browser/src/login_dialog.c`](../sovereign_browser/src/login_dialog.c)
- Rust: [`src/login_dialog.rs`](../src/login_dialog.rs)
---
## 1. Overview
Both implementations present a modal GTK dialog on startup that lets the user
authenticate with a Nostr identity before the browser becomes usable. The
original C implementation is a mature, feature-complete dialog with **five
method tabs** plus a **"No Login"** option. The Rust implementation is a
minimal port that currently supports only **two** of the five methods and
lacks most of the original's UI affordances.
The Rust dialog is a functional but **visually and functionally reduced**
version of the original. This document details, page by page, what each
login screen shows and the differences between the two implementations.
---
## 2. High-Level Structural Differences
| Aspect | Original (C) | Rust |
|---|---|---|
| Window title | `sovereign browser <version>` (e.g. `sovereign browser v0.0.70`) | `Nostr Login` |
| Default size | 560 × 380 | 500 × 400 |
| Method selection | **GtkNotebook tabs** (Local Key, Seed Phrase, Read-only, NIP-46, n_signer) | **GtkComboBoxText dropdown** (5 entries) |
| "No Login" button | ✅ Present (far left) | ❌ Absent |
| Buttons | `No Login` \| `Cancel` \| `Sign In` | `Cancel` \| `Login` |
| Title header | Bold markup: **"Sign in with your Nostr key"** | ❌ None |
| Status/error label | ✅ Present (shows validation errors inline) | ❌ None (silently returns `None` on error) |
| Monospace font styling | ✅ Applied via CSS provider | ❌ None |
| Agent auto-close (200ms poll) | ✅ Present | ❌ None |
| Methods actually implemented | All 5 (local, seed, readonly, nip46, nsigner) | Only 2 (local, readonly) |
| Error messaging | Detailed inline messages | Silent failure (returns `None`) |
---
## 3. Page-by-Page Comparison
### 3.1 Local Key
**Original (C)** — tab label **"Local Key"**
- Instruction label: *"Enter your Nostr private key (nsec) or generate a new one:"*
- Text entry with placeholder `nsec1...`, width 60 chars
- **"Generate New Key"** button — generates a fresh keypair and fills the entry with the `nsec`
- Accepts `nsec1...` (bech32) **or** 64-char hex private key
- Validates input; shows inline errors: *"Invalid nsec."*, *"Invalid hex private key."*, *"Enter an nsec1... or 64-char hex key."*, *"Enter an nsec or click Generate."*
**Rust** — dropdown entry **"Local Key (nsec)"**
- Label: *"Private Key (hex):"*
- Text entry with **password masking** (`set_visibility(false)`)
- ❌ No "Generate New Key" button
- ❌ Only accepts 64-char **hex** private key (no `nsec1...` bech32 support)
- ❌ No inline validation/error messages — invalid input silently returns `None`
**Key differences:**
1. Rust only accepts hex, not `nsec1...` bech32.
2. Rust has no key generation button.
3. Rust masks the input as a password; the original shows it in plain text.
4. Rust has no error feedback.
---
### 3.2 Seed Phrase
**Original (C)** — tab label **"Seed Phrase"**
- Instruction label: *"Enter your 12-word BIP-39 seed phrase:"*
- **12 numbered word entry boxes in a 4×3 grid** (1. through 12.)
- Each box has **dropdown completion** (BIP-39 wordlist) + **inline auto-complete** on typing
- **Tab/Enter** moves focus to the next word box
- **"Generate New Mnemonic"** button — fills all 12 boxes with a fresh mnemonic
- **Account #** spin button (0–1000) with live derivation hint: *"Key derivation: m/44'/1237'/<N>'/0/0"*
- Validates exactly 12 words; errors: *"Expected 12 words, got N."*, *"Invalid seed phrase. Check the words and try again."*
**Rust** — dropdown entry **"Seed Phrase (BIP-39)"**
- Label: *"Seed Phrase (BIP-39):"*
- Single text entry with **password masking**
- ❌ No 12-box grid, no word completion, no auto-complete, no Tab navigation
- ❌ No "Generate New Mnemonic" button
- ❌ No Account # selector or derivation path hint
- ❌ **Not implemented** — selecting this method and clicking Login returns `None` (the `Seed` arm is not handled in the match)
**Key differences:**
1. Rust does not implement seed-phrase login at all (falls through to `None`).
2. The original's rich 12-word grid with completion is entirely absent.
---
### 3.3 Read-only (npub)
**Original (C)** — tab label **"Read-only"**
- Instruction label: *"Enter a Nostr public key (npub) for read-only mode:"*
- Text entry with placeholder `npub1...`, width 60 chars
- Hint (dimmed): *"Read-only mode: you can view content but cannot sign events."*
- Accepts `npub1...` (bech32) **or** 64-char hex pubkey
- Validates; errors: *"Invalid npub."*, *"Invalid hex pubkey."*, *"Enter an npub1... or 64-char hex pubkey."*
**Rust** — dropdown entry **"Read-only (npub)"**
- Label: *"Public Key (npub or hex):"*
- Text entry (visible, not masked)
- ✅ Accepts `npub1...` (via `nostr_url_normalize`) or hex
- ❌ No hint text
- ❌ No inline validation/error messages
**Key differences:**
1. Rust supports this method (the only one besides local that works).
2. Rust lacks the explanatory hint and error feedback.
---
### 3.4 NIP-46 Remote Signer
**Original (C)** — tab label **"NIP-46"**
- Instruction label: *"Connect to a NIP-46 remote signer (bunker:// URL):"*
- Text entry with placeholder `bunker://<pubkey>?relay=wss://...&secret=...`, width 60 chars
- Wrapped hint (dimmed): *"The remote signer holds your private key. Signing requests are sent over Nostr relays."*
- Parses and validates the bunker URL; errors: *"Invalid bunker:// URL. Format: ..."*, *"Enter a bunker:// URL."*
- Generates a client keypair, stores the remote signer's pubkey as identity
**Rust** — dropdown entry **"NIP-46 Remote Signer"**
- Label: *"Bunker URL:"*
- Text entry (visible)
- ❌ **Not implemented** — the `Nip46` arm is not handled in the match, returns `None`
- ❌ No hint text, no URL validation
**Key differences:**
1. Rust does not implement NIP-46 login at all.
---
### 3.5 n_signer Hardware
**Original (C)** — tab label **"n_signer"**
- Instruction label: *"Connect to n_signer hardware signer:"*
- **Transport** combo: `USB Serial` / `UNIX Socket` / `TCP` / `Other Qube` (default: Other Qube)
- **Device** entry — label/placeholder adapts to transport:
- Serial: *"Device Path:"* / `/dev/ttyACM0` + **"Detect Serial Devices"** button
- UNIX: *"Socket Name:"* / `nsigner`
- TCP: *"Host:Port:"* / `127.0.0.1:7777`
- Qube: *"Target Qube:"* / `nostr_signer` (default value)
- **Service** entry (qrexec only): `qubes.NsignerRpc`
- **Role** entry: `nostr_range`
- **Key Index** spin (0–1000) with live BIP-32 path label: *"Key Index (m/44'/1237'/<N>'/0/0):"*
- Wrapped hint (dimmed): *"n_signer is a foreground, RAM-only hardware signer. Your private key never leaves the device."*
- Detailed error mapping (auth denied, policy denied, index not whitelisted, etc.)
**Rust** — dropdown entry **"n_signer Hardware"**
- Label: *"Device Path:"*
- Text entry (visible)
- ❌ **Not implemented** — the `Nsigner` arm is not handled in the match, returns `None`
- ❌ No transport selector, no service/role/index fields, no device detection
**Key differences:**
1. Rust does not implement n_signer login at all.
2. The original's transport-adaptive UI is entirely absent.
---
## 4. Feature Matrix
| Feature | Original (C) | Rust |
|---|---|---|
| Local key (nsec/hex) | ✅ | ⚠️ hex only |
| Local key generation | ✅ | ❌ |
| Seed phrase (BIP-39) | ✅ | ❌ |
| Seed word grid + completion | ✅ | ❌ |
| Read-only (npub) | ✅ | ✅ |
| NIP-46 remote signer | ✅ | ❌ |
| n_signer hardware | ✅ | ❌ |
| "No Login" (browse w/o identity) | ✅ | ❌ |
| Inline error/status messages | ✅ | ❌ |
| Method tabs (notebook) | ✅ | ❌ (dropdown) |
| Title header | ✅ | ❌ |
| Monospace styling | ✅ | ❌ |
| Agent auto-close | ✅ | ❌ |
| Account # / key index selectors | ✅ | ❌ |
---
## 5. Summary of Gaps
The Rust login dialog is a **minimal skeleton** compared to the original.
The most significant gaps, in priority order:
1. **Only 2 of 5 methods work** — `Seed`, `Nip46`, and `Nsigner` are listed in
the dropdown but return `None` when selected. The original implements all five.
2. **No "No Login" option** — the original lets users browse without a Nostr
identity; the Rust version has no equivalent.
3. **No error feedback** — the original shows inline validation/error messages;
the Rust version silently returns `None` on any failure, giving the user no
indication of what went wrong.
4. **Reduced local-key support** — Rust only accepts hex private keys (no
`nsec1...` bech32) and has no "Generate New Key" button.
5. **UI structure differs** — the original uses notebook tabs with rich per-method
screens (word grids, transport selectors, derivation hints); Rust uses a single
dropdown that swaps a single label/entry.
6. **Missing polish** — no title header, no monospace styling, no agent
auto-close, no version in the window title.
---
## 6. Recommended Path to Parity
To bring the Rust dialog in line with the original:
1. **Implement the missing methods** (`Seed`, `Nip46`, `Nsigner`) in
[`src/login_dialog.rs`](../src/login_dialog.rs) and
[`src/key_store.rs`](../src/key_store.rs) (`key_store_create_signer` already
returns `None` for these — see [`src/key_store.rs`](../src/key_store.rs:108)).
2. **Add a "No Login" button** that returns a `None`-method result so the browser
can run without an identity.
3. **Add a status/error label** and surface validation errors instead of
returning `None` silently.
4. **Add `nsec1...` bech32 support** to the local-key path and a
"Generate New Key" button.
5. **Switch from a dropdown to notebook tabs** to match the original's layout,
and add the per-method helper widgets (word grid, transport selector, etc.).
6. **Restore polish**: title header, monospace CSS, version in the window title,
and the agent auto-close timeout.