Skip to main content

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.

  1. 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.
  2. 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.
  3. 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
PYUSD · 2026-06
recorded SHA-256
bc79397efc2ab2d5424012428c864bb121efa6e81099a2f28e6d0bfdbd2f0697

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.

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

The files
fileSHA-256sizedownload
rb_verify.pyThe verifier: one file, Python 3.10 or later, the standard library and one pinned package.fa95a9c64a761f0dd04cc52bdd4eb24ac95ab4c7f856bb1934126ea12763a9b756658 bytesDownload rb_verify.py
requirements.txtThe one pinned package (rfc8785), hash-locked for pip --require-hashes.037d29f8c6a15cc4f04892b7d3d5ccf029a9589ddc5e12fb376c4c6b8f3dc763480 bytesDownload requirements.txt
FORMAT.mdThe evidence bundle format, version 1, and every check the verifier runs.a49bdb8d3257b3936499c058b6a274745d80220e6d828533d72ace046915a84926013 bytesDownload FORMAT.md
README.mdHow to install and run the verifier, and what each check needs.0b0cdfb5759ae6b9a7fb73286a9ee4c9b5b23b2763781ac9cd2a1e9720c5c59f2894 bytesDownload README.md
LICENSEThe Apache License 2.0 text that covers every file listed here.cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d3011358 bytesDownload LICENSE
fixtures/README.mdWhat each fixture is and what the verifier must print for it.3dd0678994d03856213cf41c740b023f689df21e70421ed5facc5f16439110e32079 bytesDownload fixtures/README.md
fixtures/demo-tampered.expected.txtWhat rb_verify.py prints for demo-tampered.zip, byte for byte, naming the changed record.3946e5794f8454ebd2cab7ac92bc966551bcfd2725d27d08539b12b654692fba9376 bytesDownload fixtures/demo-tampered.expected.txt
fixtures/demo-tampered.zipdemo.zip with one byte changed, in the upload time of record 500. It must fail.b2e0a5b71c5fb03bdbf27e9e5f782fff7d988db976b6f2935bccb15d4fe8249d287621 bytesDownload fixtures/demo-tampered.zip
fixtures/demo.expected.txtWhat rb_verify.py prints for demo.zip, byte for byte.202f867ef18df4f4635b762df3dd954617d93e9221865790fe680da4c861d1929329 bytesDownload 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.003056835a4dbb5bca5373be64768fe04851570ef519357a6f93283824f30f63287621 bytesDownload fixtures/demo.zip
fixtures/rfc6962-vectors.jsonRFC 6962 Merkle Tree Hash test vectors; every root must recompute.605ac87d318ffef2a94aa05d1566f79c860d4916d9cfb466a5a9ecbab7b1a3051222 bytesDownload fixtures/rfc6962-vectors.json
fixtures/usdc-storage-proof.expected.txtWhat rb_verify.py prints for usdc-storage-proof.json, byte for byte.f549a0604299e3a11668b42294f0e86a66ff5b00632e84cd9963bb42c9ccfb5b385 bytesDownload 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.b9e46b5bc7245e37b3430e1f96b7d5cdb3571086344e369ab9dc7bd8ecede75f73321 bytesDownload 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.