7.5 KiB
vault1984 — Commercial Onboarding Architecture
Last updated: March 2026
Overview
The commercial onboarding flow connects Stripe payments to vault provisioning without violating zero-knowledge principles. The account system knows billing metadata — who paid, how many vaults they have, when they expire. It never touches vault contents, keys, or encrypted data.
Self-hosted users are unaffected. The commercial code path only activates when vault_meta exists in a vault's SQLite.
Vault Filename Scheme
The vault filename is derived from the PRF master secret:
Master (32 bytes) from PRF
L3 = bytes[0..32] (full 32 bytes)
L2 = bytes[0..16] (first 16 bytes)
L1 = bytes[0..8] (first 8 bytes)
filename = bytes[0..4] → base64url (no padding) = 6 chars
Filename format: vault1984-AbCdEf (prefix + 6 base64url chars, no extension).
- 4 bytes → 2^32 ≈ 4 billion unique vaults
- Reveals only 4 of the 8 L1 bytes — not enough to derive any key
- Deterministic: same hardware key always produces the same filename
The Account System
A new service. Go + SQLite. Runs on HQ (Zürich). Handles billing, not security.
What it stores
accounts:
email TEXT PRIMARY KEY
stripe_customer_id TEXT
created_at TEXT
vaults:
vault_id TEXT -- the "AbCdEf" part
account_email TEXT -- FK to accounts
region TEXT -- which POP
created_at TEXT
What it does NOT store
- Encryption keys (L1, L2, L3)
- Vault contents or metadata
- Session state
- Anything that touches the zero-knowledge guarantee
API
POST /checkout Stripe Checkout session → redirect to Stripe
POST /webhook/stripe Stripe confirms payment → create account
GET /vault/{id}/status Is this vault still paid? (called by POP on expiry)
POST /vault/create POP registers new vault against account
POST /vault/{id}/delete POP notifies vault deleted, frees capacity
Consumer Flow ($12/year)
Purchase
- User visits vault1984.com → enters email → redirected to Stripe Checkout
- Stripe collects payment ($12/year)
- Stripe webhook → account system creates account (email + Stripe customer ID)
- User shown: "Pick your region" → selects a POP
Vault Creation
- User lands on POP (e.g.
eu.vault1984.com) → enters email - Registers passkey → WebAuthn PRF fires → 32-byte master derived
- POP takes first 4 bytes → base64url → vault filename
vault1984-AbCdEf - POP calls account system:
POST /vault/createwith email + vault_id - Account system checks: does this account have capacity? (consumer = max 1 vault)
- Yes → records vault, responds with
expires_at - POP creates SQLite file
vault1984-AbCdEfwithvault_metarow (account_email, expires_at) - Vault is live. User is unlocked. Registration = unlocked.
Every Request (hosted vault)
- Bearer token carries L1 (8 bytes)
- POP derives filename from first 4 bytes → opens SQLite
- Checks
vault_meta.expires_at— if past, calls home (see Expiry below) - If valid → process request as normal
Expiry & Renewal
- No per-request call to HQ. The vault checks its local
expires_at. - When
expires_atis reached, the POP callsGET /vault/{id}/statuson HQ. - HQ checks Stripe subscription status → responds with new
expires_ator "expired." - Renewed → POP updates
expires_atlocally, proceed. - Not renewed → 402 Payment Required. Vault data is intact but inaccessible.
Vault Deletion
- Initiated through account management on vault1984.com (not the vault UI).
- Account site → strong auth → user deletes vault.
- Account system calls POP → vault SQLite deleted → vault record removed from account → capacity freed.
- 30-day money-back guarantee, no questions asked.
Self-Hosted (Free, Elastic License)
No vault_meta table. No expiry check. No call home. No account system interaction. The vault binary works exactly as it does today. The commercial code path is inert.
Account Management (vault1984.com)
Central control plane for billing and vault lifecycle. Separate from any vault UI.
- View account, payment status, invoices
- See owned vaults (vault ID + region)
- Delete a vault
- Manage Stripe subscription (cancel, update payment method)
- Strong auth required (passkey — same hardware key the user already owns)
Hosted POP Architecture
The vault binary is the same binary self-hosters run. It has no management API, no delete endpoint, no inbound control surface. This is by design — self-hosters must never be exposed to hosted infrastructure concerns.
Hosted POPs run two processes:
vault1984 — port 1984, public internet
Dumb encrypted storage engine. Identical to self-hosted binary.
vault1984-mgmt — Tailscale network only, no public interface
Management sidecar. Handles commands from HQ.
Why Two Processes
The vault binary cannot have a delete endpoint. If it did, every self-hosted instance would have one too. Separating management into a sidecar means:
- The vault binary stays unchanged for self-hosters
- The management surface is only reachable via Tailscale (not the public internet)
- Self-hosters never install, run, or know about the sidecar
Tailscale for Management
Tailscale provides the secure channel between HQ and POP sidecars:
- Identity-based ACLs: Only HQ's account system can reach
vault1984-mgmton any POP - Zero config on new POPs:
tailscale upwith an auth key, done - No public exposure: The sidecar binds only to the Tailscale interface
- POPs can't reach each other's mgmt: ACL policy enforces HQ-only access
Tailscale ACL (simplified):
HQ account-system → vault1984-mgmt on any POP ✅
POP mgmt → POP mgmt ✗
Anything else → vault1984-mgmt ✗
Management Sidecar Responsibilities
The sidecar is a small Go binary with a narrow API:
POST /vault/{id}/delete Delete a vault SQLite file
POST /vault/{id}/extend Update expires_at in vault_meta
GET /vault/{id}/exists Confirm a vault file exists
GET /health Sidecar is running
It has filesystem access to the vault directory. It reads/writes vault_meta and can delete vault files. It never opens or decrypts vault contents.
POP ↔ HQ Communication
| When | Direction | Channel | What |
|---|---|---|---|
| Vault creation | POP → HQ | Tailscale | Validate account, register vault |
| Vault expiry | POP → HQ | Tailscale | Check renewal status |
| Vault deletion | HQ → POP | Tailscale (via mgmt sidecar) | Delete vault file |
| Expiry update | HQ → POP | Tailscale (via mgmt sidecar) | Extend expires_at |
No runtime dependency on HQ. POPs operate independently except at creation and expiry boundaries. All management traffic flows over Tailscale — never the public internet.
Open Questions
- Account management auth: Passkey on vault1984.com — same authenticator or separate registration?
- Grace period on expiry: How many days past
expires_atbefore 402? Immediate, or a buffer (e.g. 7 days)? - Deletion confirmation: Sidecar deletes file → responds to HQ → HQ removes vault record. What if the file delete fails? Retry? Tombstone?
- Future: second copy (read replica): Noted as planned. Architecture supports it — vault_id is deterministic, same file can exist on two POPs. Sync model TBD.