Tokenized funds, bonds and equities on public blockchains are visible to everyone: every holder, position and transfer. Shielded pools can hide that, but a security token placed in a standard shielded pool loses the compliance controls regulators require.
This post presents a compliant shielded pool for ERC-3643 tokenized securities. Only verified holders can transact, frozen holders cannot move value, an authority canclaw back a position without the holder’s key, and a supervisor can read the full history. We built it using Paladin's Zeto, an open source framework for programmable private tokens on the EVM.
This is the third post in our privacy series, after shielded pools on public chains and Stellar Private Payments.
A money-market fund, a bond, or an equity carries obligations that the token has to reproduce. In practice, that means extending the token with identity, eligibility, and transfer rules. These rules exist to meet the regulatory obligations attached to financial assets. A holder can be sanctioned, or fail a periodic review. A court can order a position moved. An investor can lose the key to their wallet and still be entitled to their holding. A supervisor can demand to see who held what, and when. An instrument whose issuer cannot respond to all four is not one a regulated market will accept.
Several standards exist for that structure. On Ethereum, ERC-3643 extends ERC-20 tokens with additional identity and compliance rules, and CMTAT defines an equivalent set of controls as a blockchain-agnostic framework.
ERC-3643 and CMTAT tokens are entirely public. Every holding and every transfer is visible to anyone, which is a poor fit for instruments whose holder base, position sizes, and rebalancing are sensitive information. One could be tempted to adopt shielded pools to provide privacy to a tokenized security. However, a general-purpose shielded pool cannot enforce the compliance controls these assets depend on.
We built on top of Paladin, a framework for programmable privacy on EVM chains, by extending Zeto, its family of shielded tokens. Zeto already provides KYC checks and auditor access. Regulated assets need more: tokens can be clawed back after a court order, holders can be frozen if their funds are considered illicit, and the authority enforcing a clawback must be able to find what it is taking.
Inside a shielded pool, each holding is a note: a record whose owner and amount are hidden onchain. A general-purpose shielded pool exposes three operations: deposit, transfer, and withdraw. For an unregulated asset that is all you need.
For a tokenized security it is not sufficient. The moment the token lands in the pool, every compliance control stops working. A frozen account can still spend its notes from its in-pool address, because "frozen" was a public-ledger concept the pool never learned. A seizure order has nothing to act on, because the issuer cannot tell which notes belong to the sanctioned party. Anyone without a viewing key sees only cryptographic data that reveals nothing about who holds what.
For an unregulated token that is a feature. For a security it is a disqualifying defect, because the pool becomes a compliance escape hatch, and the cleanest way to launder a frozen position would be to deposit into it.
The gap is not privacy. Shielded pools solved that. The gap is that the controls a tokenized security needs are precisely the ones that depend on seeing what a pool is built to hide.
These are the properties a shielded pool has to provide before a tokenized security can live inside it. Some of the terms are used loosely elsewhere, so we fix their meaning here.
We extended a general-purpose shielded pool with the compliance controls a tokenized security requires. The result is a compliant shielded pool for tokenized securities.
Inside the pool a holding is a note, and a transfer reveals nothing onchain beyond the fact that it is valid. So we added those compliance controls to the conditions a transfer must meet to be valid.
A transfer is valid only if the sender and the receivers are identity-verified and active, which means a frozen holder cannot produce a proof at all. We also added what an authority needs to inspect the full transaction history and to claw funds back when required. Value still crosses in and out by deposit and withdrawal.
That took us to five additions to the pool.
We built the pool on top of Zeto, Paladin's general-purpose shielded token, rather than from scratch, and that choice mattered as much as the cryptography. A shielded pool needs a lot of machinery around its proofs: an indexer that tracks which holdings are still live, local state that each proof is built against, a reader for the encrypted notes stored onchain, and an interface an application can actually call. Paladin already ships all of it, along with key management and transaction sequencing (see Appendix B). Starting from scratch would have meant rebuilding that machinery, and every rebuild is a fresh source of subtle bugs. Because what we produced is still a Zeto token, it plugs into any Paladin deployment and inherits all of this.

