The first post in this series mapped the privacy design space for public chains: cryptographic primitives (ZK proofs, FHE, MPC, TEEs), deployment models (application-level privacy, privacy groups, privacy-preserving rollups, validiums), and the use cases each fits. It introduced shielded pools as a pragmatic solution at the application layer, and it concluded that privacy alone is not acceptable for institutions. A privacy protocol that obfuscates every transfer cannot tell an honest user from a sanctioned one, and full anonymity is not a viable operating model for regulated entities. Compliance safeguards, such as access control, disclosure, and asset-level governance, must be part of the protocol itself.
This second post shows what that looks like in practice. We present Stellar Private Payments (SPP), a shielded pool that Nethermind is building for the Stellar network. It combines privacy with compliance safeguards embedded at the protocol level. SPP enables private payments and asset transfers on a public ledger while giving pool administrators, auditors, and regulators the controls they require: KYC-gated access, the ability to freeze non-compliant users, selective disclosure of individual transactions, and (under design) global viewing keys for authorized oversight. SPP is live today: it is deployed on the Stellar testnet, fully open source, and featured on PSE's Private Transfers Dashboard.
This post builds directly on the concepts introduced in the first one and assumes the reader is familiar with shielded pools, UTXOs, notes, commitments, nullifiers, and viewing keys. Rather than surveying the field again, we make a single argument:
Confidentiality and compliance are not opposing requirements. A shielded pool can enforce both, on public rails, today.
We proceed in three steps. First, we show how compliance is embedded in SPP's protocol, organized around the two surfaces an administrator can control: who may move value, and who may see it. Second, we present the architecture that enforces these guarantees on Stellar. Third, we discuss the UX friction and how we engineered the protocol to make it practical in a payment application. Readers interested in the underlying implementation details (circuits, the Soroban smart contract execution model on Stellar, the browser stack) will find them preserved in the appendix.
We built this in collaboration with the Stellar Development Foundation, who commissioned the work and entrusted us with developing SPP.
In a shielded pool, the public ledger records only cryptographic commitments, nullifiers, and validity proofs. That is precisely what makes it private yet unacceptable to regulated entities. The lesson of Tornado Cash, discussed in the previous post, is that privacy infrastructure without credible compliance mechanisms becomes an enforcement target rather than an adoption path, as its sanctioning by the U.S. Treasury in 2022 demonstrated.
Compliance obligations map onto two distinct control surfaces:
SPP implements both surfaces natively in the protocol. Write controls are enforced by the adoption of Association Sets; read controls are provided by selective disclosure and global viewing keys. We examine each in turn, and close the section with a compliance matrix summarizing who can enforce what, and when.
An Association Set (AS) is a mechanism for proving, in zero knowledge, that funds in a shielded pool come from legitimate sources. Concretely, an AS is a curated set of deposits, maintained by an Association Set Provider (ASP) according to a stated policy. When transacting, a user proves that their funds are associated with the set, without revealing which specific deposit is theirs. The policy comes in different flavors. It can screen deposits "after the fact", attesting that they do not originate from sanctioned or flagged wallets, or it can authorize funds "up front", admitting only deposits made from wallets that passed a pre-deposit screening or a custom policy check.
Whatever the policy, AS enforcement takes one of two complementary forms:
In both cases, the proof reveals nothing beyond the fact that the policy holds. The model is flexible, as a pool administrator can attach as many sets as needed, each managed by a different ASP, or even combine them.
The concept of AS was first formalized in the Privacy Pools paper by Buterin et al., and production systems have already implemented it, notably Railgun's Private Proofs of Innocence and 0xbow's ASP for Privacy Pools. In all of these designs, the association set is defined over deposits or individual notes; a user proves that their funds are legitimate. Policy attaches to the money.
In SPP, we take a different approach for compliance. Association sets are defined over public keys of users. A public key in the context of SPP is the cryptographic identity in the shielded pool and is deterministically derived from a Stellar wallet.
The AS enforcement remains the same: ZK membership and exclusion proofs; the attested object is the user's key, so policy attaches to the identity, not to individual deposits.
The distinction is worth formalizing, because it determines which compliance capabilities the pool can offer:
Two capabilities follow directly from placing the association set at the key level, and neither is achievable with note-level sets alone.
These capabilities carry the trade-off every compliance mechanism does. First, the ASP learns who is operating inside the pool. Second, a KYC-gated pool bounds the anonymity set to the eligible population, so if that population is small or publicly known, linking activity becomes easier despite the cryptography. For institutional deployments, a bounded, verified population is generally the intended operating point, but the degree of anonymity is a consequence of how the pool is gated, and should be treated as an explicit design parameter of each deployment, not an afterthought.
In-pool read controls address a requirement regulated entities face: proving their books when they are legitimately asked to. Audits, investigations, and reporting obligations all require visibility after the fact. SPP addresses this with two read-side mechanisms at different scopes: selective disclosure, available today, and global viewing keys, under design.
Selective disclosure is user-initiated and note-scoped. The owner of a note can disclose private information about it to a specific party: the amount, the commitment, or the spent status.
Disclosures use ZK proofs to bind the revealed information to a context, so a disclosure produced for one verifier cannot be replayed elsewhere, and the user decides exactly how much to share. Nothing else about the user's activity is revealed, and the pool's confidentiality toward everyone else is unaffected.
Under the hood, selective disclosure is a ZK statement of ownership: the user proves control of a note against a specific state root while revealing only the selected fields required for the check. Informally, they are proving the following statement:
"I own a note with commitment C and amount a in a shielded pool X. If spent, it would produce this nullifier n".
More precisely, the user:
C and which is part of the pool's note commitment tree;n is the valid nullifier for C.
The verifier learns that the user owns note C with amount a in the given pool. By checking n against the pool contract, they can also learn whether the note has been spent. They can inspect the transaction in which C was created, which may reveal the depositor's address if the note came from a fresh deposit, but reveals nothing if it was created by an in-pool transfer. The verifier cannot spend the note.
The SPP interface includes dedicated flows to generate and verify disclosures over multiple notes, for example, for tax reporting or proof-of-funds. The procedure is handled entirely offchain between the user and the requesting authority.
Global viewing keys sit on the administrator and regulator side, providing pool-scoped, view-only access to the notes in the pool. Selective disclosure is user-initiated and requires cooperation from the note owner. Some regulated deployments need to go beyond this, with standing visibility for a supervisor or auditor, independent of user action, for duties like AML monitoring or supervisory reporting. This is the gap global viewing keys are designed to fill. They enable an auditor to inspect notes in the pool without granting any spending power.
This capability is currently under design in SPP. We are evaluating two flavors, so the visibility scope can match the legal requirements of each deployment's jurisdiction:
The table below summarizes SPP's compliance safeguards: which surface each one controls, who can invoke it, when it acts, and what it enforces.
*currently under design in SPP
These safeguards are designed to be modular. In future versions of SPP, pool administrators will be able to configure which sets, disclosure mechanisms, and viewing permissions apply, based on their requirements, desired privacy levels, and the legal obligations of their jurisdiction.
SPP combines a shielded pool with ASP-based policy controls and in-pool private transfers. The protocol is enforced by a ZK circuit, settled by a set of smart contracts, and driven by a client that does all proving locally. This section presents the components; the appendix covers their internals.
Stellar, like Ethereum, is an account-based network, where the ledger maps addresses to balances. SPP instead holds value as discrete private notes within the pool, emulating the UTXO model introduced in the first post. Converting balances into notes on deposit is what makes private payments possible.

