Docs / Protocol / Ledger and key pinning
Ledger and key pinning
One issuer key for everyone, pinned on Robinhood Chain behind a 24 hour timelock, and the totals of every active hour anchored next to it.
The contract
PropolisLedger lives on Robinhood Chain (chain id 4663) at 0x9316D05E6b790215C244ee4bF6026F576e6337a4, verified on Sourcify. It does two things: it pins the issuer key, and it records one line per active hour.
Key pinning
Privacy Pass has one known way to cheat: an issuer that signs with a different key for each holder can recognise that holder's tokens later. Propolis closes it by publishing the key id on chain. The Hive and the SDK read currentKey() from PropolisLedger and refuse to mint when the served key differs.
| Field | Value |
|---|---|
| First pinned key | 0x4b2b1ee31ddc863d998ea10ba44b737489d86c0ac37e7d0a892bb83ce188dc47 |
| Rotation | proposeKey(keyId), then activateKey() after a 24 hour delay |
| Reads | currentKey(), pendingKey(), pendingSince() |
A rotation is visible a full day before it takes effect, which gives anyone time to notice and ask why.
Hourly anchors
anchor(uint64 hour, bytes32 keyId, uint32 minted, uint32 spent, uint96 work, bytes32 nullRoot)hour is unix seconds divided by 3,600. Hours are strictly increasing and only hours with activity are written. nullRoot is a binary SHA-256 Merkle root over that hour's nullifiers: leaves sorted, each pair sorted before hashing, an odd leaf promoted, zero when no token was spent.
Verify an hour
# the nullifiers spent in an hour, and the root the relay computed
curl https://propoliscompute.com/api/v1/epoch/<hour>
# the anchored hours with their transactions
curl https://propoliscompute.com/api/v1/epochs?limit=20Recompute the root from the list and compare it with the Anchored event of that hour on Blockscout. If a nullifier you spent is missing, or one appears twice across hours, the relay is lying and the chain shows it.