Documentation · Markdown source · Edit on GitHub · CC0 1.0

Key Rotation

How a VRP node changes its Ed25519 signing key without breaking verification. Spec §3.1 is normative; this document restates the requirement plainly and records the current, verified behaviour of the reference node.

The one rule everything follows

A node's signing authority is the set of keys published at its /.well-known/jwks.json. Because there is no central issuer, the JWKS is the only revocation authority: a key is valid exactly while it appears in the JWKS, and not one moment longer. Rotation and revocation are both just *edits to that published set*.

What the spec requires (§3.1)

Rotation is additive — you add the new key before you remove the old one, so there is always a window in which both verify:

  1. Generate a new Ed25519 key with a fresh kid. A kid that encodes a date and sequence (e.g. example.com-2026-05-18-01) helps operators order keys — but a verifier treats kid as opaque and infers nothing (trust, recency, status) from it.
  2. Publish the new key alongside the old one in the JWKS.
  3. Switch signing to the new key: new offers are signed with the new kid.
  4. Keep the old key in the JWKS until every offer signed with it has passed its valid_until. The retention window therefore equals the maximum offer lifetime — while it lasts, offers already issued under the old key still verify.
  5. Remove the old key from the JWKS.

Because the old key is retained until its last offer has expired, no live offer breaks.

Two independent numbers — do not conflate

The spec is explicit that these are separate controls (§3.1):

Verified live behaviour (reference node)

Fetched directly from the reference node (https://villaakerlyckan.se/.well-known/jwks.json), the current state is a normal steady state — one active key, no rotation in progress:

A single key is the expected steady state; a second key appears in the JWKS only *transiently during a rotation*, for exactly as long as the old key's outstanding offers remain fresh (step 4 above). This document does not assert any fixed overlap schedule — the overlap lasts as long as the offer-lifetime rule requires.

PhaseJWKS containsSigning withA verifier can trust offers signed by
steady state (now)one keythat keythat key
rotation startold + newnewold and new
overlap (until old key's last offer passes valid_until)old + newnewold and new
after retirenewnewnew only

After the old key is removed, any offer still bearing its kid becomes Unknown and MUST NOT be quoted — the agent should fetch a fresh offer (spec §6, §9).

What an implementer must do

Rotation is not revocation

Routine rotation retires the old key by *timing* — you remove it only after its offers have expired, so nothing breaks. Compromise is different: the old key must be removed immediately, even though that also invalidates the operator's own legitimate offers signed with it. See INCIDENT_RESPONSE.md.