Smart contract functionality on Stellar is provided by Soroban, a Rust-based platform whose contracts compile to WASM. Two properties of Soroban shaped SPP's design: execution is deterministically metered (every instruction counts against a strict resource limit, so onchain proof verification must be heavily optimized), and persistent storage pays state rent (long-lived data such as Merkle roots and nullifier sets must be managed economically). Both pushed us toward a modular contract architecture with a lean, specialized verifier that we describe in the next section. For a detailed Soroban execution model and state management, we refer the reader to the official Stellar documentation.

The on-chain protocol splits the workload across specialized contracts:

By separating the cryptographic verification, the compliance lists, and the core UTXO state, the architecture remains modular. An administrator can attach different ASPs, or upgrade policy contracts, without touching the pool's privacy core.
In a shielded pool, the smart contract cannot see how much is transferred or to whom, so it cannot validate state changes on its own. All validation is offloaded to a ZK circuit. The circuit is the pool's law; a user generates a proof locally showing their transaction satisfies all constraints, and the Soroban contract rejects the transaction if proof verification fails.
Every SPP transaction — deposit, transfer, or withdrawal — carries a single proof enforcing the following guarantees:
Compliance is verified at every state transition. Because deposits, transfers, and withdrawals are the same statement instantiated with different public amounts, the policy constraint is part of the only proof the pool ever accepts. This is a meaningful difference from note-level designs, where association is typically proven when funds exit the pool; in SPP, a user who falls out of policy cannot deposit, cannot transfer, and cannot withdraw. A freeze binds from the very next transaction the user attempts, whatever its type.
The sequence below shows the lifecycle every SPP transaction follows.

