Skip to content
BitExactdocs
Documentation
On this page

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 audit names 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 heads lists 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 archive writes each run's bundle to the archive and leaves a catalog row. An archived run is still held, and is not reported.
  • bitexact purge --delete and bitexact purge --archive DIR write 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 audit as MISSING: N refusals of RUN_ID, and by bitexact health as the critical log-refusal-gone.
  • A refusal the log lacks (its append failed) is reported as NOT LOGGED and as the warning log-refusal-not-logged; bitexact log backfill adds 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.