Key management and rotation#
Three key roles exist. Each is hybrid: an ed25519 key and its post-quantum
(ML-DSA) half, which signs everything the ed25519 key signs (see the bundle
spec's Post-quantum signatures). The export key and the vendor key are
raw key files generated with bitexact keygen (<name>.key and
<name>.pq.key, file mode 0600, keep offline where the role allows); the two
halves rotate together, as one key. The recorder key may instead live
in a PKCS#11 token or as a KMS envelope, in which case there is no key file
at all - see Recorder-key custody, and read the
rotation note below before planning one. Every signature carries a key_id
(BLAKE2b-64 of the public key) so relying parties can identify the signer
without parsing keys.
| Role | Held by | Online? |
|---|---|---|
| Export signing key | The operator exporting evidence | Only during export |
Recorder checkpoint key (BITEXACT_SIGNING_KEY) | The recording host | Yes — accept this or disable checkpoints |
| Vendor license key | BitExact | Never leaves the vendor |
Rotating an export or recorder key#
Recorder key only: get the license first. A license names the recorder keys it authorizes (
recorder_keys), and the deployment records only under one of them. Send the new key's public half and ask for a license naming both keys, then install it before switching. Switch first and every call from then on passes through unrecorded, with a declared gap, until the new license arrives. Once the old key is retired, a license naming only the new one can replace it at the next renewal.Generate the new key:
bitexact keygen -o new.key.Add the new public key to relying parties' trust files alongside the old one — trust files accept multiple keys precisely so rotation has an overlap window. A trust file is JSON with
bundle_keysandrecorder_keysarrays, each entry{"key_id": ..., "public_key": ..., "pq_public_key": ...}-keygenprints the entry; the verifier reads it with--trust-file.bitexact recorder-key(and--pqfor its post-quantum half) prints the public half of whichever backend holds the current one. A run cannot change post-quantum key part-way under the same ed25519 key, so switch both halves at once.Switch signing to the new key: replace the file behind
BITEXACT_SIGNING_KEY, or use--key new.keyfor exports.If the recorder key lives in an HSM or a KMS envelope there is no file to replace. Generate the new key in the token (or encrypt a new one under KMS), point
BITEXACT_HSM_KEY_LABELorBITEXACT_SIGNING_KMS_CIPHERTEXTat it, and restart the recorder. The old key stays in the token for as long as you need to verify what it signed; nothing in the bundle format changes.After the overlap window, remove the old public key from trust files. Bundles signed under the old key remain verifiable by anyone who keeps its public half; re-export and re-sign if a relying party must standardize on current keys.
Checkpoint records embed their signing key_id, so a rotation
mid-retention is visible and auditable in the checkpoint chain itself.
Compromise response#
Remove the compromised public key from every trust file immediately — verification against those trust files then fails for anything it signed. Re-export affected runs signed with a new key; the manifests themselves are unaffected (chain hashes carry no key material).