Yamale docs ← back to the site

Payment confidentiality

Current position (2026-08-19). Confidentiality comes from architecture, not cryptography: a tiered ledger where individual customer payments never reach the chain, read authorisation over what remains, and encrypted metadata for the detail. Confidential amounts by zero-knowledge proof are deferred, for reasons measured and recorded at the end of this document. The reserved proto fields stay reserved, so the door remains open at no cost.

Read the tiering resolution first; the sections between are the reasoning that led there and remain accurate.

Originally decided: confidential amounts, using Pedersen commitments and range proofs, with viewing keys granted to the parties and functions that need to see. This note records what that means, what must deliberately stay public, and what has to happen now versus what can follow.


Hash and encryption are not the same tool

Worth stating first, because the two get conflated and they do opposite jobs.

A hash is a fingerprint. Putting one on-chain proves a payload existed and has not been altered since. Nobody can read anything from it — not the parties, not the regulator, not anyone. It is for integrity.

Encryption with viewing keys is what lets specific named parties read. That is what "both ends plus the regulator can see it" requires.

The design uses both: a hash on-chain so the record is permanent and tamper evident, and an encrypted payload — held off-chain — that the payer, the payee and one regulator can decrypt.

What is hidden

Participant-to-participant transfers of issued currency: the amount becomes a commitment rather than a figure, and the ISO 20022 metadata (remittance information, purpose codes, references) becomes an encrypted payload with a hash on-chain.

The mechanism is standard and deployed elsewhere. A Pedersen commitment is C = aG + rH, where a is the amount and r a blinding factor. Commitments add up — C₁ + C₂ commits to a₁ + a₂ — so the chain can verify that what went in equals what came out without ever seeing either figure. A range proof accompanies it to prove the amount is not negative, which without it would be an inflation bug rather than a privacy feature.

What stays public, and why

This is the part that has to be got right, because "encrypt everything" produces a ledger that cannot do its job. Each of these is public because a designed function depends on it:

AMM pool reserves. The ratio of the reserves is the price. A pool with a secret reserve cannot quote, and a chain that cannot quote cannot route a swap. Pool operations are public, and that is not a compromise — a market price is supposed to be public.

Issuance and redemption. Mint and burn by the issuer stay in the clear, so total supply remains publicly provable. This directly answers the objection a central bank will raise first: individual holdings are private, but how much currency exists is arithmetic anyone can check. Confidentiality applies between holders, never to the money's creation.

Staking weights and voting power. Consensus safety depends on knowing who holds what weight, and the concentration caps are computed from exactly these numbers. Hiding them would break both.

Fees. Every validator must verify the fee was paid.

Oracle rates. The whole point of them is publication.

Treasury commitments. Kept public by default. The reason to use x/treasury at all is that a commitment provably left spendable balance and cannot be redirected — for donor disbursement, subsidy programmes and public payroll, the auditability is the product. A confidential option can exist later for commercial escrow; it is not the default.

Enforcement amounts. A seizure has to be visible to the ombudsman and countable against the rolling-window cap. A secret seizure is the thing the oversight design exists to prevent.

So the line is: confidential between holders, public at the edges. That is both defensible to a regulator and the only version that leaves the system able to function.

Who holds a viewing key

Because the payload is encrypted to the regulator's key at the moment of sending, the settlement jurisdiction has to be declared then. It cannot be decided later.

What this does not fix

Stated plainly, because overselling it is how a pilot ends badly.

The graph survives. Who paid whom, and when, remains visible even when amounts and purpose are not. Identifying one address later still exposes its counterparties across all of history. Only a fully shielded pool removes that, and no financial regulator will accept one. This is a large reduction in exposure, not an elimination, and it should be described that way in every external document.

Individual balances stop being enumerable. The chain can prove supply is conserved; it cannot list who holds what without the keys. That is the intent, but it changes what an explorer can show and what a supervisor's tooling must do.