Note what the ledger records at the end of this flow is a valid proof, fresh nullifiers, new commitments, and a public amount. It never records who transacted, with whom, or which identity the policy was checked against. The compliance guarantees hold without the transaction revealing anything more than a vanilla shielded pool would.
The full circuit is open source, and the appendix walks through each constraint, including the note commitment structure, state inclusion, nullifier derivation, malleability protection, value conservation, and policy enforcement.
The first post argued that the real barrier to shielded pools is not the cryptography itself, but the UX. Key management, note discovery, Merkle tree maintenance, and proof generation are all operational tasks clients must handle when using privacy solutions. Nethermind is currently building the components for SPP to address this barrier in practice.
A shielded pool moves most of the protocol's workflow onto the client. It surfaces three operational problems that any practical deployment must solve.
The first two problems are solved inside the browser; the third requires a piece of infrastructure. We take them in turn.
We made SPP UX accessible via a simple web page. Users just need to open the web application, connect a wallet, and transact privately. There is nothing to install, no node to operate, and no proving service to trust. The proof generation and state management happen locally in the browser, so private keys and notes never leave the device. Behind that simplicity, the client delivers three results:

The technical details of how we implemented the core library, the background execution model, and the local database are available in the open-source repository.
To solve the history-availability problem, we implemented a bootnode. The bootnode is a lightweight indexing service that archives SPP events from the contract's deployment ledger onward, and re-exposes them through a narrow RPC interface, without the retention limit. A client joining the pool (or recovering from lost local storage) replays history from the bootnode; once it reaches the window the public RPC still covers, the bootnode hands it off to the regular RPC for the most recent ledgers. From the client's perspective, the retention window simply disappears. Please note that the bootnode can also be run self-hosted to minimize trust assumptions.
Two remarkable properties of the bootnode:
The first post in this series established that privacy is a core requirement for bringing financial services onto public rails, and that privacy without compliance is not an institutional solution. This post presented the system where we put that thesis into practice.
Stellar Private Payments demonstrates that a shielded pool can be governed. By moving association sets from the note level to the identity level, SPP supports compliance at the write controls, KYC-gated access and user freeze. Selective disclosure, and global viewing keys under design, complete the picture on the compliance reads, giving users, auditors, and regulators calibrated visibility instead of the all-or-nothing choice between transparency and anonymity. Finally, the client and infrastructure engineering show that the operational barrier identified in the first post can be engineered away via an improved UX.
SPP is live on the Stellar testnet, fully open source, and featured on PSE's Private Transfers Dashboard. Nethermind is working with Stellar to actively maintain and improve the protocol. Next steps on the protocol roadmap include a larger decentralized trusted-setup ceremony ahead of a mainnet deployment, the public bootnode rollout, and the full UI integration of selective disclosure, and the design of global viewing keys.
The broader point stands beyond this deployment: confidentiality and compliance are jointly practical on public ledgers. Stellar is the first example from our privacy work; more will follow. The next post in the series stays at the application layer and presents a shielded pool design that remains backward compatible with existing regulated-token standards such as CMTA and ERC-3643, bringing the same guarantees to assets institutions already issue.