mirror of
https://github.com/PR0M3TH3AN/bitlogin.git
synced 2026-10-27 08:31:21 +00:00
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:
1 parent
e9a5a41ff7
commit
a465e1d8fa
22 files changed
+1843
-12
No files matched your search
+10
-5
@@ -33,14 +33,19 @@ identity and recovery model.
|
||||
|
||||
## Connection Vault
|
||||
|
||||
The Connection Vault is an experimental design for portable application
|
||||
credentials. It deliberately stores mutable credentials outside the core
|
||||
identity capsules as independently encrypted records.
|
||||
The Connection Vault stores portable application credentials outside the core
|
||||
identity capsules as independently encrypted records. Its protocol layer
|
||||
(records, key hierarchy, relay sync, migration, tiers) is **implemented in
|
||||
`@bitlogin/core`'s `src/vault/`**; the widget authorization layer (consent
|
||||
UI, grants, Connected apps) is still to come.
|
||||
|
||||
- [`connection-vault.md`](./connection-vault.md) — overall vault architecture,
|
||||
key hierarchy, relay storage, authorization, recovery, and deletion
|
||||
key hierarchy, relay storage, authorization, recovery, and deletion; §18
|
||||
records the implementation-time decisions (tier model, sudo key)
|
||||
- [`vault-ux.md`](./vault-ux.md) — the agreed user-experience walkthroughs,
|
||||
serving as the authorization layer's acceptance criteria
|
||||
- [`nwc-connections.md`](./nwc-connections.md) — Nostr Wallet Connect credential
|
||||
profile
|
||||
profile (parser/exporter implemented in `src/vault/nwc.ts`)
|
||||
- [`s3c-format.md`](./s3c-format.md) — portable `s3c1:` format for S3-compatible
|
||||
storage connections
|
||||
|
||||
|
||||
@@ -1,9 +1,18 @@
|
||||
# BitLogin Connection Vault
|
||||
|
||||
**Status:** Experimental design
|
||||
**Document version:** 0.1
|
||||
**Date:** July 22, 2026
|
||||
**Implementation status:** Not implemented
|
||||
**Status:** Design with core implementation
|
||||
**Document version:** 0.2
|
||||
**Date:** July 30, 2026
|
||||
**Implementation status:** §5–§11 and §13 are **implemented in `@bitlogin/core`**
|
||||
(`src/vault/`, plus the capsule fields in `src/capsules/` and the migration in
|
||||
`src/account/enableVault.ts`), with test vectors pinned in
|
||||
`src/vault/vault.test.ts` and protocol round trips in `vault.e2e.test.ts`.
|
||||
The application-authorization layer (§12: consent UI, grants, Connected-apps
|
||||
management) is **not yet implemented** — it is the widget generation's work.
|
||||
See §18 for the decisions finalized at implementation time, including the
|
||||
**sensitivity-tier model and the sudo key**, which supersede this document
|
||||
where they differ. The companion `vault-ux.md` holds the user-experience
|
||||
walkthroughs that act as Phase D's acceptance criteria.
|
||||
|
||||
## 1. Summary
|
||||
|
||||
@@ -525,3 +534,83 @@ At minimum:
|
||||
- Clearly distinguish BitLogin record deletion from provider-side revocation.
|
||||
- Assume a malicious or compromised client can steal all secrets available in
|
||||
that session.
|
||||
|
||||
## 18. Implementation-time decisions (2026-07-30, v0.2)
|
||||
|
||||
These were finalized when §5–§13 shipped in `@bitlogin/core`. Where this
|
||||
section and the earlier prose disagree, this section wins.
|
||||
|
||||
### 18.1 Two capsule fields, not one
|
||||
|
||||
The capsules gained **two** paired 32-byte roots, not just the vault root of
|
||||
§5.2:
|
||||
|
||||
```json
|
||||
{
|
||||
"connection_vault_root": "base64url-32-bytes",
|
||||
"vault_sudo_key": "base64url-32-bytes"
|
||||
}
|
||||
```
|
||||
|
||||
Validation enforces that they appear together or not at all (a capsule with
|
||||
one but not the other is corrupt and must not grant a session). Registration
|
||||
mints both for every new account; `enableConnectionVault` is the §5.3
|
||||
ceremony for accounts that predate them, requires the recovery phrase,
|
||||
writes the recovery capsule first, fails closed, and is idempotent — a retry
|
||||
after a partial failure REUSES roots found in either capsule rather than
|
||||
minting twice, because minting twice would strand existing records.
|
||||
|
||||
### 18.2 The sensitivity-tier model and why the sudo key exists
|
||||
|
||||
Records carry a `tier` field:
|
||||
|
||||
- **`connectable`** (NWC, scoped S3): record keys derive from
|
||||
`connection_vault_root` alone. The root is cacheable for the session, so
|
||||
daily wallet use is silent. These are the records the §12 grant flow may
|
||||
expose to application origins. The real security boundary is provider-side
|
||||
policy — wallet budgets, scoped IAM keys — and import UIs must push users
|
||||
toward it.
|
||||
- **`personal`** (stored passwords, secure notes): record keys additionally
|
||||
require `vault_sudo_key` as HKDF input
|
||||
(`personal_prk = HKDF-Extract(SHA256("bitlogin/connection-vault-personal/v1"), root || sudo_key)`).
|
||||
The honest-client contract: the sudo key is never persisted outside the
|
||||
capsules and is held in memory only during a sudo window
|
||||
(`VaultSession.enableSudo`/`endSudo`, which wipe it). Re-opening a window
|
||||
requires re-decrypting a capsule — a fresh Argon2id run on the password, or
|
||||
the phrase — which is what makes "re-enter your password to reveal" a real
|
||||
key ceremony rather than the cosmetic gate §SF2 forbids.
|
||||
**The app-facing API must be structurally blind to this tier**: an embedded
|
||||
widget cannot do exact-origin matching, and revealing one site's password
|
||||
to another site's page is a phishing machine with a consent screen.
|
||||
Password-manager UX (autofill, origin matching) waits for the extension
|
||||
generation. Crypto seed phrases remain banned from the vault entirely.
|
||||
|
||||
Tiers are invisible on the wire: identical envelopes, buckets, and d-tag
|
||||
shapes, decryption is trial-based. A distinguishable ciphertext would tell
|
||||
every relay which records are "the sensitive ones".
|
||||
|
||||
### 18.3 Resolved open decisions (§16)
|
||||
|
||||
1. **Capsule schema:** additive optional fields on the v1 schemas (§18.1) —
|
||||
a new schema id would lock old clients out of login entirely.
|
||||
**Mixed-version caveat:** an OLD client's password change rebuilds the
|
||||
credential capsule without the vault fields and strands connectable
|
||||
records until the next phrase ceremony repairs them. Deployments must
|
||||
update vendored widgets before enabling the vault for users.
|
||||
2. **Padding buckets:** the capsule buckets (1024/2048/4096) are reused.
|
||||
Every current profile fixture fits 1024 with room, and a shared bucket
|
||||
set keeps NWC and S3 records indistinguishable by size.
|
||||
3. **Relay policy:** vault records publish to the account's configured vault
|
||||
relays with the same publish-and-verify quorum bar as capsules
|
||||
(`publishConnectionRecord` / `fetchConnectionRecordEvents`).
|
||||
4. **App binding:** `application_binding.origin` is implemented;
|
||||
`app_pubkey` remains reserved.
|
||||
5. **Encrypted index:** not built; discovery lists the derived identity's
|
||||
kind-30078 events and filters the `bitlogin:connection:` prefix locally.
|
||||
6. **Grants:** deferred to the widget authorization layer (Phase D).
|
||||
7. **S3C repository split:** deferred until a second implementation exists.
|
||||
|
||||
Rollback resistance (§9) is per record: a local high-water mark stores the
|
||||
newest accepted `created_at` per connection id, and `fetchConnectionRecordEvents`
|
||||
withholds and reports anything older instead of serving it — a stale replica
|
||||
must never silently resurrect a revoked credential.
|
||||
@@ -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.
|
||||
Reference in new issue
Block a user