keyserver: cache remote users' device and cross-signing keys
CI / Test (ubuntu-latest) (push) Canceled after 0s
CI / Test (windows-latest) (push) Canceled after 0s
CI / Storage tests on PostgreSQL (push) Canceled after 0s
CI / Complement (federation off) (push) Canceled after 0s
CI / golangci-lint (push) Canceled after 0s
CI / Smoke test the binary (push) Canceled after 0s

POST /keys/query for a remote user who shares a room with a local user is
now answered from a cache, fetched whole with GET /user/devices/{userId}
on a miss. Incoming m.device_list_update EDUs are applied when their
prev_id matches the cached stream_id; anything else marks the copy stale
and it is fetched again in the background, and a stale copy is never
served. m.signing_key_update replaces the cached cross-signing keys. A
user's copy is dropped once no local user shares a room with them, by a
keyserver listener on the roomserver's emit stage.

Migration 0009 adds keyserver_remote_users, keyserver_remote_device_keys
and keyserver_remote_cross_signing_keys for sqlite and postgres.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
tajniak81andClaude Opus 5.5 committed 2026-10-17 10:36:08 +02:00
1 parent fc49f56339
commit 74f7bc8805
19 files changed
+1769 -92

No files matched your search

@@ -0,0 +1,48 @@
-- The cache of other servers' users' keys.
--
-- A local client about to send an encrypted message asks for the keys of
-- everyone in the room, and asks again whenever a sync says their device
-- list moved. Answering that for a remote user by asking their server
-- every time puts a federation round trip in front of every message, and
-- turns a busy room into a steady stream of identical queries. So the
-- keys of remote users who share a room with somebody here are kept, and
-- kept current by the m.device_list_update and m.signing_key_update EDUs
-- their server sends whenever they change.
--
-- These are tables of their own rather than rows in the local key tables.
-- A local user's keys are the source of truth, and every write to them
-- moves the device list stream and goes out over federation; a cached
-- copy of somebody else's is neither, and keeping them apart means no
-- code path that announces a local change can ever announce a copy.
-- One row per cached user: the marker that the user's device list is held
-- here at all, and the position in their server's device list stream the
-- copy is at. An update names the position before it (prev_id), and is
-- applied only when that is the position held; anything else means an
-- update was missed, and the copy is marked stale until it is fetched
-- again whole.
CREATE TABLE IF NOT EXISTS keyserver_remote_users (
user_id TEXT NOT NULL PRIMARY KEY,
stream_id BIGINT NOT NULL,
stale BOOLEAN NOT NULL DEFAULT FALSE
);
-- The identity keys of a cached user's devices, as their server sent
-- them, and the display name it gave each.
CREATE TABLE IF NOT EXISTS keyserver_remote_device_keys (
user_id TEXT NOT NULL,
device_id TEXT NOT NULL,
display_name TEXT NOT NULL DEFAULT '',
key_json TEXT NOT NULL,
PRIMARY KEY (user_id, device_id)
);
-- A cached user's master and self-signing keys. The user-signing key is
-- never sent over federation, so there is no row for one.
CREATE TABLE IF NOT EXISTS keyserver_remote_cross_signing_keys (
user_id TEXT NOT NULL,
-- master or self_signing.
purpose TEXT NOT NULL,
key_json TEXT NOT NULL,
PRIMARY KEY (user_id, purpose)
);