# Transparency log

**Nothing issued in the dark.**

Every agent identity Whisper mints — and every revocation — lands in a public, append-only Merkle log the moment it happens. If we ever tried to show two different histories to two different people, the math catches it and says so.

## The problem this solves

A certificate authority, a registry, or an identity provider is, by default, a black box: it tells you an identity is valid and you take its word for it.

That word can be wrong by accident (a mis-issuance) or on purpose — a compromised operator quietly minting a second, conflicting identity for the same address, or showing an auditor a clean history while showing everyone else a different one (the "split-view" attack).

The web PKI learned this the hard way. After the 2011 DigiNotar breach, browsers forced every public CA onto [Certificate Transparency](https://certificate.transparency.dev/) (RFC 6962): a public log every issued certificate must appear in, so mis-issuance is *detectable*, not just *forbidden by policy*.

Agent identity has the same failure mode and deserves the same fix. A `/128` and its DANE pin are exactly the kind of high-value binding you don't want to take on trust — so we don't ask you to.

## The mechanism: an RFC 6962 Merkle tree

The ledger is a standard [RFC 6962](https://www.rfc-editor.org/rfc/rfc6962) transparency log, served as [C2SP `tlog-tiles`](https://c2sp.org/tlog-tiles) with [C2SP signed-note](https://c2sp.org/signed-note) checkpoints — the same log shape as Certificate Transparency and Sigsum, not a bespoke format. Two hash rules, domain-separated by a leading byte so a leaf can never be mistaken for an interior node:

```
leaf     = SHA-256(0x00 ‖ commitment)
interior = SHA-256(0x01 ‖ left ‖ right)
```

Adding a leaf changes the root; any leaf can prove its own membership with a short **inclusion proof** (the sibling hashes on the path to the root), and any two tree sizes can prove one is a strict extension of the other with a **consistency proof** — the log can only grow, never rewrite.

**What's actually in a leaf.** Not the `/128`, not the FQDN, not the owner — a bare, opaque commitment: `commitment = SHA-256(salt ‖ canonical-event)`, where `salt` is a fresh 256-bit secret minted per event and the event is a deterministic encoding of "this `/128`+name was issued to this owner at time T" (or revoked, or rotated). The tree itself is unreadable — that's deliberate. It reconciles two things that normally fight: public verifiability and GDPR Art. 17 erasure. The `(salt, event)` pair lives outside the append-only tree, in a store that *can* delete. Verifying what a commitment means requires the subject (or an auditor they've authorized) to disclose that pair; you then recompute the hash and check it folds to the signed root — **selective disclosure, not global readability.** Erase the salt and the leaf's meaning becomes unrecoverable forever, while every prior inclusion proof stays mathematically valid — the log doesn't have to lie about its own history to honor a right to be forgotten.

## Signed checkpoints

The current state of the tree is published as a **signed checkpoint** — a plain-text [C2SP note](https://c2sp.org/signed-note): origin, tree size, base64 root hash, and an Ed25519 signature line naming the key that made it. No key required to fetch it, ever:

```
whisper.online/ledger
134
fWOenkL0m3rQx9s2VbC7pXk4hT1uJ8dNzYqR6oAsE2c=

— whisper-ledger-log+8a3a5df0 Ay7Jf3Kd0aBnQe2Ls9V…
```

The signing key is **dedicated** — it signs ledger checkpoints and nothing else (not RDAP, not the identity document), so a compromise or rotation of one never touches the others. The public key and key ID (`8a3a5df0`, `ce79119a…`) are themselves published and DNSSEC-anchored (`_whisper-ledger.whisper.online` TXT), so you can pin it once and verify forever without re-trusting a web fetch each time.

## Anchored to Bitcoin, honestly

A signature proves *Whisper* attests to a root; it doesn't prove *when*, and it doesn't stop us from moving our own clock. So every signed checkpoint's 32-byte root is submitted to public [OpenTimestamps](https://opentimestamps.org/) calendars, which batch many commitments into one aggregate and periodically timestamp that aggregate into a real Bitcoin transaction. Once the transaction is buried a few blocks deep, the proof upgrades from a *calendar promise* ("pending") to a *Bitcoin fact* ("confirmed") — anyone can verify the upgraded proof offline against raw Bitcoin block headers, with no OpenTimestamps service and no trust in Whisper's clock. Only the 32-byte root ever touches the chain — no event count, no identity data, nothing to link.

> **Honest status.** Today the ledger is **tamper-evident and Ed25519-signed**, with checkpoints anchored to Bitcoin. It is **not yet independently witnessed** — ns1 and ns2 co-signing each other is an availability check, not independence, since both are Whisper's own infrastructure. We speak the open [C2SP `tlog-witness`](https://c2sp.org/tlog-witness) protocol so any Sigsum/transparency.dev-style witness (a $5 VPS is plenty) can co-sign our checkpoints for free; the day one does, this page updates to "publicly verifiable." Full policy: [nic.whisper.online/policy#transparency](https://nic.whisper.online/policy#transparency).

## Try it: fetch and verify a checkpoint by hand

**With stock tools** — `curl`, `openssl`, `jq`, `python3`. No Whisper software anywhere in this path:

```bash
# 1. the signed checkpoint (a C2SP note: origin / size / root / signature)
curl -s https://whisper.online/checkpoint

# 2. the log's public key + key id, to verify against
curl -s https://whisper.online/checkpoint/key
# {"key_id":"8a3a5df0","algorithm":"ed25519","public_key":"ce79119a…"}

# 3. verify the Ed25519 signature (OpenSSL ≥3.0 verifies Ed25519 with -rawin)
openssl pkeyutl -verify -rawin -in checkpoint.body \
  -pubin -inkey ledger.pub -sigfile checkpoint.sig

# 4. one agent's inclusion proof — no key needed, RFC 9083 RDAP arm
curl -s https://rdap.whisper.online/ip/2a04:2a01:eb5a:ca74:cef2:2a:323d:40d4/transparency | jq .ledger
# {"tree_size":134,"checkpoint":"…",
#  "leaves":[{"index":57,"leaf_hash":"b21f…","inclusion_proof":["9ac3…","5e10…"]}]}

# 5. fold the proof to the signed root yourself
python3 - <<'PY'
import hashlib
def node(l, r): return hashlib.sha256(b"\x01" + l + r).digest()
leaf = bytes.fromhex("b21f...")          # from step 4
proof = [bytes.fromhex(h) for h in ["9ac3...", "5e10..."]]
h, idx, size = leaf, 57, 134
for sib in proof:
    h = node(sib, h) if idx % 2 else node(h, sib)
    idx //= 2
assert h.hex() == "<root from step 1>"    # matches the signed checkpoint
PY
```

The Merkle-fold logic is public domain math (RFC 6962 §2.1.1) — nothing here needs a client library.

**With Whisper** — one command per step, same math, done for you:

```bash
# fetch + verify the latest checkpoint under the published key
whisper ledger checkpoint

# prove a disclosed (salt, event) is included in the signed tree
whisper ledger verify 2a04:2a01:eb5a:ca74:cef2:2a:323d:40d4 \
  --salt 9f1e4c2a... --event-file event.bin

# the full trustless verdict — DNSSEC root → DANE pin → this ledger → identity
# document, chained, with Whisper's API trusted for nothing:
whisper verify --trustless 2a04:2a01:eb5a:ca74:cef2:2a:323d:40d4
```

`whisper ledger checkpoint` fetches the checkpoint and key, verifies the Ed25519 signature, and prints `VERIFIED`. `whisper ledger verify` additionally fetches the inclusion proof and folds it to the root for you — exit code 0 means cryptographically included, exit 1 means it is not. Full source for both: [github.com/whisper-sec/whisper-cli](https://github.com/whisper-sec/whisper-cli), `internal/cli/ledger.go`.

On the write side, you never call the ledger directly — every `whisper.agents({op:'register'|'identity'|'revoke'})` call appends the corresponding event automatically as part of issuing or tearing down a `/128`. The log is a side effect of using the control plane honestly, not a separate thing you have to remember to do.

## Next

[DANE & DNSSEC](/docs/dane) covers the cryptographic pin the ledger's inclusion proofs sit alongside; [Compliance & audit](/docs/compliance) covers how crypto-shredding a ledger entry satisfies GDPR Art. 17 without breaking any prior proof.
