Implement the Connection Vault protocol layer (phases A-C)

connection-vault.md 5-13 as code, with the implementation-time decisions
recorded in a new section 18 and vault-ux.md holding the agreed UX
walkthroughs as the authorization layer's acceptance criteria.

Capsules gain paired optional fields connection_vault_root and
vault_sudo_key (both or neither, validated); registration mints them,
login/change-password/recovery carry them forward, and
enableConnectionVault is the phrase-gated migration for existing
accounts -- recovery capsule written first, fail closed, idempotent by
reusing roots found in either capsule so a partial failure can never
mint twice and strand records.

src/vault/ holds the finalized key hierarchy (HKDF/ScalarExpand
constants pinned by test vectors that must only ever be reverted, never
updated), per-connection encrypted records signed by a derived vault
identity with the capsule JCS+padding+AES-GCM pipeline and shared
buckets so record types are indistinguishable by size, the NWC profile
with lossless URI parse/export, relay sync reusing the publish-and-
verify quorum bar, and per-record high-water marks so a stale replica
is withheld and reported rather than silently resurrecting a revoked
credential.

The two-tier model makes sudo mode cryptographically real: personal-
tier record keys require vault_sudo_key as HKDF input, VaultSession
holds it only inside an enableSudo/endSudo window (wiping on the way in
and out), and outside a window personal records are invisible -- the
same trial-decrypt returns null, not an error. The app-facing API is
structurally blind to the tier by construction.

118 tests passing, including four end-to-end scenarios against mock
relays: clean-device restore, password-change carry, legacy-account
migration (wrong phrase refused), and rollback detection.

Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01CMr9Yt8SYmZcfgrg3CV2G1
This commit is contained in:
thePR0M3TH3ANandClaude Fable 5 committed 2026-07-30 12:29:46 -04:00
1 parent e9a5a41ff7
commit a465e1d8fa
22 files changed
+1843 -12

No files matched your search

+99
View File
@@ -0,0 +1,99 @@
# Connection Vault — UX walkthroughs
**Status:** Agreed product direction (2026-07-30)
**Role:** These scenarios are the acceptance criteria for the Phase D
authorization layer (`connection-vault.md` §12, §18). The design principle
running through all of them: **friction appears exactly where danger is, and
nowhere else.** Count the friction across the whole set: one phrase entry
ever, one budget-facing tap per app, one tap per new device, sudo or phone
approval only at genuinely dangerous reveals. Everything else is silence.
## 1. Turning the vault on — existing account
Account manager card: *"Connection Vault — store wallet and storage
connections once, use them on every device."* Enabling asks for the recovery
phrase with the reason stated plainly (*"the vault must survive both your
password and your phrase"*), rewrites both capsules with quorum readback, and
fails closed with a retry that reuses the same roots
(`enableConnectionVault`). New accounts skip this scenario entirely — the
roots are minted at registration.
*Deliberate friction:* the phrase entry, once per account ever.
## 2. Connecting a wallet the first time — inside an app
The app calls the request API with a reason string. The sheet walks the user
through pasting/scanning an NWC URI, parses it, and shows what it learned —
provider, relay, and **the budget in large type**. No budget draws an amber
warning recommending a budgeted connection, never a hard block. Label
pre-filled from the app name. The final line before reveal is honest:
*"[App] will receive this connection and can spend within its budget until
you revoke it."* Save binds the record to the requesting origin.
*Deliberate friction:* one explicit share tap, budget staring at the user.
*Removed friction:* naming, binding, storage.
## 3. The payoff — new phone
Sign into the app's BitLogin, and at the first payment moment:
> **Use "Satisfied spending wallet"?**
> *You connected this wallet to Satisfied on another device. Budget: 500 sats/day.*
One tap. Zero pasting. This screen is the entire reason the vault exists.
A new device seeing a credential for the first time must never happen
silently, even with a durable grant.
## 4. Every ordinary day after
Nothing. No prompt, no branding. The grant is durable per origin per device;
the vault's best UX is absence.
## 5. Connected apps — management
Account manager page listing origin → connection → grant date → devices, with
**Revoke access** per row and one **"Revoke all app access"** panic button.
Each connection's detail page separates two red actions honestly:
- **Remove from BitLogin** — deletes the stored copy; the credential itself
keeps working for anyone who already holds it.
- **Revoke at wallet/provider** — an instruction with a guide, not a button
pretending BitLogin can reach into the wallet (§11).
## 6. S3 connections
Same sheet pattern; the import screen teaches while it collects: scoped IAM
key vs root key question, a recommended object-prefix field, amber warning on
unscoped credentials. Reveal to an app requires sudo mode — S3 secrets guard
buckets, not a 500-sat budget.
## 7. Personal tier — passwords and secure notes
Exists ONLY in the first-party account manager. No app can request, list, or
learn these records exist (the tier is invisible to the app-facing API by
construction — see §18.2). Viewing requires sudo mode: re-enter the account
password, ~5-minute window. Copy button, auto-clearing field. The copy is
honest: a safe place for a few important secrets, not a password manager —
autofill and site-matching wait for the extension generation. Seed phrases
stay banned.
## 8. Phone approval (after §SF5–7 ships)
High-tier reveals push to the enrolled phone; the record key is 2-of-2
derived so a laptop cryptographically cannot proceed on its own. Budgeted NWC
connections deliberately do NOT phone-prompt — the budget is already the
lock, and prompting every payment trains reflexive approval.
## 9. Disaster — phrase-only recovery
Twelve words on a clean device restore identity, then the vault roots ride
the recovery capsule (§13.2), so connections reappear: wallets, buckets,
passwords. The phrase remains the ungated master path (§SF9). The flow nudges
re-enrolling a phone and rotating anything sensitive.
## Build order for Phase D
The screens, in dependency order: request/consent sheet (2, 3) → guided NWC
import (2) → Connected apps + connection detail (5) → S3 import (6) → sudo
mode (7). The personal tier and phone approval layer on later without
reshaping anything before them.