# Local identity vault

> **Human version:** make a Cursors mailbox identity on this device, encrypt its private key before storage, and stay completely local until you deliberately present a separate admission grant.

![A local Ed25519 identity is wrapped by either a passphrase-derived key or a passkey PRF result before origin-scoped storage](images/identity-vault.svg)

The top-level **MAILBOX** surface is the friendliest passkey path. The cockpit’s **Pilot / Mailbox** control leads to deeper identity tooling. Both create a real Ed25519 signing identity; creation itself does not call Cloudflare, GitHub, an OAuth provider, or a Cursors mailbox service.

## What works now

1. The browser generates an Ed25519 keypair with Web Crypto.
2. The public key is hashed into a public mailbox identifier.
3. The private key is exported only long enough to wrap it locally.
4. AES-256-GCM encrypts the private key and authenticates the public metadata.
5. Only the encrypted record is written to origin-scoped browser storage.
6. Unlock imports the decrypted private key as **non-extractable**, signs a random challenge, verifies it against the public key, and then keeps the key in page memory until lock or navigation.

[Inspect Self / Body](#cursors-inspect:self%3Alocal) after creation. The pip changes from `LOCAL / UNENROLLED` to `ENCRYPTED / LOCKED` or `LOCAL KEY UNLOCKED` without claiming Cloudflare admission.

## Two local protection modes

### Passphrase

The passphrase never leaves the page. PBKDF2-SHA-256 derives an AES wrapping key from a random salt and a stored work factor. This protects a backup only as well as the passphrase and the integrity of the page running the unlock ceremony.

Use a long, unique phrase. The cockpit requires at least 12 characters but that minimum is a UI guardrail, not a guarantee.

### Passkey PRF

On a secure origin with a compatible authenticator, the cockpit can ask WebAuthn’s `prf` extension for a credential-bound 32-byte result. The result becomes the AES wrapping key. The encrypted vault stores the credential ID and a random PRF input—not the PRF output.

If registration reports that PRF is unavailable, nothing is saved and the UI returns to the passphrase path. The passkey ceremony is origin-bound: a preview path and production share an origin on GitHub Pages, so this repository also scopes the encrypted vault key by deployment pathname. Export is the explicit bridge between deployments.

## What the encrypted backup contains

- versioned vault format;
- creation timestamp;
- Ed25519 public key, mailbox identifier, and fingerprint;
- protection mode and its non-secret parameters;
- AES-GCM ciphertext containing the PKCS#8 private key.

It does **not** contain the passphrase, passkey PRF result, a Cloudflare grant, a capability, mailbox plaintext, or an OAuth token.

The backup is still important: browser storage can be cleared, devices fail, and a passphrase remembered only in one person’s head is not resilient custody. Keep encrypted copies in more than one place and test recovery before the identity matters.

## Cloudflare persistence remains optional

A local identity answers “which key signed this request?” It does not answer “may this mailbox consume curso.rs resources?”

Remote admission remains a separate root-signed, expiring grant with exact rights and tiny quotas. The optional Worker may see the public key, request proof, ciphertext envelope sizes, timing, and routing metadata. It must never receive the local private key or passphrase.

The deployed public descriptor and introduction path uses proof-of-possession today. The retained-note UI is still a prototype: it labels its note field `ciphertext` internally, but browser-side recipient encryption is not yet implemented, so Cloudflare can see note text. This documentation and the Chat truth strip treat that as an open security boundary rather than an E2E claim.

This separation lets the same identity work across:

- device-local and LAN-only Cursors worlds;
- direct peer sessions;
- a Cursors mailbox hosted by someone else;
- the optional curso.rs Cloudflare service;
- a future OAuth-shaped compatibility boundary.

Read [Cursor Identity](cursor-identity.md), [Admission and Human Presence](admission-identity.md), and [Opt-in Cloudflare Service](cloudflare-service.md) for those outer layers.

## Remaining work

- encrypted import UI and conflict-safe identity rotation;
- recovery shares or multi-device succession without copying plaintext keys;
- signed local-to-admitted enrollment ceremony;
- capability-scoped mailbox and workbench sessions;
- cross-runtime test vectors for vault metadata and mailbox identifiers;
- hardened content security policy and third-party review before calling this production custody.
