9.9 KiB
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 - Rust:
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:
- Rust only accepts hex, not
nsec1...bech32. - Rust has no key generation button.
- Rust masks the input as a password; the original shows it in plain text.
- 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'/'/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(theSeedarm is not handled in the match)
Key differences:
- Rust does not implement seed-phrase login at all (falls through to
None). - 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...(vianostr_url_normalize) or hex - ❌ No hint text
- ❌ No inline validation/error messages
Key differences:
- Rust supports this method (the only one besides local that works).
- 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
Nip46arm is not handled in the match, returnsNone - ❌ No hint text, no URL validation
Key differences:
- 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)
- Serial: "Device Path:" /
- 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'/'/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
Nsignerarm is not handled in the match, returnsNone - ❌ No transport selector, no service/role/index fields, no device detection
Key differences:
- Rust does not implement n_signer login at all.
- 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:
- Only 2 of 5 methods work —
Seed,Nip46, andNsignerare listed in the dropdown but returnNonewhen selected. The original implements all five. - No "No Login" option — the original lets users browse without a Nostr identity; the Rust version has no equivalent.
- No error feedback — the original shows inline validation/error messages;
the Rust version silently returns
Noneon any failure, giving the user no indication of what went wrong. - Reduced local-key support — Rust only accepts hex private keys (no
nsec1...bech32) and has no "Generate New Key" button. - 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.
- 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:
- Implement the missing methods (
Seed,Nip46,Nsigner) insrc/login_dialog.rsandsrc/key_store.rs(key_store_create_signeralready returnsNonefor these — seesrc/key_store.rs). - Add a "No Login" button that returns a
None-method result so the browser can run without an identity. - Add a status/error label and surface validation errors instead of
returning
Nonesilently. - Add
nsec1...bech32 support to the local-key path and a "Generate New Key" button. - 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.).
- Restore polish: title header, monospace CSS, version in the window title, and the agent auto-close timeout.