Transparency log#
Each run is a hash chain whose head the recorder signs. That catches any edit, truncation or rollback of a run someone is looking at. It does not stop whoever operates the deployment from deleting a whole run, or from showing one auditor a run as it was and another auditor a later version: the recorder key signs whatever its operator has it sign.
The transparency log closes that. Every checkpoint the recorder signs is
also appended to one append-only Merkle tree per deployment (RFC 9162).
The log's head is signed as a C2SP tlog-checkpoint note and cosigned by
witnesses - parties outside the deployment who keep the last head they
signed and refuse any head that does not extend it. Exported bundles carry
a proof that their head is in the log, which the public verifier checks.
What it proves, and what it does not#
- A run in the log cannot be deleted unnoticed:
bitexact log auditnames every run the log holds that the store no longer has. - The log cannot be rolled back or rewritten: a witness refuses to cosign
a head that is not consistent with the last one it cosigned, and an
auditor holding an earlier note checks consistency with
bitexact log consistency. - One log cannot be shown two ways: a witness cosigns one root per size.
- A bundle's head was in the one history the witnesses saw - when the
verifier is given the witnesses' keys. Without them, the verdict says
witnesses not pinned, and the proof shows only that the recorder key put the head in some log it signed. - The log proves what the recorder attested, no more. It does not prove the
head is the run's latest (
bitexact log headslists every head a run has had), and it does not prove the agent behaved as recorded.
Logging and witnessing run beside recording, never in its path. A failed
log append is logged (tlog_append_failed) and the run seals as usual;
bitexact log audit then reports the checkpoint as NOT LOGGED and
bitexact log backfill adds it. An unreachable witness never delays a run.
Purging and archiving a logged run#
The log never forgets a run: its leaves are never removed. A run the log
names that the store no longer holds is reported as MISSING by
bitexact log audit and as the critical log-run-gone by
bitexact health - unless it left the store lawfully, on the record:
bitexact archivewrites each run's bundle to the archive and leaves a catalog row. An archived run is still held, and is not reported.bitexact purge --deleteandbitexact purge --archive DIRwrite a purge record before they remove a run: its head, whether it was deleted or archived (and where to), when, and by which command, signed with the recorder key. The audit lists such a run as purged - with when, how and by what - not as missing.
A purge record counts only when the recorder key signed it. Anyone who can
write to the store could write an unsigned one, delete a run, and hide the
deletion. So a run the log names is not purged without the recorder key
(BITEXACT_SIGNING_KEY): purge refuses before it removes anything. A run
removed any other way, or with a record that does not verify, is still
log-run-gone. Purge records are kept in the store under either backend
and travel with a bitexact db-transfer.
Runs refused at open#
A run refused at open has no chain and no checkpoint. Its refusal is an
event on the run id's refusal ledger, which bitexact coverage report counts,
and every refusal event is
also a leaf in this log, naming the ledger and the event's hash. Refusal
leaves need no signer, so they enter the log during a license lapse too,
and the next signed, witnessed checkpoint covers them.
- A refusal removed from the store - the event, or its whole ledger file -
is reported by
bitexact log auditasMISSING: N refusals of RUN_ID, and bybitexact healthas the criticallog-refusal-gone. - A refusal the log lacks (its append failed) is reported as
NOT LOGGEDand as the warninglog-refusal-not-logged;bitexact log backfilladds it. - A refusal records no access scope that can be confirmed, so both are reported only by an audit of the whole store, not of chosen runs.
- What the log cannot cover is a refusal removed before any checkpoint covering it was witnessed - the same window a run checkpoint has.
Running the log#
The log needs the recorder key (BITEXACT_SIGNING_KEY) and is named by
its origin, bitexact.log/<recorder key id> unless BITEXACT_LOG_ORIGIN
says otherwise. Witnesses and auditors name the log by its origin.
bitexact log checkpoint # sign the head; ask BITEXACT_LOG_WITNESSES to cosign
bitexact log status # size, and what the latest checkpoint covers
bitexact log audit # runs and refusals the store lost; what the log lacks
bitexact log backfill # add the signed checkpoints and refusals the log lacks
bitexact log heads RUN_ID # every head of one run, oldest first
bitexact log consistency NOTE_FILE # today's log extends a note you kept
bitexact recorder signs the head and asks the witnesses every
BITEXACT_LOG_EVERY_S seconds (default 60) when the log has grown. Where
no recorder service runs, schedule bitexact log checkpoint instead. The
log commands need a license with the anchoring entitlement.
BITEXACT_LOG_WITNESSES is a comma-separated list of witness URLs. A
witness that does not cosign is named in the output and in the recorder's
log (tlog_not_cosigned); the checkpoint is still signed.
Running a witness#
A witness is run by someone other than the deployment's operator: an auditor, a regulator, a second company. It needs no license.
bitexact keygen --suite witness-ml-dsa-44 -o witness.key
bitexact witness --name witness.auditor.example --key witness.key \
--log bitexact.log/<key id>=<recorder public key hex>:<recorder post-quantum key hex> \
--state /var/lib/bitexact-witness \
--host 0.0.0.0 --tls-cert cert.pem --tls-key key.pem
--suite witness-ml-dsa-44 makes a post-quantum C2SP cosigning key;
--suite ed25519 makes the classical one. A hybrid log's notes carry both
of the recorder's signatures, so --log names both of its public keys
(bitexact recorder-key and bitexact recorder-key --pq print them).
It serves the C2SP tlog-witness POST /add-checkpoint protocol, so the
log can also be witnessed by any C2SP witness, and bitexact witness can
witness any C2SP log. Its state directory holds the last head it cosigned
for each log, written to disk before the cosignature is returned; keep it
on durable storage, because a witness that loses it can be rolled back.
Give the witness's name and public key to the auditors who will verify
bundles.
Verifying#
A bundle exported after the log exists carries tlog: the head
checkpoint's leaf, an inclusion proof, and the log's checkpoint note with
its cosignatures (see the bundle specification). An auditor pins the
recorder key and the witnesses:
bitexact-verifier bundle.json --expect-recorder-key <hex> \
--expect-witness witness.auditor.example=<hex> --witness-threshold 1
With witnesses pinned, a bundle with no log proof, or with fewer cosignatures by pinned witnesses than the threshold, fails. Cosignatures by keys the auditor did not pin are ignored. Bundles made before the log existed verify exactly as before.
To check that the log has not been rolled back since an earlier look,
keep the note bitexact log checkpoint printed (or the note of any
bundle's tlog) and later run bitexact log consistency on it.
What the offline verifier checks, and what it leaves to the witnesses: a
bundle carries one signed head and the proof that the run is in it, so the
verifier checks inclusion, never consistency - consistency is between two
heads, and needs the log between them. That is the witnesses' job: a
witness cosigns a head only after checking it extends the last head it
cosigned. An auditor working from bundles alone gets rollback protection by
pinning witnesses and a threshold; one with access to the deployment checks a
kept note directly with bitexact log consistency.