Crypto primitives
Inventory of primitives used in Vault, Elevate, and Chat, plus the evaluation decisions encoded in the packages.
Crypto primitives
Extended reading on which primitives this suite uses and what we evaluated when wiring them. WebAuthn/PRF product framing: WebAuthn and PRF. Design and upstream rationale: Crypto and package rationale. Short glossary table: Background.
Stage
This is a map of what is shipped in the packages — not a third-party audit report and not a claim of production readiness for your threat model.
Browser — vault and wraps
| Primitive | Role in the suite | Notes |
|---|---|---|
| WebAuthn + PRF | Login / elevate assertions; PRF → storage KEK | Fail closed on missing PRF for unlock paths that require it |
| HKDF-SHA256 | Domain-separated keys from PRF (and related browser KDFs) | Info strings bind purpose + user; purposes are not interchangeable |
| PBKDF2-SHA256 | Password and recovery wraps | High iteration floor in client helpers; weak user passwords remain a residual risk |
| AES-GCM | Seal product payloads under the MEK; related sealed blobs | AAD binds context where the helpers require it |
| BIP-39 | Recovery phrase → seed → recovery wrap (and optional account-recovery signing material) | Phrase is for reset, not everyday unlock |
| Ed25519 | Account-recovery challenge signing (Mode B) | Server verifies; phrase never crosses the wire |
Most product code should go through @railman/zero-vault and the passkey client helpers. Guides: Vault, Recovery.
Server — sessions, step-up, blind store
| Primitive | Role in the suite | Notes |
|---|---|---|
| HMAC (keyed digests) | Elevate claims, login-factor stamps, persistent row MACs | Bound to Better Auth / Railman secrets — authorize on the server, not from client JSON alone |
| HKDF-SHA256 | Derive MAC and storage-ID families from one root key authority | Applications do not manage child key rings |
| Versioned SHA-256 IDs | Long-lived E2E row reachability over canonical inputs | Server-only identifiers, MAC-bound; not a substitute for encryption |
zeroAccess({ secret? }) owns a single root (or captures Better Auth’s secret once). MACs detect field tampering by a DB attacker without the root; they do not detect deletion or rollback of a full valid snapshot. Depth: Security model.
Chat — after vault unlock
| Primitive | Role in the suite | Notes |
|---|---|---|
| X25519 | DH / identity material in @railman/zero-e2e | Consumes a Vault-derived identity seed — no WebAuthn inside the crypto library |
| HKDF-SHA256 | Session and ratchet KDFs | |
| AES-GCM | Message envelopes and sealed ratchet state | Prekey envelopes bind selected wire fields in AAD |
| X3DH-style init + Double Ratchet | 1:1 session setup and forward secrecy structure | Not a group / multi-device messenger |
Product path: hardened passkey → PRF vault unlock → identity seed → zero-e2e. Guide: E2E chat.
Evaluation decisions (shape)
These are the trade-offs the docs and packages encode:
- Blind store over server-held MEKs — database dumps should not yield plaintext; residual risk moves to unlocked tabs and client malware (Security model).
- PRF-first daily unlock, password optional, recovery separate — recovery stays break-glass (WebAuthn and PRF, Recovery).
- Elevate independent of Vault — sudo without “elevated ⇒ decrypt” (Elevate and sudo).
- Exact Better Auth peer pins — callback and hook ordering are security-sensitive (Security model).
- Chat on Vault identity, not a second passkey stack (E2E chat).
- M6 denylist on key-store routes — fail closed if raw secrets appear in request bodies (Plugin contract).
For why we chose these primitives and how we treat upstream packages, see Crypto and package rationale.
Related
WebAuthn and PRF
Introductory reading on WebAuthn jobs in this suite and how the PRF extension unlocks a vault without sending secrets to the server.
Crypto and package rationale
Why we chose these primitives, how they are implemented in the suite, and how we relate to Better Auth and other upstream packages.