Data model
What actually sits in the SQLite database, all of it either ciphertext, public keys, or non-secret metadata.
Entries and versions
- An entry is owned by one user and identified by a ULID.
- Each change appends an immutable entry version (also ULID-keyed) carrying a content hash and the writing device's hybrid signature over
entry_id ‖ version_ulid ‖ content_hash. - A small outline (enough to render the vault list, title, kind, match URLs) is stored under its own key so the list renders without decrypting every full entry.
History is append-only; the "current" version is simply the latest.
Shares (per-device ciphertext)
A share is the entry's content encrypted for one recipient device. A version therefore has one share per active recipient device. When you share with another user, their devices each get a share too. The server stores shares as opaque blobs keyed by (entry_version, device).
Devices and attestations
- A device row holds a label, the device's signing and encryption public keys, an active/revoked flag, and timestamps.
- An attestation is a signed blob from the device's parent, vouching for its public keys under the user id. The root device has none. Clients walk these edges to build the trusted-device set; the server stores them opaquely and can't forge them.
Optimistic concurrency
Update carries the version the writer believed was current. The server applies the change only if that's still true, as an atomic compare-and-swap, so two devices editing the same entry can't silently clobber one another; the loser is told to refetch and retry.
Instance config
A single row of server-wide settings: whether signups are open, the operator backup public key (if configured), and the server-only key used to mint session tokens. The backup private key is never here, it lives offline with the operator.
What's deliberately visible
The server can see structure even though it can't read content: how many entries you have, when they changed, their content hashes, the sharing graph (who can read what), device counts, and your email. Hiding that metadata is explicitly out of scope, see the threat model.