A lost blinding factor makes funds unspendable. If a wallet loses r, the owner cannot prove the commitment is theirs. This is a genuine new way to lose money that does not exist today, and it lands squarely on the account service and the threshold-custody decision still open in §8 of the scope. It must be designed before this ships, not after.

This is the largest single item in the specification. It is research-grade work, it needs its own audit, and it touches the AMM, treasury and enforcement. It should not be estimated as a sprint.

Sequence

Now, and genuinely urgent — reserve the proto field numbers. Before a pilot writes a single payment, MsgPayment gets reserved fields for the commitment, the range proof, the encrypted payload hash and the declared settlement jurisdiction. Field numbers cost nothing today and cannot be reclaimed once a deployment holds real balances. This is the same irreversibility class as the genesis-counter and import-uniqueness decisions already made in x/land.

Immediately after — encrypt the metadata. remittance_information and purpose_code move to an encrypted payload with a hash on-chain. No new cryptography, and it closes the concrete personal-data exposure: ISO 20022 free text is where operators actually put names, and it is currently written to an append-only ledger with no erasure path under the NDPA, the DPA, POPIA or GDPR.

Then — commitments and range proofs, as its own workstream with its own audit.


Confidential amounts: assessed 2026-08-19, not built

The decision to use commitments and range proofs stands. Building it now does not, on evidence gathered by measurement rather than reading.

There is no audited Go library

github.com/coinbase/kryptology is the only credible pure-Go Bulletproofs implementation. It is archived, unaudited, its README disclaims it — and it is broken: getaL in range_prover.go extracts bits assuming a little-endian scalar encoding, so on secp256k1, P-256 and BLS12-381 it range-proves a different number from the one the commitment binds, and its own verifier rejects the proof. Measured across all five curves; the encoding split matches the pass/fail split exactly. Its tests only ever use ed25519.

The ecosystem around it is not reassuring either. ING's zkrp was forgeable and the repository was deleted rather than fixed after Trail of Bits' Frozen Heart disclosure. Solana disabled its ZK ElGamal Proof Program in June 2025 — the same architecture — over a value missing from the Fiat-Shamir transcript that allowed arbitrary mint and burn, and it remains disabled. Sei's Go implementation is dormant and its module does not exist at any tag. The SDK's own confidential transfers module missed its target. Every Cosmos privacy chain that shipped is Rust.

gnark is the exception: Apache-2.0, actively maintained, five third-party audits in-repo. It is SNARKs rather than Bulletproofs, trading a trusted setup for the audit problem.

What it costs

proof verify
plaintext payment today — 31 µs
kryptology Bulletproof (64-bit) 672 B 34.7 ms
gnark Groth16 196 B 1.56 ms

Roughly 6 confidential transfers per second with Bulletproofs, 130/s with gnark, against ~32,000/s of execution headroom today. Neither library offers batch verification, so the aggregation result Bulletproofs are famous for is not reachable from Go — there is no escape hatch. 130/s is a credible interbank rail and consistent with §6's tiered architecture, but confidentiality moves this chain from consensus is the limit to execution is the limit, and any throughput claim must be restated accordingly.

Two corrections to the design above

An account-based pool serialises transfers per sender. Each proof is against the sender's current commitment, so two payments from one participant in the same block invalidate each other — worst on the highest-volume message on the chain. Zether uses epoch buckets; Solana splits pending from available balance with an explicit apply step. Whichever is chosen introduces a BeginBlocker, and therefore a divisor to guard.

Supply provability needs one more element. With bare Pedersen commitments the sum identity contains the secret blinding factors, so no third party can check it. Publish a kernel excess per transfer — one group element plus a Schnorr proof of knowledge — and accumulate it; the pool balance then satisfies a public, stateless equation checkable at any height, revealing no position.

The one decision that cannot wait

The blinding factor must be derived, never stored. r = HKDF(account seed, domain, index) makes losing it impossible without losing the account key, and folds the backup story into the custody question §8 already has to answer. A wallet that generates r randomly and stores it beside the account creates a permanent unrecoverable loss case that no later upgrade repairs.

