DisclosureandauditforStellar'sprivacytokens.
Tally is the disclosure and audit service for Stellar's privacy tokens: confidential payouts to many recipients, with totals an outside party can verify, and auditor access that no single party controls.
Confidential one-to-many payouts are the first use case, already working on testnet. The operator service (split auditor-key custody, scoped audit requests, tax export) is designed, not built.
Payer · on-chain
Alice declares the round
5 lanes and a ledger window, recorded in the round registry before anything is paid.
window [5032842, 5032851]
Stellar testnet · what anyone can read
- Bob GBCT2F…DVKG06b8e2c7…311b
- Charlie GAFJPJ…7QIU285d7772…44d3
- Erin GD7DRW…3UWQ187a4d47…3c76
- Frank GD27FF…EBOP1a428eb1…17c6
- Grace GCNZ57…77CO0623ad79…eb97
16 transfers in the window. Each amount field is ciphertext; the chain never holds an amount in plaintext.
Donor · off-chain, on Dave's machine
One zero-knowledge proof, 16,224B, answering Dave's own challenge
3,160
Verified total from the 5 declared lanes in round-004, over 16 transfers. No individual amount revealed.
Built on Stellar's privacy standard
Confidential Tokens
Stellar's developer preview: private balances and amounts, public addresses
OpenZeppelin contracts
Token, verifier and auditor from stellar-contracts
Branch tracked
v0.9.0 @ df602b6
Network
Stellar testnet, protocol 29
Tally builds on these open-source components. No endorsement by, or partnership with, Stellar, SDF or OpenZeppelin is implied.
A public ledger shows
every amount, forever.
Grant programmes, bounty platforms, public-goods funding and aid disbursement pay many people in public. On a public ledger every recipient's amount is visible to anyone, which exposes the people being paid. Paying off-chain hides the amounts but gives up the audit trail.
Recipients are exposed
An ordinary payment publishes its amount next to the recipient's address, permanently.
Off-chain loses the trail
Moving payouts off-chain hides amounts, and also removes the record a donor or auditor could check.
Tally keeps both
Amounts stay sealed on-chain; the round's total stays provable to whoever is entitled to check it.
Who it is for
Three parties take part in a round. Each gets something different.
The payer
Pays many recipients from lane accounts it declared on-chain before the round opened.
Amounts stay sealed on the ledger. When a donor asks, the payer answers with one zero-knowledge proof of the round's total.
The recipient
Registers a confidential account from their own wallet signature; the key never leaves their device.
Their amount is private from the public ledger and from whoever runs the payout.
The donor or auditor
Checks the total with their own key and a fresh nonce. No secret of the payer's is ever handed over.
Learns the total and how many transfers it covers, and nothing about any single amount.
How a round works
Alice declares a round and pays through several lanes. The chain records who paid whom and holds only ciphertext for each amount. Dave, a donor, checks one proof of the total on his own machine.
Payer · on-chain
Alice declares the round
5 lanes and a ledger window, recorded in the round registry before anything is paid.
window [5032842, 5032851]
Stellar testnet · what anyone can read
- Bob GBCT2F…DVKG
- Charlie GAFJPJ…7QIU
- Erin GD7DRW…3UWQ
- Frank GD27FF…EBOP
- Grace GCNZ57…77CO
16 transfers in the window. Each amount field is ciphertext; the chain never holds an amount in plaintext.
Donor · off-chain, on Dave's machine
One zero-knowledge proof, 16,224B, answering Dave's own challenge
3,160
Verified total from the 5 declared lanes in round-004, over 16 transfers. No individual amount revealed.
- 1. Declare. Alice records the round id and its lanes in the round registry. The contract stamps the opening ledger.
- 2. Pay. Each lane sends confidential transfers. Sender and recipient addresses are public; the amount is sealed.
- 3. Close. Alice closes the round. The contract stamps the closing ledger, fixing the window.
- 4. Verify. Dave issues a challenge, receives a proof, and checks it against every transfer the chain shows from the declared lanes in the window.
Use cases
Anyone who pays many people from one pool and has to account for it in public. Tally has no users today; these are the situations it is built for.
Grant programmes
Pay grantees without publishing each award; show funders the programme total.
Hackathon prizes
Pay winners privately and prove the prize pool went out in full.
Payroll
Keep salaries off the public ledger while an auditor can check the payroll total.
Aid disbursement
Protect recipients from being targeted, and give donors a checkable total.
DAO contributor payouts
Pay contributors without exposing each payment; prove the treasury outflow per round.
Tax and audit reporting
Statements for one account and period, and auditor access under a scoped request.
Needs the operator service: designed, not built
What an outside verifier learns, and does not
Learns
- The total sent from the declared lanes inside the declared window.
- How many transfers that total covers (the circuit publishes the count).
- The lane set and the window, read from the chain, not from the payer.
- That the proof answers the verifier's own challenge and nonce.
Does not learn
- Any individual amount.
- Which recipient received how much.
- Anyone's balance.
- Any secret of the payer's: the verifier holds only its own disclosure key.
The trust statement, verbatim
The donor is assured that the confidential transfers sent from the lane accounts Tally declared on-chain before the round opened, within that round's declared ledger window, total exactly the disclosed amount and that none has been withheld — because every confidential transfer publishes its sender address on-chain whether or not the funder chooses to disclose it, so an omitted transfer is visible as one the proof fails to cover.
This does not assure that the funder made no other payments, nor that the recipients are independent of the funder: the guarantee is scoped to transfers from the accounts declared before the round, not to the funder's total spend, and it establishes what amounts moved, not who ultimately controls the accounts that received them.
The operator service, planned
The compliance roles that OpenZeppelin Confidential Tokens and Nethermind's Stellar Private Payments define but leave to someone else. This is a design document today; none of it runs.
Split auditor-key custody
The auditor key is generated in pieces and held two-of-three by the issuer, Tally and an independent custodian. No single party, Tally included, can decrypt alone.
Scoped audit requests
A request names accounts, a ledger range and the data wanted. Each custodian checks scope itself and contributes only for events it resolves from the chain. Every step is logged.
Selective disclosure
The aggregate total over a round is built today. Per-period, per-counterparty and inbound totals, and auditor-attested totals, are designed.
Tax export
A statement for one account and period, generated by the holder with their own key, with the transaction references and the proofs of its totals. Not tax advice and no fiat valuation.
The design's weakest point is stated in it: for clawback and auditor-side proofs, the auditor key would be reassembled briefly inside an attested enclave. Read the design.
Live evidence
A real round on Stellar testnet, published as round-004 on 2026-10-05. Anyone with the repository can verify it until about ledger 5,153,815, when its events leave the RPC's seven-day window.
The round: round-004
- Funder
- GBMWOS47…LR654R
- Round id
- 030a11181f26…ced5dc
- Window
- ledgers 5,032,842 to 5,032,851
- Transfers
- 16 across 5 declared lanes
- Verified total
- 3,160 stroops of the wrapped asset, from the declared lanes in round-004
Contracts (testnet)
- Token
- CDRRP2JF…QCURGN
- Verifier
- CAGL7SYH…QJB5LL
- Auditor
- CDMJCKJG…C5AMW7
- Round registry
- CDWIXFBO…S7BK7W
From demo/deployment.testnet.json. OpenZeppelin stellar-contracts v0.9.0 @ df602b613fbc4ae1e98ff62ba3caef671a370f65.
| Ledger | From (lane) | To | Amount field | Transaction |
|---|---|---|---|---|
| 5032844 | GBU5CF…QOJD | GBCT2F…DVKG | 06b8e2c7…311b | 1e4bcf65…48d7c5 |
| 5032844 | GAJLWU…B5P5 | GAFJPJ…7QIU | 285d7772…44d3 | 8fb35a94…e04f46 |
| 5032844 | GDJOA6…7CMU | GD7DRW…3UWQ | 187a4d47…3c76 | 17fa5011…ed0caa |
| 5032844 | GDOIF6…6XE7 | GD27FF…EBOP | 1a428eb1…17c6 | 7f12e816…e00d0c |
| 5032844 | GDND23…3E5P | GCNZ57…77CO | 0623ad79…eb97 | 7225eb35…889ac2 |
| 5032846 | GDND23…3E5P | GAKWB6…4END | 0441c02b…7fa3 | 1ea0b2b0…3e38bd |
Showing 6 of 16. All of them are listed in the quickstart output and in the evidence pack.
Try it yourself
In your browser with no clone, or from a clone in three steps with the output recorded in the repository from the last run.
No clone needed
Verify the published round in your browser
Your browser reads the round and its transfers from testnet RPC, rebuilds every public input and checks the proof with bb.js, using the same code as tally verify. Two buttons let you tamper with the bundle and watch it fail.
Step 1
Clone and install
git clone --recurse-submodules https://github.com/Tally-Network/Tally cd Tally && pnpm install
Node and pnpm. Verifying needs no Noir toolchain: the compiled circuits are in the repository.
Step 2
Verify the published round
pnpm verify:evidence
Expected output
✓ round found: 5 lanes, window [5032842, 5032851] ✓ 16 transfers from the declared lanes inside the window ✓ public inputs reconstructed from chain state only ✓ verification key matches the pinned artifact (1760 B) ✓ proof verified (16224 B, zero-knowledge) TOTAL DISBURSED: 3160 (stroops of the wrapped asset) over 16 transfers from 5 declared lanes, ledgers 5032842–5032851 no individual amount was revealed.
Exit 0 means verified, 2 means the proof was rejected, 3 means the round aged out of the RPC window, 1 means it could not be checked.
Step 3
Run a fresh round yourself
pnpm demo
Expected output
resolved from chain: 5 lanes, window [5032842, 5032851] transfers from declared lanes (all time) : 17 inside the declared window : 16 excluded by the window : 1 <- the pre-round transfer proof 16224B in 2032ms, spans 5 sender accounts donor total = 3160 expected 3160 MATCH === ROUND VERIFIED ===
Creates fresh testnet accounts with friendbot and takes several minutes. One transfer is sent before the round opens and must be excluded.
Measured
Re-measured on 2026-10-05 on Stellar testnet, protocol 29. Method, raw data and transaction hashes are in MEASUREMENTS.md.
Confidential transfer
91,193,854
CPU instructions, 22.8 % of the 400,000,000 per-transaction cap
Transfers in one transaction
4
From one sender. At 4: 90.8 % of instructions and 95.1 % of size
Aggregate proof size
16,224 B
Zero-knowledge, the same size for 8, 16 or 64 transfers
On-chain check of a 64-transfer proof
37.7 %
Of the cap. It fits; Tally still verifies off-chain, for privacy
Register an account
88,344,016
CPU instructions, 22.1 % of the cap
Transfer proof
1.32 s
Warm, on an Apple M4 Pro
Four transfers in one transaction landed on testnet in fe362834…b59384. Earlier figures (one transfer per transaction, under a 100,000,000 cap) are superseded. MEASUREMENTS.md
Safeguards
Checks that run on every change, and the tampering cases the verifier rejects.
Secret guard
Runs first in CI and fails the build if a Stellar seed, a private key, or a hex secret appears in any tracked file. It also detects the retired demo auditor key.
Pinned verification keys
The verifier refuses a proof whose circuit key differs from the pinned file. CI rebuilds the circuits and fails on any drift.
Upstream vectors
The client reproduces all 19 OpenZeppelin v0.9.0 test vectors for the primitives it uses. One spender-only vector is not used.
Tampering rejected
A bundle whose sealed total was altered by one, and a bundle replayed against a different challenge, both fail with exit code 2.
How Tally differs
Others on Stellar cover parts of this. The full, sourced comparison is in the docs.
| Project | Privacy system | What it covers | Relation to Tally |
|---|---|---|---|
| Remi (SCF #44) | OpenZeppelin Confidential Tokens | Auditor dashboard, compliance API and selective disclosure planned for its own remittance deployment | Closest funded overlap on the same standard. Tally aims to serve other issuers, with split custody and aggregate proofs |
| Arcane (SCF #42) | Its own shielded pool | Scoped disclosure by role and time window, logged, with a case portal | Same operator functions on a different privacy system |
| ZKELLA (SCF #45) | Its own shielded token | Viewing keys, sanctions non-membership, planned auditor API | Own stack; no aggregate disclosure |
| Moonlight (SCF #37) | Its own privacy channels | Privacy providers run audit flows inside Moonlight | The operator role exists, inside Moonlight only |
| Haven (SCF #45) | Not chosen yet | Privacy-first consumer neobank, pre-launch | Adjacent: a possible user of an operator |
| SDF, OpenZeppelin, Nethermind | First-party standards | Specs, reference contracts, demo auditor UI, allow/deny trees | Supply the tools; SDF asked for someone to run the operator role |
Built, designed, and the limits
All three at the same weight. Items come from the repository's scope ledger and demonstration status.
Built
- Aggregate disclosure circuits for 8, 16 and 64 transfers, zero-knowledge, with a minimum group size enforced in the circuit
- Round registry contract on testnet, 16 tests, 10 of them negative
- Client ported to OpenZeppelin v0.9.0, passing the upstream test vectors
- End-to-end round, donor-verified, from one command
- Standalone challenge, prove and verify command-line tool
- Published round verifiable from a clean clone
- Non-custodial registration, tested against live testnet
Designed, not built
- Split auditor-key custody (two-of-three)
- Scoped audit requests with a public log
- Per-period, per-counterparty and inbound disclosure proofs
- Tax export statement
- Allow and deny lists for both privacy systems
- Durable event archive
- Auditor client that decrypts every event type
Limits
- Testnet only. Nothing has been audited
- OpenZeppelin's confidential tokens are a developer preview on an untagged branch
- Verification relies on about seven days of RPC events; there is no archive yet
- No users, integrations or partners
- Recipient independence is not verified
- The demo sends one transfer per transaction
- Registration runs headless; there is no wallet page yet
- In the design, the auditor key is reassembled briefly in an enclave for some proofs
Roadmap
No dates. The last column depends on a decision outside this project.
Working now, on testnet
Confidential one-to-many payouts, the aggregate proof of the total, the round registry, the verification tool and a published round.
Next, on testnet
The auditor client and an event archive; split auditor-key custody; scoped audit requests with a public log; allow and deny lists; more disclosure proofs and tax export; a pilot with one issuer.
After SDF approves Confidential Tokens for mainnet
Mainnet deployment, hardware-backed key shares, auditor-side proofs through an attested enclave, payout batching, and fixes from an external audit.
Questions
Team
Jagadeesh B
Stellar India Ambassador
Builds on Soroban and ships in the open: the measurements, the negative results and the upstream issues are published in the repository.
Run a round, read the design, or help shape the operator.
Issuers, pool operators and teams paying many recipients on Stellar can talk to us as design partners. Open an issue on GitHub and say what you need.