10 KiB
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 for the discipline.
Add-on roots (decided 2026-09-06, do not change). The vault never stores
the BIP-39 seed, only per-purpose roots, so add-ons cannot get a fresh
seed-level purpose after setup. api.vault.derive("<addonId>/<path>")
therefore returns HKDF-SHA256(passwords/0 root, info =
silentmode/addons/<addonId>/<path>). The bundled BCH wallet uses
bchwallet/mainnet/0 as its BIP32 master seed and derives m/44'/145'/0'
from it. This is deterministic from the mnemonic (mnemonic → seed →
passwords/0 → wallet) and works for random-seed vaults too. Changing the
info string or the parent root would orphan every wallet's funds; recovery
without Theseus is via the account xprv shown in the wallet's Settings.
The bundled Siacoin wallet follows the same rule with siawallet/mainnet/0
as the 32-byte seed for walletd's KeyFromSeed(seed, index); addresses are
standard unlock hashes. It talks to a user-configured walletd node (no
default endpoint ships in the add-on — hosted providers put an access key
in the URL path).
Strand A — Password manager
A.1 — SHIPPED (0.0.3, 9c09955e)
- Local encrypted vault at
<userData>/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.setPreloadsOR 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/soevil-google.comcan't fillgoogle.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 atsia://silentmode.st/vault/<vaultId>(metadata leaks: only "this vaultId exists", nothing else — vault is encrypted end-to-end). - On save: upload the encrypted-blob bytes to
bucket/vault-<id>.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
passwordrulesattribute (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:
-
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
-
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 originbcnr.requestAccount()— first-use modalbcnr.signMessage({message, address})— BIP-137 style, used for BCNR name registration and auth flowsbcnr.sendPayment({to, amount, memo})— modal-confirmed BCH sendbcnr.resolveName(name)— no permission needed; BCNR lookupbcnr.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.bcnron every page load. Isolated world (Electron'scontextIsolation: truealready 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'swallets.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 existingArgus/src/register.js/update.jsflows.- 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.nostrNIP-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. Extractbip39ToSeed+seedToPurposeRootinto aseed-derive.jsmodule 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.