mirror of
https://github.com/PR0M3TH3AN/bitlogin.git
synced 2026-10-27 08:31:21 +00:00
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
617 lines
21 KiB
Markdown
617 lines
21 KiB
Markdown
# BitLogin Connection Vault
|
|
|
|
**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
|
|
|
|
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": "<connection-vault-public-key>",
|
|
"created_at": 1784750000,
|
|
"kind": 30078,
|
|
"tags": [
|
|
["d", "bitlogin:connection:Vx7LgdZCsVbT_2uvB0YoGA"]
|
|
],
|
|
"content": "<encoded-encrypted-envelope>",
|
|
"sig": "<vault-signature>"
|
|
}
|
|
```
|
|
|
|
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": "<base64url-12-random-bytes>",
|
|
"ciphertext": "<base64url>"
|
|
}
|
|
```
|
|
|
|
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|<vault-pubkey>|30078|<d-tag>
|
|
```
|
|
|
|
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: <vault-pubkey>
|
|
```
|
|
|
|
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.
|
|
|
|
## 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.
|