Ethereum’s programmability makes it possible to issue stablecoins with built-in privacy backed by USDC/USDT. A privacy stablecoin protocol on Ethereum can offer:
Trustless issuance: minting and redemption are enforced by smart contracts rather than by an intermediary.
Permissionless issuance: anyone can mint and redeem under transparent, on-chain rules, with no gatekeeper.
DeFi composability: the asset plugs directly into the existing ecosystem of lending, trading, and other on-chain protocols.
Private or anonymous transactions: transaction data stays confidential or unlinkable to identities.
Native private DeFi applications: developers can build applications on top of the asset with privacy designed in from the start, rather than bolted on afterward.
Taken together, these properties give privacy stablecoins the potential to become, like stablecoins today, a core building block of the on-chain financial system.
They also open up a new design space for on-chain finance:
Confidential trading strategies: participants can execute strategies without exposing them to copycats, front-runners, or adversarial observers.
Selective disclosure: users can prove specific facts about their transactions or holdings (for example, to a counterparty, auditor, or regulator) without revealing everything.
A new model of social trading: building on selective disclosure, traders can verifiably share their performance or positions with chosen audiences, enabling copy trading and reputation systems without full public exposure.
Private prediction markets: participants can express views and take positions without revealing their identities or intentions, among other emerging use cases.
A few claims seem stronger than what it specifies:
Permissionless: every buy spends operator-signed inventory (§4.4), so the operator can refuse entry. Only deploying and redeeming are permissionless.
Unlinkable: the ramp builds the note, the operator holds viewing keys by design, and together they can tie KYC to pa (§5). That’s privacy from the chain, not from the operator.
Exits: a non-rotatable compliance admin can halt all spends, redemptions included, with a malformed root (§6). And a Circle freeze of the Vault is outside the model.
One exit dependency that isn’t in A1 to A7. Every spend, redeem included, has to prove non-membership against the current frozen root, and building that proof needs the frozen set itself, not just its root. §3.1 has the admin update only the root, and the pERC20 reference this builds on does the same.
/// @notice Update the compliance frozen root after rebuilding the off-chain blacklist SMT.
function setFrozenRoot(uint256 newRoot) external;
So a holder’s ability to redeem also depends on the admin publishing the leaves behind each root it sets. If it doesn’t, nobody can build a witness and every spend stops, and on chain that update looks exactly like an honest one. Dmitry’s malformed root is the visible version of this. A well-formed root whose leaves were never published is the quiet one, and it can happen by accident too, if the admin loses its tree. The grace window from the pERC20 audit (L-04) doesn’t help, since it only lets proofs against the previous root settle.
That sits awkwardly with G6 and Prop. 9 as written. Redemption needs no operator signature, but it does need the admin’s data.
The cheapest fix I can see is to put the leaves on chain with each update, for example freeze(uint256[] cmx) and unfreeze(uint256[] cmx) emitting the commitments, so any holder can rebuild the tree from logs. If the contract can also afford to recompute the root from those inserts, the “contract does not verify root correctness” line in §6 goes away as well, and the only way left to halt exits is one everyone can see. §7 lists freeze governance as open, and this part of it doesn’t need any governance to fix.
In §4.3 CT checks the target note’s amount and whether it belongs to inventory only when isT=1. I couldn’t find a condition requiring any output to match cmxT. If every output commitment differs from cmxT, isT=0 throughout and both product constraints are satisfied.
Could the operator therefore take a valid BuyAuth, spend inventory of value a and create a user note of value a at an address it controls, omitting the authorized cmxout? The reserve deposit and inventory accounting would still balance, while the intended recipient receives nothing.
Does the Vault require BuyAuth.cmxout to appear among the bundle’s output commitments or does the circuit enforce a match, for example with sum (isT)=1? If that check exists elsewhere, could it be made explicit in §4.3 and the argument for Proposition 7? As written, the equations seem insufficient to guarantee that the receiving note cannot be replaced.
Glad it holds up. Small depth should be fine. The circuit audit in the pERC20 repo describes the frozen set as an indexed Merkle tree with (val, next) leaves and FROZEN_IMT_DEPTH=20, so depth only caps how many entries fit. The one thing to size for is never filling it, since a full tree means the admin can’t freeze anything.
Insert on chain stays cheap to check if the admin passes the witness. The contract verifies the low leaf against the current root, checks low.val < cmx and cmx < low.next (or low.next == 0), rewrites the low leaf’s next, and appends the new leaf. That is two path updates per freeze.
Unfreeze is where I’d be careful. Relinking the predecessor to the removed leaf’s next works, but the freed slot has to go back to the tree’s empty value and not to a hashed (0, 0) leaf. Under the next == 0 rule a (0, 0) leaf brackets every commitment, so it would let any frozen note prove non-membership.
A plain append-only tree like the IncrementalMerkleTree you use for notes can only prove that something is in it, and it can’t take an entry out, so it doesn’t fit a frozen set on its own. A normal fixed-depth sparse tree does work, as long as you key it by the note’s position in the note tree and not by cm_old.x. Keyed by a slice of cm_old.x you get the suffix collisions from round-0 E in your circuit audit, and keyed by the whole value you need around 254 levels.
Keyed by position it’s depth 32, same as the note tree, and C-2 already has the position in its path_bits.
frozen[pos] = 1 if frozen, 0 (empty) otherwise
spend: prove frozen[path_bits of C-2] == 0 under rt_frozen
freeze: frozen[pos] 0 -> 1
unfreeze: frozen[pos] 1 -> 0
That drops the low leaf, the bracketing and both LessThanField checks, and since unfreeze just writes the empty value back, the (0, 0) problem can’t come up. On chain each update is one path. The admin passes the 32 siblings, the contract checks the old leaf against the current root and recomputes it.
Two things it leans on. A frozen note must not be able to show up again at another position, which ρ_new := nf_old already gives you since the same commitment can’t be created twice. And freezing is now by position, so the admin passes pos along with cmx. NoteConfirmed already emits both, so anyone can check a freeze against it, or the contract can store cmx -> pos at insert if you’d rather it checks itself.
Reading §4.7 and §4.8 from the lending side (we run a stock lending market on Morpho):
One-shot trades compose cleanly, but a position that persists, a supply or a loan, needs an owner a protocol can see. Two questions:
Private supply. After redeemForDefi supplies USDC to Aave or a Morpho vault, who holds the receipt? If it lands at a public address, the holder’s lending position is public even though their pUSDC wasn’t. ERC-4626 shares are bearer tokens with no liquidation path, so shielding vault shares into the target pool, as a private yield-bearing note, looks like the cleanest way to keep supply private. Is that the intended target-pool path, and would the Adapter whitelist cover 4626 deposits?
Re-entry depends on the operator. Exit is permissionless, but coming back into pUSDC is a buy that needs an operator-signed buyCall. If the operator is down, anyone who stepped out into DeFi stays public. Documenting shield into sUSDC as the fallback, or a buy path that needs no signature when the payer is the holder’s own exit plan, would make the round trip as trustless as the exit.
Borrowing against a private note looks out of reach by design, since liquidation needs an account a liquidator can act on. Worth stating in §7 next to the freeze discussion.
I think it’s feasible for the lending part with private stablecoin but in this paprt it’s out of the range, so we didn’t talk too much about that.Btw, you are right, we should keep clear for that. Thanks