From 98a8c608214bdbe10e65650acf3f4fecdb9805c7 Mon Sep 17 00:00:00 2001 From: thePR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> Date: Wed, 22 Jul 2026 17:39:45 -0400 Subject: [PATCH] Document experimental BitLogin Connection Vault --- docs/connection-vault.md | 527 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 527 insertions(+) create mode 100644 docs/connection-vault.md diff --git a/docs/connection-vault.md b/docs/connection-vault.md new file mode 100644 index 0000000..82d8af7 --- /dev/null +++ b/docs/connection-vault.md @@ -0,0 +1,527 @@ +# BitLogin Connection Vault + +**Status:** Experimental design +**Document version:** 0.1 +**Date:** July 22, 2026 +**Implementation status:** Not implemented + +## 1. Summary + +The BitLogin Connection Vault is a proposed encrypted, relay-backed store for +portable application credentials such as: + +- Nostr Wallet Connect (NWC) connections +- S3-compatible storage credentials +- Future API tokens, signer grants, and application-specific secrets + +The Connection Vault extends BitLogin from portable identity into portable +application authorization: + +```text +BitLogin account + | + +-- portable Nostr identity + +-- portable wallet connections + +-- portable storage connections + `-- future application credentials +``` + +The vault does **not** place wallet strings, storage access keys, or other +frequently managed secrets directly inside the credential or recovery capsule. +Instead, the capsules carry only a stable vault root. Each connection is stored +as its own independently replaceable encrypted Nostr record. + +This preserves the current capsule design principle: capsules contain only +infrequently changed access material, while ordinary mutable state lives in +separate records. + +## 2. Motivation + +Nostr applications commonly ask users to repeat setup that should be portable: + +- Paste or scan an NWC URI for every wallet-enabled application. +- Re-enter an S3 endpoint, bucket, access-key ID, and secret key on every device. +- Repeat the process after clearing browser data or switching clients. +- Manually remember which application received which credential. + +BitLogin already restores the same user-controlled identity on a clean device. +The Connection Vault applies that portability to application connections while +keeping each credential independently revocable and replaceable. + +The product goal is: + +> Sign in once, connect a service once, and authorize compatible applications +> when needed. + +## 3. Goals + +The Connection Vault should: + +1. Restore connections on a clean device after BitLogin authentication or + phrase recovery. +2. Keep individual connection records separate to avoid multi-device + last-write-wins clobbering. +3. Support multiple connections of the same type. +4. Encourage one credential per application or permission profile. +5. Allow credentials to be revoked, replaced, renamed, and exported + independently. +6. Keep connection types and contents encrypted from relays where practical. +7. Survive everyday Nostr identity rotation. +8. Reuse BitLogin's existing cryptographic primitives, canonical serialization, + padding, relay quorum, and validation rules. +9. Permit later extension to native applications, browser extensions, remote + signers, and hardware-backed authorization. + +## 4. Non-goals + +The Connection Vault is not: + +- A wallet. +- An S3 proxy. +- A guarantee that an application obeys a displayed permission request. +- A replacement for provider-side NWC budgets or S3 IAM policies. +- A defense against a malicious BitLogin build or compromised device. +- A mechanism for silently sharing all stored credentials with every app. +- A place to store cryptocurrency seed phrases. + +## 5. Relationship to the identity capsules + +### 5.1 Secrets do not live directly in the capsules + +The credential capsule is replaced wholesale and should be rewritten only for +credential events. The recovery capsule is intentionally minimal and is +written only while the recovery phrase is present. + +Putting mutable NWC and S3 credentials directly into either capsule would: + +- Expand the highest-value encrypted payload. +- Require a capsule rewrite whenever a connection changes. +- Increase multi-device replacement conflicts. +- Couple ordinary app administration to password and recovery state. +- Make independent deletion and rotation difficult. + +Therefore connection records live outside both capsules. + +### 5.2 Stable vault root + +A future capsule schema may add one new infrequently changed field: + +```json +{ + "connection_vault_root": "base64url-32-random-bytes" +} +``` + +The same 32-byte random value must be present in both the credential and +recovery payloads. It is generated once when the Connection Vault is enabled +and does not change during normal use. + +This root is not an application credential. It is key material used to locate, +sign, encrypt, and decrypt the independent connection records. + +A stable root is preferred over deriving the vault directly from the everyday +Nostr private key because the everyday identity may be rotated. Wallet and +storage connections should remain recoverable after that rotation. + +### 5.3 Existing-account migration + +Adding the vault root to an existing account requires the recovery phrase. +A password-only migration is insufficient because it could update the +credential capsule without updating the phrase-recovery path. + +The migration flow should be: + +```text +User unlocks account + | + +-- enters and verifies recovery phrase + +-- client generates connection_vault_root + +-- client writes refreshed recovery capsule + +-- client writes updated credential capsule + `-- client verifies quorum readback for both +``` + +A migration must fail closed if either capsule cannot be written and verified. + +## 6. Key hierarchy + +The following construction is proposed for a future protocol version. Exact +constants and test vectors must be finalized before implementation. + +```text +vault_prk = HKDF-Extract( + salt = SHA256("bitlogin/connection-vault-root/v1"), + IKM = connection_vault_root +) +``` + +Derive a Nostr-compatible signing identity: + +```text +vault_signing_material = ScalarExpand( + vault_prk, + "bitlogin/connection-vault-signing/v1" +) +``` + +Derive a record encryption key independently for every connection: + +```text +record_key = HKDF-Expand( + vault_prk, + "bitlogin/connection-record-encryption/v1" + || 0x00 + || connection_id_bytes, + 32 +) +``` + +The same raw material must never be used directly for both signing and +content encryption. + +## 7. Connection identifiers + +Each connection receives a random 128-bit identifier encoded as unpadded +base64url: + +```text +Vx7LgdZCsVbT_2uvB0YoGA +``` + +Requirements: + +- Generated with a cryptographically secure random generator. +- Exactly 16 random bytes before encoding. +- Never derived from an endpoint, wallet pubkey, application origin, label, or + secret. +- Stable for the lifetime of the logical connection. +- Regenerated when importing as a distinct copy unless the user explicitly + chooses to replace an existing connection. + +The identifier is not a secret, but it must not contain meaningful metadata. + +## 8. Nostr record format + +### 8.1 Event + +Each connection is stored as its own NIP-78-style addressable event: + +```json +{ + "pubkey": "", + "created_at": 1784750000, + "kind": 30078, + "tags": [ + ["d", "bitlogin:connection:Vx7LgdZCsVbT_2uvB0YoGA"] + ], + "content": "", + "sig": "" +} +``` + +The `d` tag deliberately contains only the generic BitLogin connection prefix +and an opaque identifier. It does not reveal whether the record contains NWC, +S3, or another credential type. + +The event is signed by the derived Connection Vault identity, not the user's +public everyday identity. This reduces direct linkage between the public +profile and the existence or timing of stored application credentials. + +### 8.2 Encrypted envelope + +```json +{ + "version": 1, + "algorithm": "aes-256-gcm", + "nonce": "", + "ciphertext": "" +} +``` + +The plaintext is canonically serialized using RFC 8785 JCS, padded using the +same fixed-bucket method as BitLogin capsules, and encrypted with the +connection-specific `record_key`. + +Associated data: + +```text +bitlogin|connection-record|v1||30078| +``` + +A fresh random 96-bit nonce is required for every publication. A nonce must +never be reused with the same record key. + +### 8.3 Common plaintext record + +```json +{ + "schema": "bitlogin.connection.v1", + "connection_id": "Vx7LgdZCsVbT_2uvB0YoGA", + "connection_type": "nwc", + "state": "active", + "label": "Satisfied spending wallet", + "created_at": 1784750000, + "updated_at": 1784750000, + "credential": { + "schema": "bitlogin.connection.nwc.v1" + }, + "application_binding": { + "origin": "https://satisfied.example", + "app_pubkey": null + }, + "notes": null +} +``` + +Required common fields: + +- `schema` +- `connection_id` +- `connection_type` +- `state` +- `created_at` +- `updated_at` +- `credential` + +Allowed states: + +```text +active +suspended +deleted +``` + +The credential object is defined by a connection profile such as +`nwc-connections.md` or `s3c-format.md`. + +## 9. Per-record replacement + +A connection update replaces only the event with the corresponding `d` tag. +No global array of connections is rewritten during ordinary use. + +This means two devices editing different connections do not clobber each +other. Two devices editing the same connection still race according to Nostr +addressable-event replacement rules. + +Clients must use BitLogin's monotonic replacement timestamp rule: + +```text +new_created_at = max(current_time, previous_created_at + 1) +``` + +Clients should also keep a per-connection local generation or event-ID +high-water mark and warn or fail closed on a detected regression. + +## 10. Discovery + +A client derives the vault public key after login and queries configured vault +relays for: + +```text +kind: 30078 +author: +``` + +It then selects events whose `d` tag begins with: + +```text +bitlogin:connection: +``` + +Because Nostr relay filters generally match exact tag values rather than +prefixes, the client may need to retrieve the vault identity's kind-30078 +events and filter locally. + +The initial design deliberately avoids a single mutable index event. A global +index would recreate the multi-device clobbering problem that separate records +are intended to solve. + +A future optimization may add a rebuildable encrypted index, but the index +must never be the sole source of truth. + +## 11. Deletion and revocation + +Deleting a BitLogin record does not revoke the underlying wallet or storage +credential. + +A safe deletion flow is: + +1. Offer or perform provider-side revocation first when supported. +2. Replace the record with an encrypted tombstone using `state: "deleted"`. +3. Publish a NIP-09 deletion request for the previous event ID. +4. Remove decrypted local copies and clear relevant grants. +5. Retain the tombstone high-water mark so stale relay replicas are not + silently resurrected. + +The interface must distinguish: + +- **Remove from BitLogin** — deletes the portable record only. +- **Revoke credential** — invalidates authority at the wallet or storage + provider. + +## 12. Application authorization + +### 12.1 Default behavior + +BitLogin must not expose every stored credential automatically after login. +An application requests access to a particular connection or capability, and +the user approves or denies it. + +Conceptual API: + +```javascript +const connections = await bitlogin.connections.list({ + type: "nwc" +}); + +const grant = await bitlogin.connections.requestAccess({ + connectionId: connections[0].id, + operations: ["pay_invoice", "get_balance"], + reason: "Pay for AI inference" +}); +``` + +### 12.2 Reveal versus broker + +There are two authorization modes: + +1. **Reveal:** BitLogin returns the raw credential to the requesting app. +2. **Broker:** BitLogin keeps the credential and performs approved operations + on the app's behalf. + +Brokered access is preferred because the application never receives the +long-lived secret. Reveal mode is simpler but transfers all authority granted +by the underlying credential to the host application. + +### 12.3 Browser security boundary + +An embedded same-origin web component is not a security boundary against its +host page. A malicious page can inspect values returned to it and may be able +to interfere with same-origin execution. + +Strong brokered isolation therefore requires one of: + +- A cross-origin BitLogin authorization window or iframe with a carefully + designed message protocol. +- A BitLogin browser extension. +- A native signer or credential broker. +- A NIP-46-style remote authorization service. + +The initial static widget may implement reveal mode, but the interface must +state that the requesting origin receives the credential. + +## 13. Recovery and export + +### 13.1 Password login + +The credential capsule restores the stable `connection_vault_root`, allowing +the client to derive the same vault identity and decrypt connection records. + +### 13.2 Phrase recovery + +The recovery capsule restores the same root. A clean device can therefore +recover connections without requiring the old password or prior browser data. + +### 13.3 Everyday identity rotation + +The Connection Vault root does not change when the public everyday Nostr +identity rotates. Connection records remain at the same vault identity and do +not require re-encryption solely because the public identity changed. + +### 13.4 Account export + +A full BitLogin recovery export should include: + +- The encrypted credential and recovery capsules. +- Signed Connection Vault events. +- The latest encrypted tombstones. +- Vault relay hints and readback metadata. + +The export must not contain decrypted NWC, S3, or other application credentials +unless the user explicitly selects a plainly labeled dangerous plaintext +export. + +## 14. Connection profiles + +Initial proposed profiles: + +- `bitlogin.connection.nwc.v1` — Nostr Wallet Connect +- `bitlogin.connection.s3.v1` — S3-compatible object storage + +Profiles define: + +- Required credential fields. +- Import and export formats. +- Validation rules. +- Provider verification behavior. +- Revocation behavior. +- App-facing operations. +- Profile-specific security warnings. + +## 15. Implementation phases + +### Phase A: specification and test vectors + +- Finalize capsule schema migration. +- Finalize key-derivation constants. +- Publish JCS, padding, encryption, and signed-event test vectors. +- Define valid and invalid profile fixtures. + +### Phase B: local Connection Vault + +- Add, edit, list, and remove connections locally. +- Encrypt records with the vault root. +- Add encrypted export and import. + +### Phase C: relay synchronization + +- Publish records to multiple vault relays. +- Require quorum readback. +- Add per-record rollback detection and replica repair. + +### Phase D: application authorization + +- Add permission prompts and connected-app management. +- Support explicit credential reveal. +- Add brokered operations where a safe execution boundary exists. + +### Phase E: hardened clients + +- Browser extension and native client. +- Cross-origin authorization protocol. +- Hardware-backed vault root where available. +- Independent security review. + +## 16. Open decisions + +The following require implementation review before the design becomes stable: + +1. Exact new capsule schema identifiers and migration rules. +2. Whether Connection Vault records should reuse the capsule padding buckets or + define larger buckets for storage-provider metadata. +3. Relay discovery and archival policy for the derived vault identity. +4. The precise app-origin and app-pubkey binding model. +5. Whether a rebuildable encrypted index is worthwhile. +6. How brokered grants are represented and revoked. +7. Whether the standalone S3C format should move to its own repository after a + second independent implementation exists. + +## 17. Security summary + +The Connection Vault reduces setup friction, but it also concentrates valuable +bearer credentials behind one account. Implementations must treat it as a +high-value secret manager. + +At minimum: + +- Never log decrypted credentials. +- Never place credentials in analytics, exceptions, URLs, or application state. +- Clear secret-bearing DOM fields immediately after import. +- Keep plaintext values inside the crypto or broker worker for the shortest + practical lifetime. +- Require explicit approval before reveal or export. +- Prefer separately scoped credentials for every application. +- Prefer temporary credentials, budgets, prefixes, and provider-side policy. +- Clearly distinguish BitLogin record deletion from provider-side revocation. +- Assume a malicious or compromised client can steal all secrets available in + that session.