Stellar testnetOpenZeppelin Confidential Tokens v0.9.0

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.

Demo video slot. Until the video exists, this is a still of the round explainer below: real addresses and ciphertext from round-004, illustrative names.

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. 1. Declare. Alice records the round id and its lanes in the round registry. The contract stamps the opening ledger.
  2. 2. Pay. Each lane sends confidential transfers. Sender and recipient addresses are public; the amount is sealed.
  3. 3. Close. Alice closes the round. The contract stamps the closing ledger, fixing the window.
  4. 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.

Designed, not built

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.

Transfers in the window, read from Stellar testnet RPC (https://soroban-testnet.stellar.org) on 2026-10-05. The amount column is the on-chain ciphertext.
LedgerFrom (lane)ToAmount fieldTransaction
5032844GBU5CF…QOJDGBCT2F…DVKG06b8e2c7…311b1e4bcf65…48d7c5
5032844GAJLWU…B5P5GAFJPJ…7QIU285d7772…44d38fb35a94…e04f46
5032844GDJOA6…7CMUGD7DRW…3UWQ187a4d47…3c7617fa5011…ed0caa
5032844GDOIF6…6XE7GD27FF…EBOP1a428eb1…17c67f12e816…e00d0c
5032844GDND23…3E5PGCNZ57…77CO0623ad79…eb977225eb35…889ac2
5032846GDND23…3E5PGAKWB6…4END0441c02b…7fa31ea0b2b0…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.

Verify this round

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.

ProjectPrivacy systemWhat it coversRelation to Tally
Remi (SCF #44)OpenZeppelin Confidential TokensAuditor dashboard, compliance API and selective disclosure planned for its own remittance deploymentClosest funded overlap on the same standard. Tally aims to serve other issuers, with split custody and aggregate proofs
Arcane (SCF #42)Its own shielded poolScoped disclosure by role and time window, logged, with a case portalSame operator functions on a different privacy system
ZKELLA (SCF #45)Its own shielded tokenViewing keys, sanctions non-membership, planned auditor APIOwn stack; no aggregate disclosure
Moonlight (SCF #37)Its own privacy channelsPrivacy providers run audit flows inside MoonlightThe operator role exists, inside Moonlight only
Haven (SCF #45)Not chosen yetPrivacy-first consumer neobank, pre-launchAdjacent: a possible user of an operator
SDF, OpenZeppelin, NethermindFirst-party standardsSpecs, reference contracts, demo auditor UI, allow/deny treesSupply the tools; SDF asked for someone to run the operator role

Full comparison in the docs

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.