Both Paladin and Zeto are open source under LF Decentralized Trust, and our version is published in the Nethermind Zeto repository. It can be deployed into an existing Paladin setup and to act as a privacy layer for any deployed ERC-3643 instrument.
Because Paladin is composable, this is not a silo. An issuer tokenizing financial assets can run the public instrument and its confidential pool side by side, and a bank operating that fund can use the same Paladin deployment for both, alongside other domains for the cash leg of a settlement.
We first compare this approach with the main alternative, then show how each ERC-3643 control carries over into the pool. Readers who want the mechanism will find it in the technical deep dive at the end.
Confidential tokens hide amounts but leave the sender and receiver visible. A compliant shielded pool also hides who transacts with whom, and that is what makes its compliance controls harder to build.
An alternative design keeps the account model and encrypts the numbers instead of hiding holdings in a pool. That approach is standardized as ERC-7984, the confidential fungible token standard, where balances are held encrypted and computed over using fully homomorphic encryption (FHE), without ever being decrypted.
T-REX Network adopted Zama's FHE protocol as the confidentiality layer of its ledger, combining ERC-3643 compliance with the ERC-7984 confidential token standard.
It is a good design and it solves a different problem, which the transfer function makes plain:
confidentialTransfer(address to, externalEuint64 encryptedAmount, bytes inputProof) → euint64
The amount is encrypted. The recipient is an ordinary address and the sender is msg.sender. Zama's page on confidential RWA tokenization states the consequence directly, that "sender and receiver addresses remain public; investor holdings, trade amounts, and fund composition stay encrypted". The numbers are confidential and the transaction graph is not.
In this model, compliance controls come naturally. Because the contract can always see which address is transacting, freezing an address and forcing a transfer out of it are ordinary operations on visible state. Intuitively, the difficulty of the controls is proportional to how much you hide, and hiding the transaction graph is what makes them hard.
The table below compares confidential tokens with our compliant shielded pool. As a comparison framework we recall the properties a privacy-preserving protocol should provide, which we set out in the first post of this series.
Caption: FHE confidential tokens hide amounts. A compliant shielded pool also hides who holds what and who transacts with whom.
An issuer choosing between them is really choosing how much they need to hide. If encrypted amounts on a public graph are sufficient, the FHE route is straightforward and wraps an existing position without migration. If the holder relationships themselves are the sensitive part, which for most funds they are, then the graph has to go, and the controls we built are what it takes to keep the instrument enforceable once it does.
Every ERC-3643 control keeps working inside the pool, except transfer rules that need transaction history (see Limitations).
ERC-3643 is a collection of smart contracts that implement the functionalities of a tokenized security. At its core there are three pillars: the token is an ERC-20 whose transfers are controlled; the identity registry holds the verified investors and answers whether a given address is eligible; the compliance contract evaluates the issuer's transfer rules through pluggable modules. Behind the registry sit the onchain identities and the claim registries that decide who counts as verified, which we leave to Appendix A.
Two roles operate the system. The owner is the issuer, holding governance authority. The agent is the operator who executes privileged actions such as minting, freezing, and forced transfers.
The table below lists the functions that govern an ERC-3643 token. Each exists to meet a regulatory obligation of tokenized securities. Making the instrument confidential means keeping every one of them working once the tokens are shielded. The right-hand column shows how our pool does it.
To summarize, keeping an ERC-3643 instrument compliant inside a shielded pool comes down to three things that must work together:
The technical deep dive shows how each one is built.
Inside the pool both sides of a transfer are hidden, and the proof checks both. Deposits and withdrawals are different, because one side of them is public. The pool can check only the half that sits inside it, and the public token covers the other half. So the two crossings are not checked the same way, and the controls on either side have to be read together.
The tokens leaving the depositor are still an ordinary ERC-3643 holding, so the transfer into the pool passes isVerified and canTransfer, and a frozen address cannot make it at all.
On the pool side, every new note names a holder, and the proof checks that this holder is verified and active, so a frozen holder cannot deposit into a position they still control. New notes are also placed under the clawback authority from the moment they cross.
What neither half does is identify the depositor inside the proof. A deposit creates notes without consuming any, so there is no sender identity in the statement to check. That is covered on the public side, where the depositor is a visible address subject to the token's own freeze.
Inside the pool, the sender and any change recipient must be verified and active, and the withdrawal consumes the note just as a transfer does. A frozen holder cannot withdraw their way out, and a note that has been clawed back can no longer be spent, so it cannot be withdrawn either.
The value lands as an ordinary ERC-3643 holding, so the receiving address has to pass the same isVerified and canTransfer checks as any other transfer of the instrument.
Taken together the two crossings leave no escape hatch open. A frozen holder cannot carry value in, cannot carry it out, and cannot move it while it is inside.
Some limits are deliberate and some are simply where we drew the line for a first release.
canTransfer method. Checking the compliance status of a holder is a binary check on the status tree, which the pool handles. However, financial assets may be subject to a cap on the number of holders, a rolling volume limit, or a rule comparing a transfer against recent ones, and evaluating any of those means reading the graph of previous transactions, which is exactly what the pool hides by design. Some of this logic could move inside the proof, and some is genuinely in tension with the privacy model.
In this post we tackled the problem that neither privacy nor compliance solves on its own. Moving financial assets on chain as tokenized securities carries obligations that assume the ledger can see who is transacting and what compliance status they hold. Privacy is a crucial aspect of these assets, because it is not acceptable to leak the positions of investors in a money-market fund or the trading strategies of its participants.
We discussed how to adopt shielded pools as a privacy layer for financial assets. The question left open was what happens when a tokenized security is deposited into a pool. Every compliance control is lost.
Our answer to this problem is a compliant shielded pool that enables enforcement of compliance rules under its privacy model. We built it as a Zeto token inside Paladin, so an institution can plug it into an existing deployment rather than assemble the tooling first.
Onto that we added what ERC-3643 asks for, taking the standard as our case study. Identity gating and selective disclosure came from Zeto, and we brought them together with a compliance status tree and a clawback path so that one token gates identity, discloses to a supervisor, enforces freezes and supports clawback. The controls a regulated instrument needs survive the trip into the pool.
Finally, we compared our approach with the FHE-based alternative, confidential tokens. Ours is the design to reach for when the holder relationships and the transaction graph are what must stay private.
Building the pool as a Zeto token rather than as a shielded pool of our own was a design choice with consequences beyond the ones already described. It makes the token native to Paladin, so it inherits the platform's programmability rather than sitting beside it. Four things could follow.
Privacy and compliance enforceability were supposed to be in tension, and we have shown how to relax that tension with compliance built inside a shielded pool.
We thank Goutham Denthaje for the implementation of the compliant shielded pool described in this post and our colleagues at Nethermind Research for the fruitful discussions and for reviewing this work.
This section is for readers who want the mechanism. It starts from what Zeto already provides, then builds the three missing controls one at a time.
Zeto is Paladin's family of zero-knowledge UTXO tokens. Its central idea is that a confidential token is a composition of reusable layers, each adding one capability, so you build the token you need by choosing how far up the stack to go.
Zeto ships identity gating and non-repudiation as two independent building blocks. In our work, we inherited those layers to build a version of Zeto that combined them together along with the enforceable freezing and clawback functions.
H is a collision-resistant hash function. A participant is identified by a keypair (pk, sk), and subscripts name whose key it is, pk_own for the holder and pk_cb for the clawback authority. A note is committed as cm. Spending a note publishes a nullifier nf, which the pool records so that the same note cannot be spent twice. Each tree stores leaf values and publishes a root. ECDH is a Diffie-Hellman shared secret, computable by either of the two parties.
In our second post we introduced access controls for shielded pools as user-level association sets. A curated set of public keys lets the pool prove that a counterparty is approved, with policy attached to the identity rather than to individual notes. Here the issuer curates the sets and ERC-3643 supplies the policy.
The mechanism is the same in both. The issuer keeps a tree of public keys and publishes only its root onchain inside the pool. Every transaction then proves, with the zero-knowledge proof, that the keys it involves sit in that tree, without revealing which leaves they are. Nothing about the holders reaches the chain, and the check cannot be skipped, because a proof that omits it is not a valid proof.
The Zeto implementation follows the same approach. Identity gating implements KYC controls as a Merkle tree holding the public keys entitled to hold the asset, and then each transfer proves in zero knowledge that the sender and every recipient are members of that tree. For each transfer, the proof checks:
identity check
sender's pk derived in-circuit from the sender's secret key
recipient's pk taken from the commitment of the note being created
identitiesRoot the published root of the issuer's identity tree
then, for the sender and every recipient:
leaf = H(pk)
verify inclusion of leaf in identitiesRoot
On the public side that check is isVerified, answered by the registry. In the pool it is the same decision, proved in zero knowledge rather than queried.
We implemented freezing holders under the same mechanism. We built a second tree, the compliance status tree, alongside the identity tree. The status tree records each participant as active or frozen. Every transfer and withdrawal proves that the sender and the recipients are active. Deposits prove it of the recipients, because value arriving from the public side is still under that token's own controls until it crosses.
We use a sparse Merkle tree, where each leaf is addressed by a key and carries a value. The key is the holder's public key, and the status is bound into the leaf value.
status check
pk as above, for the sender and every recipient
complianceRoot the published root of the issuer's status tree
STATUS the status being proved, active or frozen
then, for the sender and every recipient:
leafKey = H(pk)
leafValue = H(pk, STATUS)
verify inclusion of (leafKey, leafValue) in complianceRoot
On the public side the equivalent is setAddressFrozen, a flag the token checks on every transfer. In the pool there is no flag and no branch to check, because a frozen holder cannot produce a valid proof at all. A non-compliant transfer has nothing to submit, rather than something an implementation might forget to reject.
Tokenized securities need a way to move an asset out of an account that will not cooperate, whether to enforce a court order, a sanctions designation, or a lost-key recovery.
In a normal shielded pool this is impossible by construction. The only party who can spend a note is whoever holds the owner's private key, because that key is baked into the nullifier the pool uses to prevent double-spends. That is precisely what makes the note theirs, and it is also what makes seizure unachievable.
Here we introduce a second way to spend a note. A designated authority can exercise a clawback from a frozen holder, without weakening the holder's normal control and without revealing onchain which note is being taken.
This starts with the note itself. Alongside the holder's own key, every note now commits the public key of the authority entitled to claw it back.
note = (
pk_own, // the holder's public key
value, // the amount
salt, // commitment randomness
pk_cb // the clawback authority's public key
)
cm = H(note)
The authority's key is registered in the pool contract, and every note created has to commit a key the contract recognizes. A holder cannot mint notes naming an authority of their own and walk out from under the control, and because pk_cb sits inside the commitment, no later change to contract state can alter which authority a given note answers to.
To spend a note we leverage Diffie-Hellman symmetry. Alongside its normal nullifier, every note carries a second one, a clawback nullifier, derived from a shared secret between the holder and the clawback authority the note names. Because such a secret can be computed from either side of the pair, the holder and the authority independently arrive at the same nullifier for the same note, from opposite directions, and neither needs the other's secret.
k = H(ECDH(sk_own, pk_cb)) // the holder's derivation
= H(ECDH(sk_cb, pk_own)) // the authority's derivation
nf_cb = H(cm, k)
A note counts as spent once either of its nullifiers has been revealed onchain. The table below shows which operation reveals which.
A holder must compute both nullifiers, since they know their own key and the authority's public key. The natural worry is that a holder who sees a freeze coming could reveal the clawback nullifier alone, keeping the note alive for themselves while making it unseizable.
They cannot. There is no operation that reveals the clawback nullifier alone from the holder's side. A normal transfer or withdrawal reveals both nullifiers together, which consumes the note entirely and leaves nothing to seize. Only the clawback path reveals the clawback nullifier alone, but producing that proof requires the authority's private key. The holder's only move is to spend the note properly, and then there is nothing left to take.
The clawback flow is shown in the diagram below.

