// private proof of reserves & liabilities

Every ring counted.
No ledger shown.

Mainnet live · checking… Status: Ticker (placeholder, no token exists):
// the working app

Build the tree, hand out proofs, read the vault

Three roles in one page. The operator builds the tree and publishes a commitment; each account holder checks their own proof; anyone reads the reserve wallets live from mainnet and compares them to the published total.

Liabilities ledger

One account per line: account_id, balance_in_SOL. A header row is fine. Balances must be zero or positive with at most 9 decimals. Up to 5,000 rows. This list never leaves the page.

Sample data · randomly generated · not real accounts

Published commitment

This is the only thing the operator publishes: the root hash and the total. Everything else stays private.

Build a tree to see the root hash and total.

Check your proof

Paste the proof the operator gave you, and the root + total they published publicly (website, X, on-chain memo — wherever they committed it). The check runs entirely in this page.

Try to cheat — edit the proof, then it re-verifies

Result

Paste a proof and press Verify.

Reserve wallets

The operator lists the wallets holding reserves. This page reads their live SOL balances from Solana mainnet (getMultipleAccounts through a read-only proxy) and compares them with the published liabilities total. Up to 100 addresses.

Reserves vs liabilities

Add reserve addresses and press Read.
// how it works

Rings of sums, sealed by hashes

  1. A salted leaf per account. Each account gets a fresh 32-byte random salt. Its leaf is SHA-256("reservetree:leaf:v1" ‖ salt ‖ len(id) ‖ id ‖ balance), with the balance (in lamports, unsigned 64-bit) also carried beside the hash as the leaf's sum. Without the salt, nobody can test guesses of an id or balance against a leaf.
  2. Shuffle and pad. Leaves are put in random order and padded to a power of two with zero-balance leaves that have random salts, so a leaf position says nothing about sign-up order and the padding looks like any empty account.
  3. Nodes commit to both child sums. Every parent is (SHA-256("reservetree:node:v1" ‖ hashL ‖ hashR ‖ sumL ‖ sumR), sumL + sumR). Hashing both child sums (not just their total) is what stops an operator from quietly shrinking a branch.
  4. Publish one root and one total. The root hash and the root sum — total liabilities — are the public commitment. They must be posted somewhere public and permanent before proofs are handed out.
  5. Each holder checks their own path. A proof is the holder's id, balance and salt plus, for each level, the sibling's hash and sum. The holder recomputes their leaf, climbs to the root checking every sum is a whole, non-negative number and every parent sum equals its children, and confirms the result matches the published root and total.
  6. Reserves are read live. The listed reserve wallets' lamport balances are fetched from mainnet in one batched read and compared to the published total, giving a live reserves ÷ liabilities ratio.
// privacy model

Who sees what

Kept private

  • The full ledger — it stays in the operator's browser tab
  • Every account id: leaves are salted hashes; only the holder has their salt
  • Other holders' balances: a proof shows sibling subtree sums, not who is in them
  • How many real accounts exist, within a power of two (zero-balance padding)

Still visible

  • The total liabilities and the tree depth, to everyone
  • To each holder: their sibling sums — the level-0 sibling is one other account's exact balance (with no name attached)
  • Reserve wallet addresses and balances — they are public on-chain, and listing them links them to the operator
  • Your leaf position, to the operator (who built it anyway)

Reservetree reduces how much of the ledger has to be exposed to prove solvency. It does not provide anonymity, and it does not hide the operator's reserve wallets.

Known limitations — please read

  • Omitted accounts go unnoticed unless their owners check. An operator can leave out accounts that never verify, shrinking the total. The proof only protects holders who actually check, against a root published before proofs were handed out.
  • Negative-balance trick: blocked by the sum rules. Every sum is an unsigned 64-bit value inside the hashes, and the verifier rejects any sum that is negative, fractional, or doesn't equal its children. A fake negative leaf would show up in some holder's path or would have to sit in a branch whose visible sum is still ≥ 0 — so it can't pull the total below the real balances of holders who check.
  • Point in time. The tree is a snapshot of one moment and reserves are read at one slot. Funds can be borrowed for the snapshot and returned afterwards. Repeated, unannounced snapshots are needed for real assurance.
  • Listing a wallet doesn't prove control. Anyone can paste a whale's address. Real proof of control needs each reserve key to sign a fresh message; this demo does not do that.
  • SOL only, assets only. Reserves are native SOL balances (including rent-exempt lamports). SPL tokens, staked SOL and off-chain debts the operator owes others are not counted.
  • One sibling balance leaks. Your level-0 sibling sum is one other account's exact balance (unnamed). Splitting balances across several leaves would reduce this; this demo keeps one leaf per account.
  • The commitment must be public and fixed. If the operator shows different roots to different people, proofs mean little. Compare the root with others and against a public, timestamped post.
  • Experimental and unaudited. Hand-written for this demo using Web Crypto SHA-256. Mainnet reads go through a public RPC by default and can lag or be rate-limited.