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 (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 transparency log, served as C2SP tlog-tiles with C2SP 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: line one is the log origin, line two the tree size, line three the base64 root hash, then one Ed25519 signature line per cosigner (the log itself, and each witness). No key required to fetch it, ever. A copy pinned in a doc goes stale the moment the tree grows, so fetch the live one and the key it is signed under rather than trust a printed sample:
# the current signed checkpoint: origin, size, root, then the signature lines
curl -s https://whisper.online/checkpoint
# the dedicated log key that signs it
curl -s https://whisper.online/checkpoint/key
# {"object":"whisper-ledger-key","origin":"whisper.online/ledger/g2","alg":"Ed25519",
# "key_id":"d40e573a","public_key":"7pSYYInKXm2TMjwAqEsoKon/d3E8SDq34V1VUrgTAm0=",
# "public_key_spki":"MCowBQYDK2VwAyEA7pSYYInKXm2TMjwAqEsoKon/d3E8SDq34V1VUrgTAm0="}
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 log runs on its second genesis today, origin whisper.online/ledger/g2, signed by key id d40e573a. That key, and the full set the log may sign under, are published and DNSSEC-anchored as _whisper-ledger.whisper.online TXT records, so you pin the set from signed DNS 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 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. The ledger is tamper-evident and Ed25519-signed, with checkpoints anchored to Bitcoin, and it is independently witnessed by MarkovianProtocol (C2SP tlog-witness): a genuinely separate party, not an availability check between our own boxes. The served claim reaches publicly verifiable / split-view-resistant once a quorum of at least two genuinely independent witnesses is cosigning the served checkpoint (gated in code, self-reverting); MarkovianProtocol is one such witness today, and the live X-Whisper-Ledger-Claim header is the source of truth for the current level. We speak the open C2SP tlog-witness protocol, so any Sigsum/transparency.dev-style witness (a $5 VPS is plenty) can co-sign our checkpoints for free and bring the quorum. Full policy: nic.whisper.online/policy#transparency.
The freshest confirmed anchor
The newest checkpoint's OpenTimestamps proof sits calendar-pending for a few hours before it upgrades to a Bitcoin confirmation, so the freshest root you can anchor a continuity check against is the newest confirmed one, which lags the head. GET /checkpoint/ots/latest-confirmed returns exactly that, keyless:
# the newest Bitcoin-confirmed anchored root
curl -s https://whisper.online/checkpoint/ots/latest-confirmed
# {
# "object": "whisper-ledger-ots-latest-confirmed",
# "tree_size": 18114,
# "root": "dCibwNKApBOjN/HdbzX3XpCM7XOXl5R/3fHCmxz5XwA=",
# "bitcoin_block": 959234,
# "stamped_at_millis": 1784794550778,
# "confirmed_at_millis": 1784797492799,
# "ots": "/ots/18114",
# "consistency": "/consistency?from=18114"
# }
Then GET /consistency?from=18114 from that confirmed size up to the current head proves, with no trust in our clock, that the live tree is an append-only extension of a root already buried in Bitcoin block 959234. Response headers carry the same facts (X-Whisper-OTS-Status: confirmed, X-Whisper-OTS-Bitcoin-Block, X-Whisper-Ledger-Tree-Size); an empty store answers a clean 404, never a 500.
Try it: fetch and verify a checkpoint by hand
With stock tools - curl, openssl, jq, python3. No Whisper software anywhere in this path:
# 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 - pin it from signed DNS,
# which is DNSSEC-anchored, so you trust the chain and not this fetch
dig +short TXT _whisper-ledger.whisper.online
# "v=whisper1; k=ed25519; n=whisper.online/ledger/g2; p=MCowBQYDK2VwAyEA7pSYYInKXm2TMjwAqEsoKon/d3E8SDq34V1VUrgTAm0=" …
curl -s https://whisper.online/checkpoint/key
# {"origin":"whisper.online/ledger/g2","alg":"Ed25519","key_id":"d40e573a",
# "public_key":"7pSYYInKXm2TMjwAqEsoKon/d3E8SDq34V1VUrgTAm0=", …}
# 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. any leaf's inclusion proof, by index: leaf_hash + sibling path + the signed
# checkpoint it folds to, one keyless call (a bad leaf is a clear 400, never a 500)
curl -s "https://whisper.online/inclusion?leaf=0"
# {"object":"ledger-inclusion","leaf":0,"tree_size":429,
# "leaf_hash":"9e47e79ecfecbce8babb888fc7f7e3481b45fb5b1d5c4492671e69a0042e119a",
# "proof":["82f522c46fcaad3f3aa070b468be09dbf1b532e98aa1615b5b1141bf81ca281f",
# "b19e49843367422013a66b799da22f495683d6c9a78e7087e5a5119071dc9647", …],
# "checkpoint":"…"}
# 5. 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":39858,"checkpoint":"…",
# "leaves":[{"index":57,"leaf_hash":"b21f…","inclusion_proof":["9ac3…","5e10…"]}]}
# 6. 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 or 5
proof = [bytes.fromhex(h) for h in ["9ac3...", "5e10..."]]
h, idx, size = leaf, 57, 39858 # size = the tree_size from your own fetch
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. The same checkpoint's witness cosignature is verifiable independently at witness.markovianprotocol.com/checkpoints - a separate party vouching for the very root you just folded to.
With Whisper - one command per step, same math, done for you:
# 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, internal/cli/ledger.go.
On the write side, identity events 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. The tree does have two deliberate, API-key-gated write doors for your own material: POST /entries registers a COSE Signed Statement you signed, and POST /attest commits a typed compliance attestation. Both land as the same opaque, keyless-verifiable leaves as everything else.
SCITT receipts (RFC 9943 / RFC 9942)
This same log supports SCITT: every leaf can be pulled, keyless, as an RFC 9942 COSE receipt under the RFC 9943 architecture (GET /entries/{leaf}, key discovery at /.well-known/scitt-keys), signed by the same Ed25519 key that signs the checkpoint above. Fold the receipt's inclusion proof and it byte-equals the checkpoint root at the same tree size: one tree, two envelopes, verifiable with stock COSE tooling and no Whisper code. The receipt inherits this page's honest gate, log-signed inclusion, and now independent witnessing. Full walkthrough: SCITT receipts.
Point-in-time RDAP, anchored in this same log
The same tree also holds a second leaf kind: not identity events, an observation, "we saw subject X have operator-of-record Y at time T." A keyless "operator of record as of T" endpoint returns an RFC 9083 object plus a SCITT receipt for that observation, so the same fold that proves an identity leaf or a receipt is in this tree proves an RDAP observation is too: one tree, three surfaces. The ?at= query parameter is Whisper's own extension, not a standardized RDAP param, and the honest ceiling is an observation of what we saw, never a claim about the registry's own ground truth. Full walkthrough: Point-in-time RDAP.
Next
DANE & DNSSEC covers the cryptographic pin the ledger's inclusion proofs sit alongside; Compliance & audit covers how crypto-shredding a ledger entry satisfies GDPR Art. 17 without breaking any prior proof.