EIP-8182: Private ETH and ERC-20 Transfers

What this EIP proposes

A canonical private transfer system for Ethereum: one shared pool for ETH and ERC-20 tokens, implemented as a system contract and verifier precompile, with public deposits and withdrawals. The system contract holds one note tree, nullifier set, user registry, auth policy registry, and delivery-key registry. It has no admin key and no on-chain upgrade path.

A recursive proof architecture keeps protocol invariants in a hard-fork-managed outer circuit while allowing permissionless inner auth circuits for authorization. New auth methods can be added without creating a new pool: users register an auth policy for an inner circuit, and the outer circuit continues to enforce the shared protocol rules.

Motivation

Sending assets publicly on Ethereum has a shared, widely interoperable default. Users send ETH and tokens to ordinary Ethereum addresses, often reached through ENS, and wallets, recipients, and applications rely on the same shared standards. Private transfers have no analogous shared default today, even though many ordinary financial activities require privacy.

Why has the market not produced a widely adopted default privacy application on Ethereum? Because a private transfer application cannot compete on product quality alone. Its effectiveness also depends on how many users and how much value shares the same pool. A small pool offers weak privacy even for a superior product, while a large pool can remain attractive even when competing products are better. That means app-layer teams cannot focus only on wallet UX, authentication, compliance, or proof systems. They must also persuade users to deposit into their pool, which is difficult when the pool is not already large.

But growing the pool is only part of the problem. App-layer teams also have to decide how the pool changes over time. If the pool is upgradeable, the parties with the power to change it could compromise user funds. Immutable pools avoid that risk, but they cannot adapt as proof systems weaken or cryptographic assumptions change. Neither is a good foundation for common privacy infrastructure.

Ethereum should break this impasse by providing a shared privacy layer.

Key design choices I’d like feedback on

  • Recursive outer/inner split: The outer circuit is fork-managed and enforces all protocol invariants. Inner circuits are permissionless and handle authentication plus intent parsing.

  • Permissionless auth methods: The current draft is neutral about the signing format. Companion auth circuits can implement ECDSA, passkeys, multisig, or other schemes so long as they produce the required inner outputs and bind the canonical intent digest. Is that the right level of protocol neutrality, or should the base layer be more opinionated about auth?

  • Delivery-key registry and baseline delivery scheme: The current draft includes an on-chain delivery-key registry and a baseline note-delivery scheme so private transfers can work with ordinary Ethereum addresses without a separate address system. Is that the right scope for the base layer, or should the protocol do less here?

  • Optional origin tracking: Notes may carry an originTag that traces back to one originating deposit. Deposits are untagged by default; if a depositor opts in, compatible transfers preserve the tag, while incompatible combinations clear it to 0. The circuit enforces the tag’s creation and propagation, but applications and counterparties decide what proofs or policies to build on top of it.

  • Private fee compensation: Output slot 2 serves as an optional private fee note for compensating relayers, provers, or other service providers. The protocol itself charges no fee. Is a single fee output sufficient, or are there service-provider configurations that need more?

  • State growth: Each pool transaction adds permanent state (nullifiers and new commitment leaves). Is that acceptable for an initial version, or should an accumulator-based nullifier design be a prerequisite for enshrinement?

Links

4 Likes

Quite amusing that I missed you X post entirely but found it here. (To be fair I was sick last week)

Regarding intent format, have you considered EIP-712? It uses fixed-size fields which may be easier on circuits than RLP, and doesn’t require inventing fake chains. (Fun fact: I once had a conversation with Yak, who’s design for Ethereum wallets on Chia was also synthetic transactions and convinced him to go for EIP-712 as well) You still need to keccak twice in-circuit though.

About precompiles, I would say that precompiles should be as small and generic as possible given how hard they are to implement and maintain. It would be nice to have a new general purpose ZK precompile if the current ones are not enough. You already know how to write shielded pool protocols in solidity, the main novelty is to make them socially governed.

I don’t think state growth is a specific concern: since it happens via the regular EVM, existing and upcoming state growth control strategies like repricings, state gas etc. can still operate the same. It’s orthogonal.

1 Like

[Note: this post is based on an earlier version of the EIP. Changes have been made since then that obsolete some of the content.]

Thanks for the thoughtful feedback, @bbjubjub!

EIP-712 vs. intent transactions

The overall design philosophy of the EIP is that implementers should do extra work whenever it’s possible to make things easier for users and increase adoption potential.

With the intent transaction approach, the user hits the Send button in their wallet — the same Send button they use for everything else. The wallet shows “Send 1 ETH to 0xabc…” with token resolution, decimal formatting, dollar amounts, the whole purpose-built transaction confirmation UI. On day-one users get UX parity with their public transactions.

