Key rotation
Rotating the checkpoint signer: the new secret in SIGNING_SECRET, the old in SIGNING_SECRETS_PRIOR, so every checkpoint ever sealed keeps verifying.
The ledger seals its history under a signing key. Changing that key the naive way would make every old seal look forged; the rotation procedure keeps the old key around for checking while the new one takes over signing.
Source src/from-env.tssrc/runtime.tssrc/worker/checkpoint.ts
The checkpoint signer derives its Ed25519 key from SIGNING_SECRET. Checkpoints sealed under
an old secret must keep verifying forever — the chain of custody is only as good as the oldest
verifiable seal — so rotation is a two-variable move, never a replacement.
Never rotate by replacing SIGNING_SECRET alone. That silently breaks verification of
every checkpoint sealed before the change: the next checkpointVerify sweep reports a
mismatch, and you will be running the
integrity-mismatch runbook against your own key
change.
The procedure
-
Generate the new secret — any high-entropy string; it is hashed to the key seed.
-
Deploy with both variables set:
SIGNING_SECRET=<new secret> SIGNING_SECRETS_PRIOR=<old secret>SIGNING_SECRETS_PRIORis comma-separated; a second rotation makes it<old>,<older>. Each entry goes through the same derivation asSIGNING_SECRET, andSigner.verifytries the current key first, then each prior in order. -
Confirm continuity: the next scheduled sweep’s
worker.checkpoint.verifybeat must beoutcome: 'ok'— the old checkpoint verifying under a prior key. -
Confirm succession: after the next seal, the newest checkpoint is signed under the new key and verifies without the prior list;
make provechecks on demand. -
Keep a prior secret listed for as long as any checkpoint signed under it must verify — until every pre-rotation checkpoint has been superseded and any external audit that pinned
signingPublicKeyHexfor the old key has re-anchored. Dropping an entry is the deliberate act of expiring that era’s seals.
If verification breaks after a rotation
The near-certain cause is a dropped or mistyped prior secret. Restore the exact old value into
SIGNING_SECRETS_PRIOR and re-check the verify beat before suspecting tampering — but do not
skip the check: a mismatch that survives a corrected prior list is a real
integrity-mismatch.
Scope
The same secret also signs entitlement-gated URLs; those are short-lived, so rotation only
invalidates in-flight links. WEBHOOK_SECRET rotation is a separate, provider-coordinated
procedure — see webhook-replay-storm for when
it becomes urgent.