diff --git a/ROADMAP-identity-wallet.md b/ROADMAP-identity-wallet.md new file mode 100644 index 0000000..0b59828 --- /dev/null +++ b/ROADMAP-identity-wallet.md @@ -0,0 +1,198 @@ +# Identity + wallet roadmap + +Two independent strands, both rooted in the same BCH-style seed-with-hardened- +derivation model. Order them however makes sense to the session picking this +up; strands don't block each other. + +**Strand A — Password manager.** Local-first vault, deterministic derivation +per site+username, optional Sia backup for cross-device. +**Strand B — Browser-integrated BCH wallet.** MetaMask-style provider so +Silent Mode pages (and any BCH-native dApp) can request signatures, +payments, and BCNR name operations from the user's wallet. + +Both use the SAME seed via distinct hardened purpose subtrees so one +compromise can't leak the other: + +``` +m / 44' / 145' BCH wallet (SLIP-44) ← wallet already ships +m / 1381' / 0' passwords ← phase 1 shipped +m / 1414' / 0' messenger (Nostr etc.) ← future +``` + +Same seed, three purposes, no cross-derivation. See +[DESIGN-password-manager.md](DESIGN-password-manager.md) for the discipline. + +## Strand A — Password manager + +### A.1 — SHIPPED (0.0.3, 9c09955e) +- Local encrypted vault at `/passwords.vault` +- AES-256-GCM under PBKDF2 (200k iters, SHA-256) master-password key +- BIP39 mnemonic → seed → HKDF purpose root +- Deterministic derivation per (domain, username, version, rules) +- Settings UI: setup / locked / unlocked panels, add/edit/reveal/copy/delete +- IPC surface: pwStatus/Setup/Unlock/Lock/List/Get/Add/Update/Remove/Generate +- 12 crypto unit tests, all passing + +### A.2 — Autofill (~ 1 week) +The user-visible next step. Right now users must copy from Settings. + +- **ContentScript** injected on every page (Electron + `session.defaultSession.setPreloads` OR per-webContents preload extension) +- Watch `input[type=password]` + adjacent username field on load AND on + DOM mutation (SPAs). Debounce. +- **Origin binding via eTLD+1** using the public suffix list. Ship a + bundled snapshot of https://publicsuffix.org/list/public_suffix_list.dat + under `Argus/data/` so `evil-google.com` can't fill `google.com`. +- **Toolbar affordance**: a key icon in the address bar OR the shield's + overlay grows a "Passwords" chip when entries exist for the current + origin. Click → pick which credential to fill. +- **Keyboard shortcut**: Ctrl+Shift+L to fill the current form. +- **Save prompt**: when a form submit yields a password field the vault + hasn't seen, prompt "Save this password? (Generate strong one instead?)" +- **Per-origin permission** cached: sites that were already refused don't + re-prompt. +- Tests: origin-binding table (eTLD+1 for suffix corner cases — + `*.co.uk`, `*.compute.amazonaws.com`, IDN), autofill DOM injection + vector coverage. + +### A.3 — Sia backup / cross-device restore (~ 3-5 days) +Opt-in cloud sync of the encrypted vault blob. + +- New setting: "Sync vault to Sia". Off by default. +- User provides their own Sia S3 endpoint + credentials (like the operator's + `sia-s3.json`), OR opts to use the operator's public relay at + `sia://silentmode.st/vault/` (metadata leaks: only "this + vaultId exists", nothing else — vault is encrypted end-to-end). +- On save: upload the encrypted-blob bytes to `bucket/vault-.bin`. + Include an integrity hash in the local vault header. +- On new-device setup: user picks "Restore from Sia" → enters vaultId + + master password → download blob → decrypt. +- **Explicit non-goal**: no operational sync (last-write-wins across + devices, no merge). Phase A.3 is "clone the vault to another laptop", + not "keep two laptops perfectly in step". Multi-device concurrent-edit + is A.4 if we ever want it. + +### A.4 — Nice-to-haves +Order these however makes sense; each is small and independent. +- Import from Bitwarden JSON / KeePass CSV / Firefox CSV +- Export to same +- Password strength meter on the paste-a-password entry +- Auto-lock after N minutes of Theseus idle +- Password-history per entry (previous versions kept, so a rotation + doesn't lose the old password until the user confirms it's no longer + needed anywhere) +- Compromised-password check via HIBP k-anonymity API (opt-in, sends + a 5-char SHA-1 prefix only) +- Password-strength / composition rules from the site's `passwordrules` + attribute (WICG spec) so generated passwords satisfy site policy + +## Strand B — Browser-integrated BCH wallet + +MetaMask-style `window.bcnr` (or `window.bch`) provider that lets a page +request signatures, payments, and name operations from the user's wallet. +The wallet already exists in `site/js/` for the registrar flow; this +strand generalises it to a per-origin API for any page. + +### B.1 — Design (start here) (~ 2-3 days) + +Ship the doc before writing code. Two hard problems to nail: + +1. **Permission model**. Chrome dapp providers have taught us how easily + this becomes malware infrastructure. The doc must lock down: + - Permission granularity: per-origin, per-method, per-amount + - First-use always shows a modal (never auto-approves) + - Persistent permissions revocable in Settings > Wallet > Sites + - "You're on a BCNR name backed by an on-chain contract" vs "You're + on random-site.com" — trust badges the user can see before signing + - Anti-phishing: display the recipient's cash address in the + confirmation dialog with a big warning if the address changes + mid-session + +2. **API surface**. Match existing convention where possible so BCH dApp + developers don't learn a Silent Mode-only dialect: + - `bcnr.getAccounts()` — returns cash addresses the user has approved + for this origin + - `bcnr.requestAccount()` — first-use modal + - `bcnr.signMessage({message, address})` — BIP-137 style, used for + BCNR name registration and auth flows + - `bcnr.sendPayment({to, amount, memo})` — modal-confirmed BCH send + - `bcnr.resolveName(name)` — no permission needed; BCNR lookup + - `bcnr.registerName({name, records})`, `bcnr.updateName(name, records)` + — modal-confirmed; ties into the site/register.html flow + +Deliverable: `TheseusNavigator/DESIGN-integrated-wallet.md` in the same +shape as `DESIGN-password-manager.md`. + +### B.2 — Injection scaffold + read-only APIs (~ 1 week) + +- ContentScript that injects `window.bcnr` on every page load. Isolated + world (Electron's `contextIsolation: true` already enforces this) so + page JS can't tamper with the provider. +- Provider is an event-emitting object that speaks JSON-RPC over + postMessage to the preload, which forwards to main via IPC. +- Read-only methods (no permission needed): `resolveName`, `getBcnrTlds`, + `isRegistered`, `getRecordVersion`. +- Tests: page-script → provider → main round trip; provider surface + matches the spec. + +### B.3 — Signature + payment APIs (~ 2-3 weeks) + +- Permission-gated: `requestAccount`, `signMessage`, `sendPayment`. +- Reuse the existing wallet in `site/js/` (libauth-based) as the signing + core; wrap it in a main-process handler that reads the wallet's + `wallets.json` (already encrypted with the wallet passphrase). +- Modal confirmations rendered as a new WebContentsView overlay (same + pattern as popover/engine-picker/downloads). +- Site permissions persisted in `userData/wallet-permissions.json`. +- Settings → Wallet section: list connected sites, revoke per-site or + per-method. + +### B.4 — BCNR name operations (~ 1 week) + +- `registerName`, `updateName` — thin wrappers over the existing + `Argus/src/register.js` / `update.js` flows. +- Sanity: same modal-first pattern; show the exact record payload before + signing (users need to see what's going on-chain permanently). + +### B.5 — Nostr messenger integration (~ 2-3 weeks) + +Bring the messenger strand in as a parallel purpose. Same seed under +`m/1414'/0'`, so users who set up passwords in strand A already have +messenger keys. + +- Derive Nostr keys (secp256k1, npub/nsec encoding) from the messenger + purpose root +- `window.nostr` NIP-07 provider (widely-supported protocol, dozens of + compatible clients) +- Chrome integration: address bar chip when a Nostr-native site connects +- Threat model: Nostr keys don't hold funds, but do bind identity — + compromise = impersonation. Same modal-first permission model. + +### B.6 — Advanced: connect-wallet UX + +- QR-pairing so a mobile Ariadne can act as a hardware-wallet-like + signer for a desktop Theseus +- Multisig confirmations (2-of-3 with a hardware wallet + Theseus + + Nostr-relayed remote signer) + +## Cross-strand dependencies + +- Both strands rely on the crypto module at + `Argus/src/lib/password-vault.js`. Extract `bip39ToSeed` + + `seedToPurposeRoot` into a `seed-derive.js` module the wallet strand + can also import cleanly. +- Both strands should share the master-password unlock. If a user has + the vault unlocked and the wallet locked (or vice versa), the second + unlock should not re-prompt. + +## Non-goals + +- No third-party cloud sync. Sia is the only sync target we bless. +- No Chromium password-manager UI reuse. Ergonomics don't fit our threat + model (Google Sync-shaped). +- No Ethereum / Solana / other-chain support in Strand B. This is BCH- + specific; multi-chain is a separate future decision. +- No hardware-wallet abstraction layer in phase 1. Ledger/Trezor + integration is a phase 4 conversation. +- No autofill for credit cards, addresses, or contact forms. Each has + security nuances that deserve their own design pass. Password-only. diff --git a/SESSION-PROMPT-identity-wallet.md b/SESSION-PROMPT-identity-wallet.md new file mode 100644 index 0000000..4dfe3bb --- /dev/null +++ b/SESSION-PROMPT-identity-wallet.md @@ -0,0 +1,105 @@ +# Session kickoff — identity + wallet work + +Paste the block below into a fresh Claude Code session started in +`D:\Dev\SilentMode`. Zero prior context needed. + +--- + +You are picking up **identity + wallet** work on Silent Mode's **Theseus +Navigator** — the Electron browser with in-process BCNR resolution. Two +independent strands: **password manager** (phase 1 shipped, needs +autofill + Sia sync next) and **browser-integrated BCH wallet** (design ++ scaffold, then permissioned APIs). Both derive from the same BCH-style +seed via distinct hardened purpose subtrees. + +**Read these first, in order, before touching anything:** +1. `HANDOFF.md` — session hand-off protocol + subproject index. Learn + the `PENDING.md` per-subproject convention, the session-naming rule, + and coordination guidance for concurrent sessions. +2. `TheseusNavigator/README.md` — scope + status of the browser. +3. `TheseusNavigator/PENDING.md` — current uncommitted work, grouped by + session. Read the other session's group FIRST so you don't stomp. +4. `TheseusNavigator/GOTCHAS.md` — non-obvious traps (`build.files`, + admin-terminal-for-first-build, native-select popup theme, DNS + hosts-pin for Sia upload, etc.). Save yourself an hour. +5. `TheseusNavigator/ROADMAP-identity-wallet.md` — the full roadmap for + both strands, sorted into small deliverables. +6. `TheseusNavigator/DESIGN-password-manager.md` — the crypto discipline + + threat model. Any new derivation MUST reuse the "purpose subtree" + pattern documented there. +7. If you'll touch the wallet strand: read + `Argus/src/lib/register-tx.js` + `Argus/src/lib/wallet-web.js` to see + how the existing signing works, and `site/register.html` for how the + built-in wallet UI is currently invoked from a page. +8. `SESSION-CONTEXT.md` — the product context (BCNR terminology, + resolver rules, hosting model, on-chain invariants). + +**What is already shipped (Theseus 0.0.3, `9c09955e…`):** +- Password vault + deterministic derivation (`Argus/src/lib/password-vault.js`, + 12 tests passing). +- Settings > Passwords section: setup / locked / unlocked panels; add, + reveal, copy, delete entries. No autofill yet — users copy from + Settings and paste into sites. +- Everything from earlier UX rounds: shield security badge, three-tier + engine catalog, download tracker, cache/history storage settings, + fingerprint spoofing selects, theme cards. + +**What has NOT been designed yet — flag these decisions before starting:** +- Wallet strand's permission model + API surface. Write + `TheseusNavigator/DESIGN-integrated-wallet.md` before B.2+ code. +- The autofill contentScript's origin-binding rule details (eTLD+1 via + public-suffix list — bundle the snapshot; do not fetch at runtime). +- Sia sync UX (user's own credentials vs operator relay). Design in the + A.3 write-up before code. + +**Coordination:** +- A parallel session (labeled `parallel:*` in PENDING.md) has been very + active in this repo — mobile Ariadne, VPS infrastructure, docs, and + frequent snapshot commits. Look at `git log --oneline -20` before + touching a file to know if it recently changed. Commit small and often + so your working tree isn't a shared bucket. +- Use session label `YYYY-MM-DD:identity-wallet` in `PENDING.md` for + anything you leave uncommitted. +- Deploys are one-at-a-time. Check `PENDING.md` before starting one. + +**Ship discipline (from `HANDOFF.md` and `GOTCHAS.md`):** +- Every new `WebContentsView` or `loadFile` — add the target file to + `TheseusNavigator/package.json` `build.files`. Verify present in + `dist-public/win-unpacked/resources/app.asar` after build (there's a + one-liner probe in `GOTCHAS.md`). +- Build: from `TheseusNavigator/`, `SOURCE_DATE_EPOCH= + CSC_IDENTITY_AUTO_DISCOVERY=false npm run dist`. Admin terminal only + the first time on a fresh machine (winCodeSign symlinks). +- Ship sequence: backup live to `/opt/silent-mode/dl/_prev/`, scp new + `.exe`s + manifest, `node Argus/src/lib/sia-upload.js ../site + bns/silentmode`. Rollback commands documented in `PENDING.md` under + "Last shipment". +- On-chain `releases.silentmode.bch` publishes the manifest URL, not + fixed hashes — you don't need a wallet spend to make new hashes + reachable via BCNR unless the URL itself changes. + +**Where to start (pick one, in order of user demand):** +1. **A.2 — password autofill.** The user-visible next step. Small enough + to ship in one round. Users are currently copying passwords from + Settings; autofill removes that friction. Design detail is in + `ROADMAP-identity-wallet.md`. +2. **B.1 — wallet design doc.** No code yet. Write + `DESIGN-integrated-wallet.md`, get user alignment on the permission + model, then B.2 (injection scaffold + read-only APIs). +3. **A.3 — Sia backup.** Small feature, unlocks cross-device story. + Blocks on user picking "own creds vs operator relay" default. + +**Then tell me what you want to do.** If unspecified, start with A.2 +(autofill) — it's the highest-leverage next step in Strand A and the +crypto foundation is already tested + shipped. + +**Conventions:** +- Prefix shell commands with `rtk` (user convention, see + `~/.claude/CLAUDE.md`). +- Windows + PowerShell primary; Bash tool available for POSIX. +- Chipnet only unless explicitly directed to touch mainnet. +- Never ship secrets (`Argus/wallets.json`, `sia-s3.json`, + `ca/root-ca.key`). The build enforces this; don't weaken the check. +- Never put the operator's name or email in any file. +- Recovery phrases, elevated steps, captchas, and on-chain wallet + spends are the user's to perform — hand off with exact commands. diff --git a/address-picker-preload.js b/address-picker-preload.js new file mode 100644 index 0000000..2b4eea4 --- /dev/null +++ b/address-picker-preload.js @@ -0,0 +1,8 @@ +const { contextBridge, ipcRenderer } = require("electron"); +contextBridge.exposeInMainWorld("picker", { + onSuggestions: (cb) => ipcRenderer.on("address-suggest", (_e, d) => cb(d)), + onCursor: (cb) => ipcRenderer.on("address-cursor", (_e, dir) => cb(dir)), + pick: (url) => ipcRenderer.invoke("address-pick", url), + close: () => ipcRenderer.invoke("close-address-picker"), + resize: (h) => ipcRenderer.invoke("address-picker-resize", h), +}); diff --git a/address-picker.html b/address-picker.html new file mode 100644 index 0000000..f1f3dd5 --- /dev/null +++ b/address-picker.html @@ -0,0 +1,48 @@ + + + + + + + diff --git a/chrome.html b/chrome.html index ccc5f8e..eccc7cf 100644 --- a/chrome.html +++ b/chrome.html @@ -17,10 +17,16 @@ } [hidden] { display: none !important; } body { margin: 0; height: 100vh; background: var(--bg); color: var(--ink); user-select: none; overflow: hidden; } - .tabs { display: flex; align-items: flex-end; gap: 4px; padding: 6px 8px 0; height: 34px; } - .tab { display: flex; align-items: center; gap: 8px; max-width: 200px; min-width: 90px; padding: 6px 10px; + .tabs { display: flex; align-items: flex-end; gap: 4px; padding: 6px 8px 0; height: 34px; overflow: hidden; } + /* Same-size tabs: each tab claims an equal share of the row, capped at + 200px so 2 tabs don't stretch across the whole window. min-width lets + many tabs shrink cleanly while still showing a couple letters. */ + .tab { display: flex; align-items: center; gap: 8px; flex: 1 1 0; max-width: 200px; min-width: 60px; padding: 6px 10px; background: var(--surface); border: 1px solid var(--line2); border-bottom: none; border-radius: 8px 8px 0 0; - font-size: 12.5px; cursor: default; color: var(--mut); } + font-size: 12.5px; cursor: grab; color: var(--mut); user-select: none; } + .tab.dragging { opacity: .45; cursor: grabbing; } + .tab.dropbefore { box-shadow: -2px 0 0 var(--acid); } + .tab.dropafter { box-shadow: 2px 0 0 var(--acid); } .tab.active { background: var(--active); color: var(--ink); } .tab .t { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: 1; } .tab .spin { width: 10px; height: 10px; flex: none; border: 1.5px solid #ffffff2e; border-top-color: #d6ff3d; border-radius: 50%; animation: spin .7s linear infinite; } @@ -94,6 +100,11 @@ .tog .reg.switch:hover { color: #e5e7eb; background: rgba(255,255,255,.05); } .star { border: none; background: transparent; cursor: pointer; color: var(--dim); font-size: 15px; padding: 2px 4px; border-radius: 6px; } .star:hover { background: var(--line2); color: var(--ink); } .star.on { color: #ffd23d; } + /* Password-fill chip — only visible when the vault is unlocked AND the + current site has matching credentials. Count sits to the left of the key. */ + .pwchip { display: inline-flex; align-items: center; gap: 3px; border: none; background: transparent; color: #3fb950; cursor: pointer; padding: 2px 5px; border-radius: 6px; font-size: 11px; font-weight: 700; } + .pwchip:hover { background: rgba(63,185,80,.14); } + .pwchip svg { width: 14px; height: 14px; } /* search box: engine icon (click = pick engine) + a wide typing area */ .searchbox { display: flex; align-items: center; width: 300px; background: var(--surface); border: 1px solid var(--line); border-radius: 999px; padding: 0 12px 0 3px; } @@ -122,6 +133,18 @@ .bm .bx { opacity: 0; cursor: pointer; font-size: 11px; } .bm:hover .bx { opacity: .55; } .bm .bx:hover { opacity: 1; color: #f6768a; } .bm-empty { color: var(--faint); font-size: 11.5px; white-space: nowrap; } + /* Right-click menu on the bookmarks bar (same visual as settings ctxmenu). */ + .ctxmenu { position: fixed; z-index: 9999; background: #1c222c; border: 1px solid var(--line); + border-radius: 8px; box-shadow: 0 12px 34px #000c; padding: 4px; min-width: 180px; font-size: 13px; color: var(--ink); } + .ctxmenu .mi { padding: 7px 12px; border-radius: 5px; cursor: pointer; white-space: nowrap; } + .ctxmenu .mi:hover { background: #ffffff10; } + .ctxmenu .mi.danger { color: #f6768a; } .ctxmenu .mi.danger:hover { background: rgba(246,118,138,.12); } + .ctxmenu .mi.off { color: var(--faint); cursor: default; } .ctxmenu .mi.off:hover { background: transparent; } + .ctxmenu .sep { height: 1px; background: var(--line); margin: 4px 0; } + @media (prefers-color-scheme: light) { + .ctxmenu { background: #ffffff; border-color: rgba(0,0,0,.15); } + .ctxmenu .mi:hover { background: rgba(0,0,0,.05); } + } .tordisc { font-size: 11.5px; color: #d9c7f2; background: #2a1c40; border-top: 1px solid #6b3fa055; padding: 5px 14px; } .tordisc a { color: #d6ff3d; } .bcnrbar { display: flex; align-items: center; gap: 10px; font-size: 11.5px; color: var(--mut); @@ -154,6 +177,7 @@ +