EIP-712 can express the same information, but wallets render it through a “Signature Request” modal — the raw data being signed, with no interpretation. Users would need to understand this new format on their own, and without wallet simulation showing how their balances will change, it’s easier to sign the wrong thing. For a system meant to bring new users to Ethereum, that’s a disaster.

Yes, it costs us extra circuit complexity (RLP parsing, keccak in-circuit), but that’s implementer pain, which scales better than user pain. The spec does anticipate adding an EIP-712 circuit later (same public-input interface, new circuitId) for wallets that build native privacy support and can meaningfully render the payload.

Precompiles

Fully agree — the proof verification precompile in this spec is intended to be generic. Any contract can call it with a circuitId and get back a verification result. The enshrined pool is just the first consumer.

An application-level pool would use it like this:

  1. Route proof verification through the precompile, passing in the user’s chosen circuitId.
  2. When a new circuit is available (e.g., secp256r1-based verification), the pool would not need to update. Instead, users would call the same pool function with the new circuitId as a parameter and a corresponding proof.

This precompile benefits the entire ecosystem, not just the enshrined pool.

Notably, I do not call for an EIP-5988-esque Poseidon precompile. Poseidon is actually not prohibitively expensive to compute in the EVM today, and as gas limits grow it may be cheaper to just increase chain throughput than to maintain a hashing precompile. So there’s exactly one new precompile in this spec.

On state growth: I completely agree!

And to your broader point — yes, the novelty here is making the pool socially governed, not inventing a new privacy model. This is deliberately de-risking: we’re enshrining something we already know works at the app layer (shielded pools, proof of innocence, UTXO notes), removing the governance-vs-ossification dilemma, and giving it a canonical anonymity set.

Thank you for your answers. Since we are aligned on precompiles and state growth, I will push back on synthetic transactions a little bit more.

UX-wise, it depends what wallet you are using, but even a pretty generic EIP-712 decoder can display you something akin to this:

Field Value
TYPE FacetPrivateTransfer(…)
CHAINID 11155111
ASSET 0x…
AMOUNT 1e18
BENEFICIARY kzg.eth

and wallets recognize and display addresses. OTOH with intent transactions, while it is a cool hack and a great demo, you see a cryptic chain id along with a transaction fee that you actually have to ignore. Even if we assume that we don’t modify wallets, it’s not so clearly preferable.

Furthermore, if we are going to enshrine something protocol-level, we can certainly expect wallets to have first-class support for it eventually. Wallet UX is malleable, while system contract are much harder to modify, so any UX benefit is temporary while the note format we chose will be here much longer.

On a different note, if there is a soundness bug and we have to fix the contract via hard fork, the problem will be that divine intervention takes time and publicity — exactly what you don’t want in this situation. At this rate the pool will be drained well before we can fork and it ends up kind of pointless. The only way I can think of to fix this is to introduce a semi-trusted council with the ability to pause zk proofs for at most 3 months while we wait for the fork. Of course they would have no other power and cannot freeze or steal funds. How this plays with core devs is another question entirely.

Given that there is a pause button, we can also introduce public exits as a last resort way to get funds out. The way it works is you submit all the merkle proof data publicly onchain and it proceeds the same as a withdrawal except without privacy. This is similar to ragequits in Privacy pools. Since there is no zk this is not affected by the pause.

If something is to be backstopped by social consensus, we need to make sure people are comfortable with it, and so it’s important to make it as simple as possible. For this, external ASP support is a great feature. If withdrawals have to be implemented once in-circuit and once in-EVM though, they especially need to be simple, and EIP-712 is better in that regard.

Taking a step back, it’s also worth noting that both systems assume addresses are EOAs, otherwise ECDSA signatures don’t work.

1 Like

In general I like the idea of enshrined note and nullifier trees that privacy protocols can build on to increase effective anonymity set sizes.

Some questions I have with this proposal:

  1. I don’t have a solution to this problem, but adding new authorisation circuits via hardforks feels risky. Every new authorisation circuit here adds a lot of risk since it requires hardforks, new consensus-critical code, and extensive auditing. Maybe private transfers should be designed to be post quantum secure from the beginning? Or should we instead pick a single, well-defined authorisation primitive and commit to it, with a single upgrade path to PQ, rather than designing for flexibility that requires an unspecified number of upgrades?

  2. Could you elaborate on how this proposal fits into Ethereum’s PQ roadmap? The EIP mentions post‑quantum signatures, but would be interested to know about how the full upgrade path would work.

  3. I don’t think state growth is a blocker, implementing data structures such as note and nullifier trees will always result in more state growth, and this might be the right model for L1 privacy. That said, state size is a bottleneck that should be managed even when accounting for the expected increase in state growth rate with scaling the L1, and we should look to minimise it where possible. It might be good to eventually have numbers on what the impact on state growth would be. And agree it would be useful to come to a decision on whether to use an accumulator-based nullifier design.