After a clawback, the holder's later spend attempt fails, because the normal spend path checks that the clawback nullifier has not been revealed before, and finds that it has. Their own derivation of the nullifier collides with the one the authority already revealed. This holds even if their standing is restored afterwards, so a clawback does not quietly unwind when a freeze is lifted.
Partial clawbacks are possible. The authority chooses the output notes, so it can take part of a holding and leave the rest with the holder, which matters for court orders that specify an amount rather than an account.
This mechanism naturally unlocks lost-key recovery as well, a clawback that moves the holding to the investor's new account rather than to a recipient chosen by the authority.
What an observer sees onchain is some clawback nullifiers, some new commitments, and ciphertext. The note being taken and the identity it belonged to never appear. The clawback is enforceable, and the affected party is not exposed by the act of enforcing.
On the public side the equivalent is forcedTransfer, a call an agent makes against a named address, in full view of anyone watching. Here the authority proves it may take the note, and neither the note nor the holder appears onchain.
Zeto already provides selective disclosure. Every transfer carries an encrypted copy of its full details under a designated auditor's viewing key, so a supervisor can reconstruct who sent what to whom without the holder's cooperation and without any ability to move value.
The clawback authority needs the same view. To build a clawback proof it has to know which notes belong to the frozen holder, and that means reading the same trail the auditor reads. So every transfer carries a second encrypted copy, under the clawback authority's key.
We keep the two keys separate for simplicity, and because the two roles are genuinely different, one read-only and one able to consume a note. Other arrangements are possible. A single key shared through multi-party computation, for instance, would give the same visibility while requiring several parties to agree before anything is decrypted.
ERC-3643 is an Ethereum standard for permissioned tokens. It is not a single contract but a system of them, and understanding the pieces is what makes the privacy problem concrete.
Paladin is an open source, LF Decentralized Trust project providing programmable privacy for EVM chains. It runs as a sidecar process alongside an Ethereum execution client and works with any unmodified EVM chain, which remains the base layer.
In the vocabulary of our first post, Paladin is the canonical privacy group framework. What we built sits inside one, and it is an instance of application-level privacy, the shielded pool model. Our first post presented those two models as alternatives. They are not. This post is what it looks like when they compose.
A domain is a privacy model that plugs into that runtime, and Paladin ships three. They differ in how a transaction is shown to be valid, which is what makes them suited to different jobs.
The three domains are composable. A single flow can hold a Zeto token, a Noto token and private EVM logic running in a Pente group, each keeping its own privacy model, and settle them together atomically.