EIP-8182: Private ETH and ERC-20 Transfers

What this proposes: A fixed-address system contract and verifier precompile that define Ethereum’s canonical privacy pool: one shared note tree, nullifier set, user registry, and auth policy registry for private asset transfers of ETH and ERC-20 tokens, with public deposits and withdrawals. A recursive architecture keeps protocol invariants in a hard-fork-managed outer circuit while allowing permissionless inner auth circuits for authorization. The contract has no admin key and no on-chain upgrade path.

The outer circuit enforces value conservation, nullifiers, Merkle membership, encryption correctness, and registry checks. Inner circuits handle authentication and intent parsing. 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. The goal is one canonical privacy substrate, not a new app-specific pool.

Why enshrine: Ethereum transactions and balances are public by default, but Ethereum has no canonical private transfer substrate. App-layer pools can provide useful privacy features, but multiple incompatible deployments coexist and none is the default target for wallet and infrastructure integration. That fragmentation hurts adoption and hurts privacy, because splitting users across pools shrinks the anonymity set each pool provides.

This EIP proposes that Ethereum provide the common substrate directly: one system contract, one shared state surface, one verifier path, and one hard-fork-governed upgrade path. If a canonical privacy layer is going to exist, Ethereum should define it directly rather than rely on app-layer competition to converge on a neutral default.

Protocol enshrinement also resolves the upgradeability dilemma. An upgradeable app-layer pool carries admin or governance risk; an immutable pool avoids that risk but cannot evolve as cryptography, proof systems, or auth methods change. The system-contract model keeps the trust base neutral: no admin key, no proxy, and upgrades only through Ethereum’s normal social-consensus hard-fork process.

Key design choices we’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. Is the interface between them — authorizingAddress, authDataCommitment, policyVersion, intentDigest — sufficient, or are there auth patterns that need more from the outer circuit?

  • 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?

  • Label-based lineage: Notes carry a cryptographic label tracing back to their original deposit. Mixed-origin notes receive a sentinel label (MIXED_LABEL). This is committed in the circuit but not enforced as policy — counterparties and wrapper contracts decide what to do with it. Is that enough as a base-layer lineage primitive, or is there a stronger requirement we are missing?

  • System contract vs. precompile-only: A verifier precompile alone makes proof verification gas-feasible, but it does not create a canonical shared pool state or solve the governance/ossification problem for the pool itself. Do people see a precompile-only path that actually addresses those concerns?

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

Links:

3 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.

1 Like

@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

1 Like

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.