2 Likes

[Note: this post is based on an earlier version of the EIP. Changes have been made since then that obsolete some of the content.]

Thanks, @johnG and @bbjubjub!

On auth circuit risk and hard forks:

A well-defined, general authorization primitive is a good goal. Here’s how one could work within this framework. Split the auth circuit into an:

  • Outer circuit that enforces protocol-level invariants (value conservation, nullifier derivation, Merkle membership, encryption)

  • Inner circuit that handles only auth and intent parsing.

The outer circuit recursively verifies the inner circuit. A broken inner circuit can only prove false authorization claims — it can’t weaken value conservation or encryption because those are enforced by the outer circuit.

This setup would make it safe to allow permissionless inner circuits, and a new auth method would never require a hard fork.

The question is whether to build this recursive architecture for v0 or hard fork into it later. I think later:

  • Recursive proof composition in privacy systems is new. The monolithic approach uses the same proven cryptography as existing deployed systems. Less novelty means less risk for the version that needs to earn trust.

  • Hard forking into the recursive architecture has no major disadvantage — notes created by the v0 circuit are fully compatible with any future circuit.

On PQ roadmap fit:

The circuitId architecture lets a hard fork change the encryption scheme without changing the auth method. This matters because PQ encryption is more urgent than PQ auth — “harvest-now-decrypt-later” means pre-quantum ciphertexts are retroactively vulnerable even while ECDSA remains secure against online attacks. So the first PQ upgrade can add a circuit that pairs ECDSA auth with PQ encryption, without touching anything else.

Until a PQ encryption circuit is available, users should be aware that historical ciphertexts could eventually be decrypted by a quantum adversary.

This isn’t unique to the enshrined pool. Every privacy protocol deployed today faces the same exposure: a quantum adversary could decrypt Tornado Cash ciphertexts, and worse, forge Groth16 proofs by breaking the pairing assumptions, putting the entire TVL at risk. And Tornado Cash can’t upgrade.

An enshrined pool is better positioned to protect users against quantum risks than are app-level pools.

On state growth:

Following @bbjubjub’s argument, I believe that state growth per se isn’t the concern — the pool is an ordinary EVM contract from a state perspective, and existing strategies for managing high-usage contracts apply equally.

Once state growth is priced correctly, what does using the privacy pool cost? Moving to accumulator-based nullifiers would reduce storage but add significant circuit complexity, trading lower on-chain gas costs for higher proving costs. Whether that helps users depends on the tradeoff.

My inclination is to pursue cost reduction when it becomes a real problem. The note format doesn’t encode the nullifier storage mechanism, so this optimization can be introduced later without disrupting anything.

For the moment, the important thing is to ensure that the enshrined pool manages state growth at least as well as the alternatives (app-level pools) and I believe it does.

On EIP-712 vs type-2 intents:

Even if a wallet shows the EIP-712 struct in the most intuitive way, the UX will not be good unless the wallet understands the semantics of the signature.

E.g., when you sign to list an item for sale on OpenSea, wallets show you what the signature could cause: a loss of an NFT and a gain of ETH. Wallets do this because users can’t be expected to understand what a signature can do by inspection. This is particularly true here as, in all other contexts when a user is sending an ERC20, they expect the wallet to at the very least resolve the token name and symbol.

That said, you’re right that wallet UX is malleable and system contracts are not. We should absolutely expect wallets to build first-class support for an enshrined privacy pool. When they do, an EIP-712 circuit becomes a serious option — and the circuitId architecture lets us add one without changing anything else. A lighter EIP-712 circuit (smaller, cheaper, no RLP parsing or keccak) could coexist alongside Circuit A. Users with updated wallets use the EIP-712 circuit; everyone else stays on type-2 intents.

On soundness bugs, pause mechanisms, and public exits:

A soundness bug in the proof system could indeed allow an attacker to forge proofs and drain the pool before a hard fork is possible. A pause council could respond in seconds, but it would be virtually impossible to ensure that they only responded to soundness bugs, and not, for example, the desires of their local government (leaving aside even the nightmare scenarios of kidnapping etc).

Zcash doesn’t have a pause button either, and when they discovered an infinite counterfeiting vulnerability in their zk-SNARK construction, they fixed it through a scheduled upgrade without an emergency mechanism. Zcash had the advantage of being able to include the fix covertly, which Ethereum’s public hard fork process wouldn’t allow. But the enshrined pool is better positioned to tolerate that transparency: in Zcash, a counterfeiting bug lets an attacker mint unlimited fake tokens inside the shielded pool, and because the shielded supply is opaque, the damage is unbounded and undetectable. In the enshrined pool, an attacker can at most drain the pool’s actual token balance. No fake tokens enter the broader Ethereum economy. The damage is bounded by TVL and immediately visible on-chain.

