Files
alexm 4fa326dc76
check / check (push) Successful in 42s
site / site (push) Successful in 3m4s
First implementation (#1)
Reviewed-on: #1
2026-10-15 05:47:05 -04:00

69 lines
5.3 KiB
Markdown

# 23. The transparency log
A receipt's `at` is what its signer wrote, and a roster is a file a package carries.
Neither proves that a receipt existed before the key that signed it was revoked, or that the roster a verifier holds is the one the team published.
A team's transparency log settles both: an append-only record of every receipt, revocation and roster the team publishes, whose state is a Merkle tree root that the team signs and that witnesses may cosign.
The log is the same tree as receipts and the correction history ([8.5](08-fidelity.md#85-merkle-roots), [11.5](11-corrections.md#115-the-history-root)), with the same proofs.
Both formats of this chapter are `provisional` ([0.4](00-introduction.md#04-status)): the design follows the specifications that transparency logs share, and the first deployment will shape the details.
## 23.1 Entries
An entry, `scienting-log-entry/1`, is one item of the tree, `[kind, id, hash]`:
| `kind` | `id` | `hash` |
| --- | --- | --- |
| `receipt` | The receipt's record ID. | Its record hash ([8.4](08-fidelity.md#84-record-hashes-and-signatures)). |
| `revocation` | The revocation's record ID. | Its record hash. |
| `roster` | The store the roster is for. | SHA-256 of the canonical JSON of the roster. |
Entries are appended in the order the team publishes them and never removed.
The leaf hash of an entry is SHA-256 of the byte 0x00 and its canonical JSON, the interior hashes follow RFC 9162, and the root over no entries is SHA-256 of the empty string, as for the history root.
A roster is logged before any receipt a key it adds may sign, so a verifier can find the roster that governed a receipt by looking at the entries before it.
## 23.2 Checkpoints
A checkpoint, `scienting-checkpoint/1`, is a signed note in the format transparency logs share: an origin line, the tree size, the root hash in Base64, an empty line, and one signature line per signer.
```text
scient.ing/log/smithlab
41
Q2xlYXIgdGV4dCBpcyBub3QgYSByb290IGhhc2guLi4uLi4uLi4=
— ana AAAAAAHZ…
```
The origin names the log, and a team's logs are named `scient.ing/log/<store>` or under a domain the team controls.
A signature line is an em dash (U+2014), a space, the signer's roster key, a space, and Base64 of the signer's four-byte key hash followed by the signature.
The key hash is the first four bytes of SHA-256 of the signer's name, a newline, the byte 0x01 and the Ed25519 public key; the signature is Ed25519 over the note's first three lines, each ending in a newline.
A note parsed into JSON has `format`, `origin`, `size`, `root` as hex, `signatures` as `{name, key_hash, value}` and the `note` text itself; the schema is `checkpoint.schema.json`.
`checkpoint.jsonl` gives a checkpoint over the example paper's receipts and its verification.
A checkpoint may carry further signature lines from witnesses: roster keys with the `witness` purpose, held by independent parties, that cosign only a checkpoint consistent with every earlier one they saw.
A verifier that holds two checkpoints of one log checks a consistency proof between them ([11.5](11-corrections.md#115-the-history-root)); a log that cannot produce one has rewritten its past.
A project accepts a checkpoint when its origin is the project's log, a key with the role `owner` and the `sign` purpose signed it, and at least the project's `threshold` of its listed witnesses cosigned it ([20](20-projects.md)); `fixtures/records/log/checkpoint` is signed by the team and one witness.
## 23.3 What a log settles
When a project names a log in its settings ([20](20-projects.md)), a receipt counts toward a level only when the log holds it, and the log's order replaces the records' own times in two rules:
1. A revocation of a signer's period revokes the receipts logged before the revocation that fall in the period; a receipt logged after a revocation of its key never counts, whatever its `at` says.
2. The roster that governs a receipt is the latest roster entry before it.
A reader fetches the latest checkpoint, verifies its signatures against the roster and its consistency with the checkpoint it last saw, then verifies inclusion proofs for the receipts it relies on.
Without a log the rules of [8.6](08-fidelity.md#86-receipt-status) stand, and the `at` of each record is taken at its word.
## 23.4 Serving a log
A log is served as static files in the tile layout transparency logs share, so that a client fetches only the subtrees it needs and any file server can host a log.
| Path | Holds |
| --- | --- |
| `checkpoint` | The latest checkpoint note ([23.2](#232-checkpoints)). |
| `tile/<L>/<N>` | Hash tile N of level L: the 32-byte hashes of the 256 complete subtrees of 256^L entries each that the tile covers, in order. Level 0 holds the leaf hashes. |
| `tile/<L>/<N>.p/<W>` | The rightmost, partial tile of level L, holding W hashes. |
| `tile/entries/<N>` and `tile/entries/<N>.p/<W>` | The entries themselves, 256 to a tile, as canonical JSON Lines. |
N is written as 3-digit groups, every group but the last prefixed `x`: tile 1234067 is `tile/0/x001/x234/067`.
A tile at level L holds only the hashes of complete subtrees, so the hashes a partial tree still needs are computed from lower levels, as RFC 9162 computes a root from its subtrees.
`fixtures/records/log/` is the example paper's log in this layout, and `checkpoint.jsonl` lists its tile paths.