437 lines
21 KiB
Markdown
437 lines
21 KiB
Markdown
|
|
# Theseus wallet — multi-account amendment
|
|||
|
|
|
|||
|
|
Amendment to [`DESIGN-integrated-wallet.md`](DESIGN-integrated-wallet.md) §0
|
|||
|
|
("Multi-account UX. One account per Theseus profile in v1."). Proposes lifting
|
|||
|
|
that restriction under a specific discipline that keeps the single-seed HKDF
|
|||
|
|
purpose-subtree model intact.
|
|||
|
|
|
|||
|
|
Status: **draft** — not accepted, not scheduled. Written 2026-09-09 as the
|
|||
|
|
formal path to "Theseus recognises the master password and shows all of my
|
|||
|
|
existing wallets, including externally-generated ones", raised while migrating
|
|||
|
|
the Deviant `Keys/chipnet-keystore.json` to master-password encryption.
|
|||
|
|
|
|||
|
|
Companion documents:
|
|||
|
|
- [`DESIGN-integrated-wallet.md`](DESIGN-integrated-wallet.md) — the v1 design this amends
|
|||
|
|
- [`DESIGN-password-manager.md`](DESIGN-password-manager.md) — the crypto discipline (PBKDF2, HKDF, purpose subtrees)
|
|||
|
|
- [`../Argus/src/lib/password-vault.js`](../Argus/src/lib/password-vault.js) — the implemented vault primitives
|
|||
|
|
- [`../Argus/src/lib/wallet.js`](../Argus/src/lib/wallet.js) — the just-landed cross-repo loader (Option A) that resolves `loadWallet('sirius.x')` from either SilentMode's own `wallets.json` or Deviant's encrypted `Keys/chipnet-keystore.json`
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 0. Why amend now
|
|||
|
|
|
|||
|
|
Three facts changed since §0 was written:
|
|||
|
|
|
|||
|
|
1. **The user has 15 chipnet wallets already** — 5 HD seeds plus 10 single-key
|
|||
|
|
WIFs — spread across Deviant (`Keys/chipnet-keystore.json`, encrypted with
|
|||
|
|
PBKDF2/AES-256-GCM and the master password) and SilentMode
|
|||
|
|
(`Argus/wallets.json`, plaintext). Consolidating them under Theseus is the
|
|||
|
|
natural next step now that both projects use the same master password.
|
|||
|
|
2. **`password-vault.js` already ships** the primitives that make this cheap:
|
|||
|
|
`unlockVault(path, pw)` returns a state object; `seedToPurposeRoot(seed,
|
|||
|
|
purpose)` derives an HKDF subtree deterministically; `bytesToHex/hexToBytes`
|
|||
|
|
are the wire format. There is no new crypto to design — only new state to
|
|||
|
|
serialise.
|
|||
|
|
3. **The Deviant wallet dApp (`shells/app/`) already has multi-account UX**,
|
|||
|
|
including `MultiKeyStore.WalletSummary { id, label, network, cashaddr, kind:
|
|||
|
|
'independent' | 'hd-account' }`. The alternative to amending Theseus is to
|
|||
|
|
host that dApp inside Theseus. This document argues the amendment is
|
|||
|
|
*additionally* worth doing, not a replacement for that path.
|
|||
|
|
|
|||
|
|
The core question §0 punted on was not "is multi-account complicated?" — it was
|
|||
|
|
"does supporting external key material contaminate the single-seed model?"
|
|||
|
|
This document answers: **no, if we keep external material in a distinct storage
|
|||
|
|
lane with a separate origin tier in every UI surface.**
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 1. What "multi-account" means here — three distinct notions
|
|||
|
|
|
|||
|
|
We need to keep these apart because they have different threat models:
|
|||
|
|
|
|||
|
|
| Kind | Storage | Origin | Recovery | Example |
|
|||
|
|
|---|---|---|---|---|
|
|||
|
|
| **Primary account (unchanged)** | HKDF subtree under `silentmode/wallet/0/<index>` | Derived from the profile's single seed | Recover the seed → recover every primary account | The wallet a fresh Theseus profile creates for you |
|
|||
|
|
| **Primary sub-accounts (new)** | HKDF subtree at index N under the same purpose root | Same seed | Same seed | "Trading", "Savings", "GMX game bets" — different indexes off the same profile seed |
|
|||
|
|
| **External accounts (new)** | Separately encrypted blob keyed by the same master password | Independent (imported seed OR raw WIF) | Recover the *external material* — the profile seed does NOT recover them | Deviant's `sirius.x`, `bns-beacon`, the a-g throughput test WIFs |
|
|||
|
|
|
|||
|
|
The confusion §0 avoided was mixing categories 1 and 3 in one bag. The
|
|||
|
|
amendment keeps them **in separate files** on disk. The user sees one unified
|
|||
|
|
UI, the crypto keeps them completely isolated.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 2. Threat-model deltas from §1 of the base doc
|
|||
|
|
|
|||
|
|
### 2.1 New surface: importing external key material
|
|||
|
|
|
|||
|
|
Every import path is a potential exfiltration event. Three attack shapes:
|
|||
|
|
|
|||
|
|
- **Shoulder-surf on paste** — someone reads the seed / WIF as it's typed or
|
|||
|
|
pasted. Mitigation: never render the imported string; the modal shows only a
|
|||
|
|
masked derived cashaddr after the import completes ("Import a wallet ending
|
|||
|
|
in …abc123?"). No "show me the WIF I just imported" affordance.
|
|||
|
|
- **Import from clipboard by malicious page** — a page holding
|
|||
|
|
`navigator.clipboard.readText()` permission could feed material INTO the
|
|||
|
|
import flow. Mitigation: **import is a settings-page action only**, never
|
|||
|
|
reachable from `window.bcnr` or WizardConnect. There is no
|
|||
|
|
`bcnr.importWallet()` method. Ever.
|
|||
|
|
- **Import from disk by malicious extension** — Theseus itself can't fully
|
|||
|
|
defend against this (§1.4 base doc), but the imported material lives in a
|
|||
|
|
separately-encrypted file so a compromised in-memory purpose-root leak does
|
|||
|
|
not leak the imports.
|
|||
|
|
|
|||
|
|
### 2.2 New surface: rendering many wallets in one UI
|
|||
|
|
|
|||
|
|
The user's clicks now cost more, because a wrong-account signature is
|
|||
|
|
irrecoverable. Mitigations:
|
|||
|
|
|
|||
|
|
- **Explicit signer selection per sign-request.** The sign modal shows
|
|||
|
|
`Signing as: [Sirius.x · BNS name buyer ▾]` with the account picker OPEN by
|
|||
|
|
default (not collapsed). If a page requested a specific account via
|
|||
|
|
`bcnr.requestAccount({ account: 'sirius.x' })`, the modal still shows every
|
|||
|
|
account the user could have chosen — the requested one is pre-selected and
|
|||
|
|
the picker collapsed, but expanding it is one click.
|
|||
|
|
- **Category badges everywhere the account appears.** The wallet metadata
|
|||
|
|
already carries a `category` field (`bns`, `bns-infra`, `chipnet-test`,
|
|||
|
|
`operational`, …). The modal chip is coloured by category so `sirius.x`
|
|||
|
|
(BNS) is visually distinct from `gmx` (operational) even if the user only
|
|||
|
|
glanced at the modal.
|
|||
|
|
- **"Recently used" is per-origin.** `deviant.bch` remembers which account you
|
|||
|
|
last signed with — but it does not silently reuse it; it pre-selects it in
|
|||
|
|
the modal, and a category change ("last time you used a `bns` account here,
|
|||
|
|
now the page wants an `operational` one") gets a red warning banner.
|
|||
|
|
|
|||
|
|
### 2.3 What we still do NOT defend against (unchanged from base doc §1.4)
|
|||
|
|
|
|||
|
|
Imports of a seed the user already leaked, RCE on Theseus itself, keylogger.
|
|||
|
|
These remain out-of-scope; the amendment does not widen the scope, only the
|
|||
|
|
attack surface within it.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 3. Storage model
|
|||
|
|
|
|||
|
|
### 3.1 The primary vault stays as designed
|
|||
|
|
|
|||
|
|
`<userData>/wallet.enc` (from `DESIGN-integrated-wallet.md` §6) holds the
|
|||
|
|
profile's single seed. Every primary sub-account is derived on demand via
|
|||
|
|
`seedToPurposeRoot(seed, "wallet/0")` → BIP32-style index. Nothing about the
|
|||
|
|
existing design changes.
|
|||
|
|
|
|||
|
|
### 3.2 New file: `<userData>/wallet-imports.enc`
|
|||
|
|
|
|||
|
|
Same crypto envelope as `wallet.enc` (PBKDF2-SHA256 → AES-256-GCM), same
|
|||
|
|
master password, separate KDF salt and IV. This is the ONLY place external key
|
|||
|
|
material lives. Format after decryption:
|
|||
|
|
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"version": "silentmode/wallet-imports/1",
|
|||
|
|
"generated": "2026-09-09T12:00:00Z",
|
|||
|
|
"accounts": {
|
|||
|
|
"sirius-x": {
|
|||
|
|
"kind": "seed",
|
|||
|
|
"seed": "…",
|
|||
|
|
"path": "m/44'/145'/0'/0/0",
|
|||
|
|
"cashaddr": "bchtest:qzy92wznza359wzqz5r732wylfyrgl4djsl2agdp0p",
|
|||
|
|
"label": "Sirius.x · BNS name buyer",
|
|||
|
|
"category": "bns",
|
|||
|
|
"source": "imported-from-D:/Dev/Deviant/Keys/chipnet-keystore.json#sirius-x",
|
|||
|
|
"createdAt": 1725840000000
|
|||
|
|
},
|
|||
|
|
"gmx": {
|
|||
|
|
"kind": "wif",
|
|||
|
|
"wif": "…",
|
|||
|
|
"cashaddr": "bchtest:qz040z8hza8n96llrk5kewp5dmyr5rd7csj8rasu9d",
|
|||
|
|
"label": "GMX platform operator",
|
|||
|
|
"category": "operational",
|
|||
|
|
"source": "imported-from-D:/Dev/Deviant/Keys/chipnet-keystore.json#gmx",
|
|||
|
|
"createdAt": 1725840000001
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
The `accounts` shape deliberately mirrors the fields already used by Deviant's
|
|||
|
|
`chipnet-keystore/1` format (`cashaddr`, `label`, `category`, `source`, `path`,
|
|||
|
|
`seed`, `wif`) so a Deviant → Theseus import is a one-to-one field mapping,
|
|||
|
|
not a schema translation.
|
|||
|
|
|
|||
|
|
**Why a separate file, not one file with two sections?** Blast radius. A bug
|
|||
|
|
that corrupts `wallet-imports.enc` cannot destroy the primary seed. A revoke
|
|||
|
|
"remove all imports" clears one file — the primary vault is untouched. This
|
|||
|
|
is the same discipline `DESIGN-password-manager.md` uses to keep the password
|
|||
|
|
vault distinct from the seed vault: distinct files, same master password.
|
|||
|
|
|
|||
|
|
### 3.3 One-way pointer: primary → imports, never the other way
|
|||
|
|
|
|||
|
|
The primary vault gets a new small field:
|
|||
|
|
|
|||
|
|
```json
|
|||
|
|
{ …existing wallet.enc payload…, "importsExist": true }
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
...updated on every import/revoke. Loading the primary vault tells Theseus
|
|||
|
|
whether it should also try to decrypt `wallet-imports.enc`. The **imports
|
|||
|
|
file NEVER references the primary** — an attacker who exfiltrates
|
|||
|
|
`wallet-imports.enc` learns nothing about the profile seed, not even its
|
|||
|
|
existence.
|
|||
|
|
|
|||
|
|
### 3.4 What the master password unlocks in one session
|
|||
|
|
|
|||
|
|
On `unlockVault(masterPw)`:
|
|||
|
|
|
|||
|
|
1. Derive `vaultKey` (PBKDF2 with `wallet.enc`'s salt).
|
|||
|
|
2. Decrypt `wallet.enc` → get profile seed → derive `purposes/wallet/0`,
|
|||
|
|
`purposes/passwords/0`, `purposes/messenger/0`.
|
|||
|
|
3. If `importsExist`: derive `vaultKey'` (PBKDF2 with
|
|||
|
|
`wallet-imports.enc`'s DIFFERENT salt — deliberately not the same key
|
|||
|
|
material) and decrypt.
|
|||
|
|
|
|||
|
|
Both keys are held in RAM for the session; both are wiped on auto-lock. A
|
|||
|
|
malicious page that gets an in-memory read of the wallet purpose root learns
|
|||
|
|
nothing about imports.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 4. UX
|
|||
|
|
|
|||
|
|
### 4.1 Settings > Wallet — the accounts panel
|
|||
|
|
|
|||
|
|
Replaces the base doc's "Account row: address, balance, backup phrase".
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
── Accounts ─────────────────────────────────────────────────────
|
|||
|
|
[+ Create sub-account] [+ Import external wallet]
|
|||
|
|
|
|||
|
|
● Primary [rename ▎revoke]
|
|||
|
|
bchtest:qxxx…yyy · category "primary"
|
|||
|
|
|
|||
|
|
● Trading (primary sub-account #1) [rename ▎revoke]
|
|||
|
|
bchtest:qaaa…bbb · category "primary"
|
|||
|
|
|
|||
|
|
── Imported ─────────────────────────────────────────────────
|
|||
|
|
● Sirius.x · BNS name buyer [rename ▎revoke]
|
|||
|
|
bchtest:qzy…dp0p · category "bns"
|
|||
|
|
source: Deviant/Keys/chipnet-keystore.json#sirius-x
|
|||
|
|
|
|||
|
|
● BNS TLD Beacon (.asm/.x) [rename ▎revoke]
|
|||
|
|
bchtest:qzc…pmerh · category "bns-infra"
|
|||
|
|
source: Deviant/Keys/chipnet-keystore.json#bns-tld-beacon
|
|||
|
|
|
|||
|
|
● GMX platform operator [rename ▎revoke]
|
|||
|
|
bchtest:qz0…rasu9d · category "operational"
|
|||
|
|
source: Deviant/Keys/chipnet-keystore.json#gmx
|
|||
|
|
─────────────────────────────────────────────────────────────────
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- Primary and sub-accounts always appear above imports. This is not just
|
|||
|
|
cosmetic — it reflects that the profile seed is the wallet's identity.
|
|||
|
|
- Imports are grouped by `category` when there are more than 5. `bns`,
|
|||
|
|
`bns-infra`, `operational`, `chipnet-test` collapse into collapsible groups.
|
|||
|
|
- Revoke removes an import (from `wallet-imports.enc`); it does NOT touch the
|
|||
|
|
on-chain material and warns clearly: "The on-chain wallet still exists. Its
|
|||
|
|
seed/WIF is only removed from this Theseus profile."
|
|||
|
|
- **No affordance to view seed / WIF material after import.** Recovery lives
|
|||
|
|
in the SOURCE (Deviant's keystore, or wherever the user got it from). §2.1
|
|||
|
|
shoulder-surf mitigation.
|
|||
|
|
|
|||
|
|
### 4.2 Sign-request modal — the account picker
|
|||
|
|
|
|||
|
|
Same modal as base doc §5, extended:
|
|||
|
|
|
|||
|
|
- Account row becomes `Signing as: [dropdown ▾]`.
|
|||
|
|
- Dropdown lists every unlocked account, grouped: Primary → sub-accounts → imports.
|
|||
|
|
- Each row shows label + truncated cashaddr + category chip.
|
|||
|
|
- Origin's "recently used" account is pre-selected (§2.2 above).
|
|||
|
|
- If the calling page passed `bcnr.requestAccount({ account: 'sirius.x' })`
|
|||
|
|
and the account exists, it's pre-selected. If it doesn't exist, the modal
|
|||
|
|
says "This site is asking for an account you don't have. Sign as:" and the
|
|||
|
|
user picks from what they do have.
|
|||
|
|
|
|||
|
|
### 4.3 Import flow
|
|||
|
|
|
|||
|
|
Two entry points, both in Settings > Wallet:
|
|||
|
|
|
|||
|
|
1. **"Import external wallet" → paste seed OR WIF.**
|
|||
|
|
The pasted string never renders back; the modal shows only what will be
|
|||
|
|
stored (`kind`, derived `cashaddr` truncated, `label` input, `category`
|
|||
|
|
picker).
|
|||
|
|
|
|||
|
|
2. **"Import from Deviant keystore" (if `D:/Dev/Deviant/Keys/chipnet-keystore.json`
|
|||
|
|
is reachable).** Detects the file, prompts for its master password (which
|
|||
|
|
must equal Theseus's — refuses otherwise with a clear message: "The Deviant
|
|||
|
|
keystore uses a different master password. Change one of them first."),
|
|||
|
|
decrypts, shows a preview list of every entry (label + cashaddr + category
|
|||
|
|
only), user picks which to import. Bulk-select shortcuts by category
|
|||
|
|
("all `bns`", "all `operational`").
|
|||
|
|
|
|||
|
|
The "different master password" rejection is a deliberate ergonomic
|
|||
|
|
constraint: we choose the pain of "unify your passwords once" over the pain
|
|||
|
|
of "which password unlocks what in this profile".
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 5. API surface for pages (deltas from §3 of base doc)
|
|||
|
|
|
|||
|
|
Backwards-compatible additions only:
|
|||
|
|
|
|||
|
|
```ts
|
|||
|
|
bcnr.requestAccount({ account?: string, category?: string }): Promise<string | null>
|
|||
|
|
// If neither `account` nor `category` given: current behaviour (returns cashaddr
|
|||
|
|
// of the default account, or null if not granted).
|
|||
|
|
// If `account`: requests a specific labelled account. Modal pre-selects it; if
|
|||
|
|
// it doesn't exist, the modal falls back to a picker over what the user has.
|
|||
|
|
// If `category`: requests any account in that category (e.g. "bns"). Useful
|
|||
|
|
// for the SilentMode registrar UI which knows it wants a BNS-holding wallet
|
|||
|
|
// but not which one.
|
|||
|
|
|
|||
|
|
bcnr.getAccounts({ category?: string }): Promise<Array<{
|
|||
|
|
cashaddr: string;
|
|||
|
|
label: string;
|
|||
|
|
category: string;
|
|||
|
|
}>>
|
|||
|
|
// Returns granted accounts. Optionally filtered by category. Note: the user
|
|||
|
|
// grants per-account, not per-category — this method never leaks accounts the
|
|||
|
|
// origin was not granted access to.
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
The `bcnr.signMessage` / `bcnr.sendPayment` / `bcnr.registerName` signatures
|
|||
|
|
gain an optional `account?: string` parameter that pre-selects an account in
|
|||
|
|
the modal. Non-passing preserves current behaviour.
|
|||
|
|
|
|||
|
|
**Not added:** no method to import, revoke, or rename. All state-modifying
|
|||
|
|
account operations are settings-page only (§2.1).
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 6. Compatibility with Option A (the cross-repo loader that shipped 2026-09-09)
|
|||
|
|
|
|||
|
|
Option A lets `Argus/src/lib/wallet.js:loadWallet('sirius.x')` transparently
|
|||
|
|
read Deviant's encrypted keystore. That path is authoritative today and stays
|
|||
|
|
authoritative after this amendment ships:
|
|||
|
|
|
|||
|
|
- Theseus imports are **copies**, not sources of truth. Revoking an import in
|
|||
|
|
Theseus does not affect Deviant's keystore or SilentMode's `wallets.json`.
|
|||
|
|
- If the user rotates a key in Deviant's keystore, the corresponding Theseus
|
|||
|
|
import goes stale. Theseus should detect this by comparing derived
|
|||
|
|
`cashaddr` on unlock — a mismatch surfaces a banner: "The Deviant source of
|
|||
|
|
Sirius.x has changed since this import. [Re-import] or [Remove]."
|
|||
|
|
- The two systems remain independent by design. This is not a bug; it is the
|
|||
|
|
same discipline that keeps password-vault seeds separate from wallet seeds
|
|||
|
|
(§3.4 base doc).
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 7. Sequencing — where this slots into the base roadmap
|
|||
|
|
|
|||
|
|
Adds to the base doc's §7:
|
|||
|
|
|
|||
|
|
| Phase | What ships | Depends on |
|
|||
|
|
|---|---|---|
|
|||
|
|
| B.3d | Sub-account creation (primary purpose subtree, indexes 1..N) — `Trading` / `Savings` labels | B.3a shipped |
|
|||
|
|
| **M.1** | Import external seed / WIF, `wallet-imports.enc` file, account picker in sign modal | B.3b + `password-vault.js` shipped ✓ |
|
|||
|
|
| **M.2** | Import from Deviant keystore (file detection + bulk-select UX) | M.1 + `chipnet-keystore/2-encrypted` format stable ✓ |
|
|||
|
|
| **M.3** | `bcnr.requestAccount({ account, category })` + `getAccounts({ category })` API extensions | M.1 |
|
|||
|
|
| **M.4** | Stale-import detection (source-file cashaddr comparison) | M.2 |
|
|||
|
|
|
|||
|
|
M.1 is the load-bearing phase — everything else composes on top of the
|
|||
|
|
`wallet-imports.enc` file. M.2 through M.4 are refinements the user's specific
|
|||
|
|
setup makes worthwhile.
|
|||
|
|
|
|||
|
|
Do NOT ship M.2 (Deviant integration) before M.1 (generic import). The
|
|||
|
|
generic-first path keeps the design honest — if the amendment cannot be used
|
|||
|
|
by a user who never touches Deviant, we've over-fitted.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 8. Open questions — decide before writing M.1 code
|
|||
|
|
|
|||
|
|
1. **What's the primary account's "category"?** Options: `primary` (mirrors
|
|||
|
|
the model), leave the field null, or use the user-picked purpose ("Personal
|
|||
|
|
spending"). *Recommend `primary` — it's the one category name that never
|
|||
|
|
collides with an import.*
|
|||
|
|
|
|||
|
|
2. **How does WizardConnect surface multi-account?** External pairings via
|
|||
|
|
`wiz://` — the `@wizardconnect/wallet@0.2.2` SDK's WalletAdapter has one
|
|||
|
|
`getXpub(path)` and one `signTransaction(request)`. Neither takes an
|
|||
|
|
account parameter. Options: pair-per-account (each account has its own
|
|||
|
|
`wiz://` URI); pair-per-profile with account selection at sign time (like
|
|||
|
|
in-page dApps); or accept that WizardConnect only exposes the primary
|
|||
|
|
account. *Recommend option 3 for M.1 (only-primary), revisit when a user
|
|||
|
|
asks. Multi-account WizardConnect is a real cross-cutting design that
|
|||
|
|
deserves its own doc.*
|
|||
|
|
|
|||
|
|
3. **Do we let the profile primary seed be *replaced* by an import?** The
|
|||
|
|
proposal above says no — imports are always in the imports file, and the
|
|||
|
|
primary purpose subtrees stay tied to the profile seed. A user who wants
|
|||
|
|
"my Deviant seed as the profile seed" would create a fresh profile and
|
|||
|
|
restore that seed at profile-creation. *Recommend keeping the restriction
|
|||
|
|
— it makes the sub-account / import distinction fundamental rather than
|
|||
|
|
optional.*
|
|||
|
|
|
|||
|
|
4. **Auto-lock behaviour with imports.** Auto-lock re-encrypts the primary
|
|||
|
|
purpose root under a session key (§6 base doc). Do we do the same for the
|
|||
|
|
imports? Or wipe the imports outright and force a re-decrypt on unlock?
|
|||
|
|
*Recommend the same treatment as primary — session-key re-encryption. The
|
|||
|
|
cost is symmetric.*
|
|||
|
|
|
|||
|
|
5. **Export.** Can the user export an imported wallet's key back out of
|
|||
|
|
Theseus? *Recommend no by default (§4.1 no-view-material rule), but a
|
|||
|
|
"Show recovery WIF for this import" behind master-password reprompt AND
|
|||
|
|
"why is this dangerous" panel (like the base doc §8.6 for the profile
|
|||
|
|
seed) is a reasonable escape hatch for users who need to move a wallet
|
|||
|
|
between machines.*
|
|||
|
|
|
|||
|
|
6. **Sub-account discovery.** If a user restores their profile seed on a new
|
|||
|
|
Theseus install, do we auto-scan chipnet for used sub-account addresses?
|
|||
|
|
*Recommend a "Scan for used sub-accounts" button in settings, not
|
|||
|
|
automatic. Scanning is a chain-load event; make the user ask for it.*
|
|||
|
|
|
|||
|
|
7. **Category taxonomy.** The Deviant keystore uses `bns`, `bns-infra`,
|
|||
|
|
`chipnet-test`, `operational`, `primary`, `hd-general`. Should Theseus
|
|||
|
|
enforce a fixed set or allow free-form strings? *Recommend free-form
|
|||
|
|
strings with the six above as autocomplete suggestions. Constraining now
|
|||
|
|
would force a v2 migration when a user coins a new category.*
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 9. What this amendment does NOT decide
|
|||
|
|
|
|||
|
|
- The wallet-imports file format after v1. Version-tag it now; iterate as
|
|||
|
|
needed. The `version: "silentmode/wallet-imports/1"` field exists precisely
|
|||
|
|
so a v2 migration is a well-defined event, not a guess.
|
|||
|
|
- The Deviant-side story. Deviant's `chipnet-keystore.json` continues to be
|
|||
|
|
read by Deviant scripts (via `scripts/lib/chipnet-keystore.mjs`) and by
|
|||
|
|
SilentMode's `Argus/src/lib/wallet.js` (via the Option A fall-through). This
|
|||
|
|
amendment adds a THIRD reader (Theseus) — it does not consolidate the two
|
|||
|
|
existing readers.
|
|||
|
|
- The mobile story. Ariadne (mobile companion, base doc §9) is out of scope.
|
|||
|
|
When it lands, the same `wallet-imports.enc` format ports over — that's the
|
|||
|
|
point of designing it version-tagged and portable.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 10. Suggested acceptance path
|
|||
|
|
|
|||
|
|
If the reader agrees with this amendment:
|
|||
|
|
|
|||
|
|
1. Merge M.1 into the roadmap between B.3b and B.4 in
|
|||
|
|
`ROADMAP-identity-wallet.md`.
|
|||
|
|
2. Move the base doc's §0 "Multi-account UX. One account per Theseus profile
|
|||
|
|
in v1." to §9 "What this design does NOT decide, in v1 — this is now
|
|||
|
|
addressed by [`DESIGN-wallet-multi-account-amendment.md`](DESIGN-wallet-multi-account-amendment.md)".
|
|||
|
|
3. Address the open questions in §8 above in the roadmap discussion, not by
|
|||
|
|
editing this file — the design is stable, only the answers are open.
|
|||
|
|
|
|||
|
|
If the reader disagrees, the fallback path is what the base doc already
|
|||
|
|
implies: **host the Deviant wallet dApp inside Theseus, once `window.bcnr`
|
|||
|
|
ships** (base doc §4.4 — "External dApp using WizardConnect = Nostr relay",
|
|||
|
|
minus the Nostr relay because the dApp is loaded IN Theseus, not paired
|
|||
|
|
externally). That path requires no Theseus code changes beyond what B.2–B.4
|
|||
|
|
already deliver, and it inherits Deviant's own multi-account UX for free. The
|
|||
|
|
trade-off is that "my wallet" and "the Deviant wallet loaded in my wallet
|
|||
|
|
browser" become two things instead of one; this amendment is the case for
|
|||
|
|
one.
|