Verify a stablecoin attestation report
ReserveBeat stores the SHA-256 of every issuer attestation report it captures. Pick a report, drop in your own copy of the file, and your browser hashes it and compares the result with the stored hash: MATCH or NO MATCH. The file never leaves your browser. The Apache-2.0 verifier, rb_verify.py, is published here too, with its bundle format and fixtures, each with its SHA-256.
01Reserves
How can I verify a stablecoin's reserves myself?
Check three things, in order. Read the token's total supply on-chain at the date the issuer's report covers. Open the issuer's attestation report, hash it here to confirm it is the copy ReserveBeat captured, and note its reserves and tokens outstanding. Then set the report's figures beside the supply summed across every chain the token is on.
- Read totalSupply() on the token contract at a block on the report's as-of date, on every chain the issuer counts. Each result is in base units; divide it by ten to the power of the contract's decimals.
- Download the issuer's attestation report from the issuer's own site, drop it into the check below, and confirm MATCH against the SHA-256 ReserveBeat stored when it captured the report.
- Add up the supply across those chains and set it beside the tokens outstanding the report states, then beside the reserves it states. Differences in date, scope and chain list explain most gaps; the report's own notes name its scope.
02Check
Check a report file
Your browser computes the file's SHA-256 with WebCrypto. Nothing is uploaded, and the page makes no request while it hashes.
- report
- USDT · 2026-03
- recorded SHA-256
21643a118a8fae0c450863c1086bd0f6a0fd35c68c00d843d9e1e1b0b58c5019- report page
- /attestations/usdt/2026-03
A MATCH states only that the two files are identical. It is not a statement about the report's figures, the issuer or its reserves.
03Reports
Choose a report
Every report the board holds, by token and period. Choosing one loads the SHA-256 recorded when ReserveBeat captured the document.
04Verifier
The independent verifier
rb_verify.py is one Python file, released under the Apache License 2.0 together with every file listed below, so anyone may read, run, change and redistribute it. It reads an evidence bundle, walks the hash chain from genesis, recomputes every anchor's Merkle root, checks the payloads the bundle carries and re-derives each storage proof. It imports nothing from ReserveBeat and makes no network request.
What each check needs
- The chain, the anchors, the Merkle roots, the carried payloads and the storage proofs need only Python 3.10 or later, its standard library and the one pinned package in requirements.txt. Keccak-256 is built in.
- RFC 3161 timestamps need openssl on your PATH. Without it, every timestamp leg that holds a token counts as NOT VERIFIED and the run fails. A leg whose bundle stores no CA chain also counts as NOT VERIFIED unless you pass --tsa-ca with trust roots you choose, such as your system's CA bundle. The bundle run line below passes the Debian and Ubuntu path; on another system, give the path of its own CA bundle.
- OpenTimestamps proofs are counted, not checked. Checking one needs the ots client and a Bitcoin node; the --ots-dir option writes each proof beside the bytes it timestamps, ready for ots verify.
Run it
python3 -m venv .venv && .venv/bin/pip install --require-hashes -r requirements.txt
.venv/bin/python rb_verify.py bundle <bundle.zip> --tsa-ca /etc/ssl/certs/ca-certificates.crt
.venv/bin/python rb_verify.py storage-proof <payload.json>
.venv/bin/python rb_verify.py pdf <report.pdf> --record <seq> --bundle <bundle.zip>Exit status 0 means every check that applies verified, 1 means at least one failed (every failure is listed), and 2 means the input could not be read. The closing banner prints only on exit 0. Records newer than the last daily anchor read PENDING: the banner counts them and says they are not yet timestamped, and once the oldest is more than 30 hours older than the bundle they are OVERDUE and the run fails.
A bundle prepares evidence for examination. It is not an attestation. Verifying one shows that the records, anchors and self-made payloads are the ones committed, in the order and at the times claimed. It does not show that the figures in the underlying documents are true.
05Bundles
Evidence bundles
One zip per published token: every record of the public registry's evidence chain from genesis, every anchor, the manifests the anchors name and the storage proof behind the token's newest proven snapshot. Third-party documents are in it by SHA-256 only. Download one and run rb_verify.py bundle on it.
06Files
The files
| file | SHA-256 | size | download |
|---|---|---|---|
rb_verify.pyThe verifier: one file, Python 3.10 or later, the standard library and one pinned package. | fa95a9c64a761f0dd04cc52bdd4eb24ac95ab4c7f856bb1934126ea12763a9b7 | 56658 bytes | Download rb_verify.py |
requirements.txtThe one pinned package (rfc8785), hash-locked for pip --require-hashes. | 037d29f8c6a15cc4f04892b7d3d5ccf029a9589ddc5e12fb376c4c6b8f3dc763 | 480 bytes | Download requirements.txt |
FORMAT.mdThe evidence bundle format, version 1, and every check the verifier runs. | a49bdb8d3257b3936499c058b6a274745d80220e6d828533d72ace046915a849 | 26013 bytes | Download FORMAT.md |
README.mdHow to install and run the verifier, and what each check needs. | 0b0cdfb5759ae6b9a7fb73286a9ee4c9b5b23b2763781ac9cd2a1e9720c5c59f | 2894 bytes | Download README.md |
LICENSEThe Apache License 2.0 text that covers every file listed here. | cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30 | 11358 bytes | Download LICENSE |
fixtures/README.mdWhat each fixture is and what the verifier must print for it. | 3dd0678994d03856213cf41c740b023f689df21e70421ed5facc5f16439110e3 | 2079 bytes | Download fixtures/README.md |
fixtures/demo-tampered.expected.txtWhat rb_verify.py prints for demo-tampered.zip, byte for byte, naming the changed record. | 3946e5794f8454ebd2cab7ac92bc966551bcfd2725d27d08539b12b654692fba | 9376 bytes | Download fixtures/demo-tampered.expected.txt |
fixtures/demo-tampered.zipdemo.zip with one byte changed, in the upload time of record 500. It must fail. | b2e0a5b71c5fb03bdbf27e9e5f782fff7d988db976b6f2935bccb15d4fe8249d | 287621 bytes | Download fixtures/demo-tampered.zip |
fixtures/demo.expected.txtWhat rb_verify.py prints for demo.zip, byte for byte. | 202f867ef18df4f4635b762df3dd954617d93e9221865790fe680da4c861d192 | 9329 bytes | Download fixtures/demo.expected.txt |
fixtures/demo.zipThe evidence bundle of the synthetic demo tenant: 1,229 records and 19 anchors. Its timestamps come from an offline local timestamp authority, so they are not third-party evidence. It must verify. | 003056835a4dbb5bca5373be64768fe04851570ef519357a6f93283824f30f63 | 287621 bytes | Download fixtures/demo.zip |
fixtures/rfc6962-vectors.jsonRFC 6962 Merkle Tree Hash test vectors; every root must recompute. | 605ac87d318ffef2a94aa05d1566f79c860d4916d9cfb466a5a9ecbab7b1a305 | 1222 bytes | Download fixtures/rfc6962-vectors.json |
fixtures/usdc-storage-proof.expected.txtWhat rb_verify.py prints for usdc-storage-proof.json, byte for byte. | f549a0604299e3a11668b42294f0e86a66ff5b00632e84cd9963bb42c9ccfb5b | 385 bytes | Download fixtures/usdc-storage-proof.expected.txt |
fixtures/usdc-storage-proof.jsonA USDC total-supply storage proof at Ethereum mainnet block 25498038, from real mainnet header and eth_getProof data. It must verify. | b9e46b5bc7245e37b3430e1f96b7d5cdb3571086344e369ab9dc7bd8ecede75f | 73321 bytes | Download fixtures/usdc-storage-proof.json |
To check a download, run sha256sum on it (on macOS, shasum -a 256) and compare the output with the value shown.