# Checkpoints

A checkpoint is the transparency log's size and root, signed by the sequencer as a C2SP signed note, and it is the one thing you need to keep to catch a rewritten history later.

## Get the newest checkpoint

```endpoint try
GET /api/log/checkpoint
```

Returns the newest checkpoint as plain text (`text/plain; charset=utf-8`), not cached. No parameters. Before the first block is logged it answers `404` with `no checkpoint yet`.

```bash
curl https://mlx.app/api/log/checkpoint
```

```text title="Response"
mlx.app/ledger/mlx-1
398
y5euvUWfr1GfTZZ9W2qY+v/y3w9qaB9L+J00VgslPX4=

— mlx.app/ledger/mlx-1 dAoLiBOyZ5xVXeU7JgBkvkJ4wV/8/ZvBbicOP4TsnQnNzvq3yWezFEx/fF10adlfmUJfQ4EmTZLa9gDek7gKluD3oAk=
```

The same checkpoint, with its parts split out, is `checkpoint` in [`GET /api/log`](/integrity/overview#the-summary-endpoint).

## The format

The text follows the [C2SP tlog-checkpoint](https://github.com/C2SP/C2SP/blob/main/tlog-checkpoint.md) format inside a [C2SP signed note](https://github.com/C2SP/C2SP/blob/main/signed-note.md):

| Line | Value |
|---|---|
| 1 | The origin, the log's name: `mlx.app/ledger/<chain>`. |
| 2 | The log size: how many blocks it holds. Blocks are numbered from 0, so a size of 398 covers blocks 0 to 397. |
| 3 | The root hash of the log at that size, base64 (32 bytes). |
| 4 | Empty: the end of the note body. |
| 5 | The signature line: `—` (an em dash), a space, the origin, a space, then base64 of the 4-byte key ID followed by the 64-byte Ed25519 signature. |

The **body** is lines 1 to 3, each ending in `\n` (`checkpointBody` in `lib/chain/tlog.ts`). The signature is Ed25519 over the UTF-8 bytes of the body, with the sequencer's key, the same key that signs the blocks.

The **key ID** is the first 4 bytes of `SHA-256(origin ‖ "\n" ‖ 0x01 ‖ public key)`, where `0x01` marks an Ed25519 key, as C2SP defines it. It lets a verifier pick the right signature line when a note carries several.

A new checkpoint is signed whenever the log grows. The server keeps every one: `GET /api/log/checkpoint` returns the newest, and `?size=N` returns the one signed for a log of N blocks (404 if none was).

## Verify a checkpoint

```js title="checkpoint.mjs"
import { ed25519 } from "@noble/curves/ed25519";
import { sha256 } from "@noble/hashes/sha2";
import { bytesToHex, concatBytes, hexToBytes, utf8ToBytes } from "@noble/hashes/utils";

const unb64 = (s) => Uint8Array.from(atob(s), (c) => c.charCodeAt(0));

/** Returns { origin, size, root } if `note` carries a valid signature by `pubHex`, else null. */
function verifyCheckpoint(note, pubHex) {
  const split = note.indexOf("\n\n");
  if (split < 0) return null;
  const body = note.slice(0, split + 1); // origin, size and root, each line ending in \n
  const [origin, size, rootB64] = body.split("\n");
  const keyId = sha256(concatBytes(utf8ToBytes(origin + "\n"), Uint8Array.of(0x01), hexToBytes(pubHex))).slice(0, 4);
  for (const line of note.slice(split + 2).split("\n")) {
    const m = /^— (\S+) (\S+)$/.exec(line);
    if (!m || m[1] !== origin) continue;
    const raw = unb64(m[2]); // 4-byte key ID ‖ 64-byte Ed25519 signature
    if (raw.length !== 68 || bytesToHex(raw.slice(0, 4)) !== bytesToHex(keyId)) continue;
    if (ed25519.verify(raw.slice(4), utf8ToBytes(body), hexToBytes(pubHex))) {
      return { origin, size: Number(size), root: bytesToHex(unb64(rootB64)) };
    }
  }
  return null;
}

// Pin the sequencer key once (here read from /api/log; better: keep the value you saw first).
const { sequencer } = await (await fetch("https://mlx.app/api/log")).json();
const note = await (await fetch("https://mlx.app/api/log/checkpoint")).text();
console.log(verifyCheckpoint(note, sequencer));
```

```text title="Output (when the log held 398 blocks)"
{
  origin: 'mlx.app/ledger/mlx-1',
  size: 398,
  root: 'cb97aebd459faf519f4d967d5b6a98fafff2df0f6a681f4bf89d34560b253d7e'
}
```

The same check ships in the explorer, the wallet and MLX Node (`parseCheckpoint` in `lib/chain/tlog.ts`, ported to Swift). It needs @noble/curves and @noble/hashes (tested with 1.9 and 1.8).

## Keep what you saw

A signature alone only shows the sequencer signed *something*. The protection comes from comparing checkpoints over time:

:::steps
### Save the checkpoint
Keep the size and root (or the whole note) of a checkpoint you verified.

### Later, fetch the new one
Verify its signature the same way. Its size must be at least the size you kept; a smaller size, or the same size with another root, is proof of a rewrite: keep both notes.

### Check that it extends the old one
Ask for a consistency proof from your size to the new size and verify it: [Proofs](/integrity/proofs#consistency-proofs). If it verifies, the new log contains the old one unchanged.

### Keep the new one
Replace the saved checkpoint and repeat next time.
:::

That is what [explorer.mlx.app/integrity](https://explorer.mlx.app/integrity) does with the checkpoint your browser saw last, and what every [witness](/integrity/witnesses) Mac does before it cosigns. A checkpoint is also carried by the [anchors](/integrity/anchors), so you can check one you never fetched yourself.
