Data & Storage

The server database

PostgreSQL holds the durable spine of Privex — but only public keys, hashes, and ciphertext. Eleven tables, split between a crash-safe half and a leaves-no-trace half.

Think of the server’s database as a public phone book plus a few locked drop-boxes. The phone book lists only the public keys strangers need to message you — never your name, email, or address. The drop-boxes hold sealed envelopes the server can’t open. Nothing here identifies a real person.

The current migrations (00010012) create 11 tables. Every stored value is a public key, a hash, a blinded/HMAC value, or ciphertext. No table has an IP, email, phone, display-name, or last_seen column. Deleting a key_directory row cascades to a user’s prekeys, OPAQUE record, recovery shares, and history blobs.

Two kinds of table

Some tables are durable — they survive a power cut, like a filing cabinet bolted to the floor. Others are UNLOGGED — they’re deliberately built on a table that tips over and spills if the server crashes, so nothing sensitive is left behind to recover.

LOGGED tables use the write-ahead log and survive a crash — the WAL only ever holds ciphertext and px_ ids. UNLOGGED tables skip the WAL entirely and are truncated on an unclean restart, so transient user data never lands in a journal. kt_log is UNLOGGED but rebuildable from the durable key_directory on boot.

LOGGED — durable UNLOGGED — no WAL, truncated on crash
Entity–relationship diagram
11 tables · solid = real FK (CASCADE) · dashed = logical id, no FK
LOGGED (durable) UNLOGGED (no WAL) solid FK · dashed = logical
Figure. Click to open the full diagram with pan & zoom. Reserved tables (group_state, relay_nodes) and the legacy pow_challenges are defined but not wired into live code.

Every table, in detail

Filter by durability or status. In Technical mode each card lists its full columns; in Plain mode you get the one-line “what it’s for”.

Watch out Three tables aren’t what a casual reader might assume: group_state and relay_nodes are schema-only (no live routes), and pow_challenges is legacy — the live proof-of-work state lives in Redis (pow:challenge:*). The V2 doc’s history_backup_settings and a server-side linked_devices table do not exist in the migrations.