2 Likes

@JohnG @bbjubjub

I rewrote the draft and now many of my earlier comments in this thread are no longer accurate! Here are the changes:

1. Recursive auth is now the v0 architecture.

I previously argued for a monolithic v0 and a later hard fork into recursion. I no longer think that is the right trade. I now think it is important not to require repeated hard forks for new auth methods, and not to split the anonymity pool by auth method.

2. The old synthetic-intent / Privacy RPC framing is gone from the spec.

I still think delegated proving and a Privacy-RPC-style UX are useful, but they do not belong in the base protocol. The draft still supports third-party proving; it just no longer bakes a specific delegated-proving UX into the protocol.

The only concrete auth flow in the spec is a simpler ECDSA / EIP-712 example, and that is just an example companion auth circuit, not the protocol’s required signing scheme.

3. Labels are narrower now.

Labels are no longer presented as a full proof-of-innocence / compliance system. They are a limited lineage primitive in the base layer. This feature can be extended in the future.

4. Issuer-specific visibility is out of the base draft.

I still think compliance-related extensions may matter, and I’m open to adding something back if we get clearer requirements. But issuer visibility felt too underspecified to enshrine now, especially without better evidence about what issuers actually want from the feature.

The rationale and security sections were also rewritten heavily, so a lot of the earlier discussion about synthetic transaction UX, circuitId, hard-forking in new auth methods, labels, and issuer visibility is now stale.

So if you read the thread from the top, please treat many of my earlier replies as superseded by the current draft.

Thanks again for your feedback! You showed me the error of my ways and the EIP is stronger because of it!

Updated PR: Add EIP: Protocol-Enshrined Privacy Pool by RogerPodacter · Pull Request #11373 · ethereum/EIPs · GitHub

3 Likes

Note: this post is based on an earlier version of the EIP. Changes have been made since then that obsolete some of the content.

S1nus wrote on the PR:

This mechanism is opinionated about note encryption, but not opinionated about secret dispersion: there is no description of where in the block the ciphertexts will live, or if wallets will have to perform trial decryption

Rather than enforcing a specific cipher for note encryption, maybe just leave that up to the wallet and fully decouple the shielded protocol from the payment protocol

Responding here:

On note discovery: fair point. The draft did specify that the ciphertexts are emitted in encryptedNoteData, but it did not state the wallet-side flow clearly enough. I’ve updated the draft to say that wallets discover incoming notes by scanning encryptedNoteData from each ShieldedPoolTransact event and trial-decrypting it with their viewing key material.

On decoupling encryption from the protocol: encryption correctness is enforced in the outer circuit because it means a transfer literally cannot succeed unless the recipient can recover the funds. A proof that encrypts incorrectly doesn’t verify. This is a nice property generally (it prevents users from accidentally burning funds), but it’s especially important for delegated proving as it ensures that a malicious prover can’t silently burn your recipient’s funds by producing garbage ciphertexts.

The tradeoff is that changing the encryption scheme requires a hard fork. But encryption schemes change much less frequently than auth methods, which is why auth is permissionless and encryption is fork-managed.

1 Like

I’ve reversed my position on in-circuit encryption.

Above I argued that the protocol should enforce correct encryption so that a valid transfer guarantees the recipient can decrypt. That’s a nice property, but the cost is too high.

There is a fundamental asymmetry between doing encryption and proving encryption. Encrypting something is cheap. Proving you encrypted correctly inside a ZK circuit is more expensive. By putting encryption in the circuit, the protocol can only use schemes that are cheap to prove, not schemes that are cheap to do.

This matters most for post-quantum encryption. Users should be able to adopt PQ encryption on day one. But if the protocol mandates proving encryption in-circuit, PQ adoption has to wait until someone figures out how to prove a PQ scheme cheaply.

Moving encryption out of the circuit removes this constraint.

The draft has changed substantially since my last update. My March 9 post still applies (recursive auth as v0, synthetic-intent/Privacy RPC removed, issuer-specific visibility removed). Here is what has changed since then:

  1. Note delivery is out of circuit. The protocol no longer proves correct encryption in-circuit. It emits opaque outputNoteData bytes that are hash-bound to the proof, supports an optional delivery-key registry, and defines one baseline X-Wing scheme interpretation.

  2. The key model has changed. The current draft separates:

  • nullifierKey: stable spend secret

  • outputSecret: rotatable secret used only for future output blinding

  • auth credential: per-auth-method credential in the inner circuit

  1. “Labels” are now narrower opt-in origin tags. Deposits are untagged by default. Tagged deposits are opt-in via originMode, compatible transfers preserve the tag, incompatible combinations clear it to zero.

The updated FAQ covers the current design.