A consequence: deriving r recovers the factor but not the amount, and a holder needs both to build the next proof. So a stored position is a ciphertext under the holder's key, not a bare commitment. Field 10 on MsgSendPayment is correctly specified — a transfer amount is a commitment — but a position is a larger thing and belongs in its own message.

Recommendation

Wait. The irreversible part — the reserved field numbers — is done, and upstream may yet supply an audited module. If waiting is not possible, use gnark, and design around two properties of it: Groth16 proofs are re-randomisable, so a proof must never serve as a replay key or uniqueness index; and BN254 is roughly 100-bit security post-exTNFS, not 128.

Do not vendor and patch kryptology for a system a central bank is meant to trust.


The resolution: tier the ledger

The measurements above priced the cryptographic route and it is not worth the money. This is the design that replaces it.

Why "encrypt it lightly" is not an option

It is worth stating once, plainly, because it is the natural thing to reach for. The chain must verify that a sender has the balance and that the sums add up. If it can decrypt the amount to check, then every validator can decrypt it and nothing is hidden — least of all from the participants, who are the ones the confidentiality was for. If it cannot decrypt, it needs a proof that the hidden number is valid, and that proof is the expensive thing.

There is no middle position. Every design that looks like one turns out to hide amounts from outsiders reading a block explorer while leaving them legible to everyone running a node. That is not a limitation of Go's libraries; it is arithmetic, and it is the entire reason zero-knowledge proofs cost what they do.

No settlement system does this

Not SWIFT, not CHIPS, not TARGET2, not PAPSS. Every real-time gross settlement system in the world protects amounts with authorisation, contract and supervision, not cryptography. Confidential amounts was a requirement this project invented for itself; no central banker will ask for it.

What replaces it

Scope §6 already calls for a tiered architecture — participants run retail ledgers, the chain settles net positions and high-value items. Taking that seriously dissolves most of the problem:

And it makes the expensive option affordable later

Net settlement is low-volume by construction — tens to hundreds of transactions a day between institutions, not thousands a second. At that volume even gnark's measured 130/s is enormous headroom. So this defers confidential amounts to a layer where they would cost nothing, rather than abandoning them. If a customer ever asks, the reserved fields and this document are what they need.

What this obliges

Tiering is not free — it moves the work rather than removing it. A netting layer has to answer what no ledger of individual payments has to: what happens when a participant cannot cover its net position at settlement. That is unwinding risk, it is the reason modern systems moved away from pure deferred net settlement, and it must be designed rather than discovered. Multilateral netting must also settle atomically — a cycle where A owes B, B owes C and C owes A either clears entirely or not at all.

Both obligations, discharged

x/netting answers them, and the answers are worth stating here because this document's recommendation depends on them rather than the other way round. See the settlement guide for the operational version.

A participant cannot reach a net position it cannot cover. Everything it may owe across unsettled windows is bounded by reserve it has already moved into the module account, and the bound is checked when the obligation is submitted — in a transaction, where the answer "no" is a rejected message rather than a cycle nobody can settle. Settlement then rearranges claims on money the module already holds, so there is no transfer for anybody to refuse. Nothing in the module can reduce, reassign or recompute an obligation; a slice that cannot settle is held whole and retried unchanged.

A cycle clears entirely or not at all. The whole currency slice runs against a cached branch of the store that is committed only if every leg succeeded, because an end blocker has no transaction to roll back and a write made before an error stays made.

And the compression is real. Measured on a four-participant devnet, 16 obligations totalling 39,300,000 base units settled against 3,400,000 actually funded — 91.3% of the value never had to move. In tests, a day of 349 obligations between eight institutions compresses 86.2%, against 54.2% for bilateral netting on the same traffic. Net settlement is low-volume by construction, which is the premise this document defers confidential amounts on; these are the figures behind it.