PRIVACY WITH NATIVE AA Research · 14 September 2026
Privacy with native AA Private ownership needs a private path to paying for execution.
State Authorization Computation Fees
The presentation moves from the account model through privacy solutions to native-AA transaction workflows, and ends with a direct 8130/8141 comparison. The central question is whether a private user can spend through a fresh account and pay from private value without an identifying native-token top-up or mandatory relayer. Existing shared ETH reserves are allowed. Draft proposals and unmerged edits are distinguished throughout, and every protocol identifier on a slide is the fetched specification’s own name.
PRIVACY WITH NATIVE AA Contents
From the account model to native AA Four parts. The native-AA section uses the specifications’ own frame and field names and ends on the 8130 / 8141 comparison.
I
The EVM account model
03
II
Privacy techniques: pools, RAILGUN and 0xbow, FHE, stealth addresses
04–07
III
Existing systems and how they pay for gas
08–12
IV
Native AA: 8130 and 8141 workflows, extensions, comparison
13–22
Start with the account model, then explain concrete privacy constructions and the gas mechanisms used by existing systems. Zcash, Aztec and Starknet each have a separate page. The native-AA section uses the specifications’ own workflow names and ends on the comparison.
PRIVACY WITH NATIVE AA I · Account model
An account is not its token balance State, authorization and computation are distinct parts of the EVM model.
State What the account holds nonce, ETH balance, codeHash, storageRootUSDC balances live in the USDC contract’s storage Authorization Who may act A signature authenticates the sender Contract rules authorize token spending: transfer, allowance Computation What gets executed The EVM runs the contract call Execution reads and updates state An ERC-20 balance is application state. ETH for gas is native account state, checked by the protocol before any contract code runs.
An Ethereum account has a nonce, ETH balance, storage root and code hash. An ERC-20 balance belongs to token-contract storage. Ordinary transaction validation authenticates the sender and checks nonce and ETH affordability, while token authorization and allowance rules execute inside the EVM.
PRIVACY WITH NATIVE AA II · Privacy pool
How a privacy pool works: Tornado Cash Deposit and withdrawal are public. The proof says only: I control one unspent note in this tree.
Start with the original fixed-denomination Tornado Cash pools. Alice chooses private note material and publishes its commitment with a deposit. Many commitments form a public Merkle tree; the contract holds the assets. Later the note holder proves knowledge of one included note and binds the withdrawal to Bob, without revealing which commitment was used. Bob can be any chosen recipient. In the circuit, nullifier and secret are private inputs; nullifierHash is public. The contract checks an accepted root and valid proof, rejects a reused nullifier hash, records it as spent and releases the denomination minus any relayer fee. A commitment is a sealed receipt, not encrypted tokens. The proof, root, commitments, nullifier hash, addresses, denomination, fee and transaction timing remain public. Unlinkability concerns the deposit-to-withdrawal match under the construction’s assumptions; timing, reused addresses or gas funding can still narrow plausible origins. Original pools have no continuing recipient/change-note ledger. The next page compares this proof statement with the two systems that extend it.
PRIVACY WITH NATIVE AA II · Three pools compared
Same primitives. Three different proof statements. Commitments, a Merkle tree, nullifiers and a ZK proof in all three. What each proof asserts is what each one hides.
Tornado Cash 0xbow · Privacy Pools RAILGUN The proof says “I control an unspent deposit in this tree.” “…and its label is in the approved set.” “This private state transition is valid.” Privacy set Every deposit in the pool The ASP-approved subset of deposits Every shielded balance and transfer A spend leaves A public withdrawal to a chosen address A public withdrawal plus a change commitment New private notes, or a public unshield Who can exclude you Nobody: the pool holds no policy The ASP revokes the label; ragequit exits publicly A Broadcaster may decline; self-signing stays open
All three hide which input funded the spend. Only RAILGUN also hides the output; only 0xbow lets a third party decide whose input qualifies.
These are variations on one design pattern, not code forks: the original Privacy Pools specification explicitly starts from a Tornado-like construction and adds subset proofs, while RAILGUN’s documentation establishes a UTXO architecture without any Tornado lineage. Read the table down the “proof says” row. Tornado proves membership in the whole deposit tree. 0xbow’s withdrawal circuit adds the second membership check: public inputs withdrawnValue, stateRoot, ASPRoot and context; private inputs label, existing value, nullifier, secret and both Merkle paths; outputs a new commitment hash for the remainder and the existing nullifier hash. RAILGUN proves a state transition instead of a single withdrawal, which is why its outputs can stay private. The exclusion row is the one people miss. Tornado’s core layer draws no distinction between deposits. 0xbow’s ASP maintains the approved-label set and its roots through authorized postmen, can revoke a label, and a failed validation leaves only ragequit: a public withdrawal by the original depositor of the full commitment amount, which surrenders the unlinkability. RAILGUN’s Broadcasters are a service, not an eligibility gate, and a user can still self-sign a public submission at the cost of the gas link.
PRIVACY WITH NATIVE AA II · RAILGUN and public DeFi
RAILGUN: private balances, public execution Private balance → Relay Adapt → ordinary EVM calls → private balance. Ownership is hidden; the calls are not.
This is the claim to get right: RAILGUN does not run a private EVM. Its private layer is a shielded UTXO system; anything beyond a private transfer is an ordinary public call temporarily borrowing private funds. The documented mechanism is one transaction in one block: an unshield moves the exact relayAdaptUnshieldERC20Amounts into the generalized Relay Adapt contract, a multicall executes any number of ordered {to, data, value} calls against external protocols, and the tokens named in relayAdaptShieldERC20Addresses are shielded back. Any failed call reverts the whole thing. Three consequences. First, the unshield costs 0.25% of the amount unshielded, so the amount reaching the swap is smaller than the amount spent — the docs work the example of 100 DAI becoming 99.75 DAI. Second, because the tokens sit in Relay Adapt while the calls run, the external protocol sees that contract as its caller, not the 0zk wallet; a protocol that keys a position to msg.sender records Relay Adapt, and a non-transferable position cannot be shielded back. Third, the shield-back list is exhaustive: the docs warn that tokens not listed in the array are not recoverable, which is why Cookbook Recipes generate the fields. Shielding itself is executed publicly from a public wallet. So the privacy boundary is private ownership on either side of a public execution — the right comparison for a private fee path, because the same shape recurs in every design where private value has to settle a public cost.
PRIVACY WITH NATIVE AA II · FHE and stealth addresses
FHE hides values. Stealth addresses hide the recipient link. The two techniques protect different parts of the same transfer.
Zama / ERC-7984 · FHE Alice → Bob: encrypted amount Public token contract and public addresses Encrypted balances and arithmetic Decryption follows an access-control policy ERC-5564 · stealth address Alice → fresh address B′ Bob derives B′’s spending key from a stealth meta-address Public amount and token flow Funding B′ with ETH for gas can reveal Bob again Combine mechanisms when both the value and the identity link must be hidden.
Zama confidential tokens use FHE for encrypted amounts and balances; threshold key management and access control govern who can decrypt. ERC-7984 does not promise hidden sender or recipient addresses. ERC-5564 uses key agreement and ephemeral keys to derive a fresh receiving address from a stealth meta-address. The sender, amount and subsequent public activity remain visible. Both approaches need a fee path that does not reintroduce the identity link.
PRIVACY WITH NATIVE AA III · Two levels of authorization
The same account model applies at both levels Account / state → auth → EVM. The pool’s contract logic runs inside the protocol’s execution step.
Protocol level Ordinary EOA transaction · sender pays gas
State Account Sender’s nonce and ETH balance codeHash and storageRootAuthorization Auth Authorization · sender signatureNonce · replay protectionFee payer · enough ETH for gasComputation EVM Run the pool’s contract call Read and update public state Contract level Privacy pool · inside the EVM above
State Pool account / storage Commitments and accepted roots Spent nullifier hashes Authorization Pool auth Valid ZK proof against a known root Nullifier hash not already spent Computation EVM contract logic Mark the nullifier hash as spent Release funds to the recipient A pool proof authorizes the withdrawal. Protocol auth still requires a valid nonce and an ETH-funded fee payer.
Read the same state, authorization and computation columns as slide 03 at two levels. The protocol row depicts an ordinary EOA transaction: the sender signature authenticates and authorizes the outer transaction, its nonce prevents replay, and the sender is the fee payer whose ETH covers gas plus any call value. “Auth” groups these transaction-validity checks; the three listed checks are privacy-relevant examples, not the entire validity rule set. The EVM then executes the pool call. The lower row expands that EVM step, rather than introducing a second virtual machine: the pool reads commitments, accepted roots and spent nullifier hashes; checks the withdrawal proof, root and unused nullifier; and marks the nullifier as spent before releasing funds. Pool authorization itself is also EVM computation; the columns separate responsibilities conceptually. Other pool checks, including fee bounds, are omitted for readability. A private note does not supply the outer sender’s ETH. If Bob submits directly, a public top-up can expose a funding relationship; this is evidence of funding, not proof of identity. This is the ordinary transaction baseline for the native-AA discussion later in the deck.
PRIVACY WITH NATIVE AA III · Zcash
Zcash: the fee is a value-balance residual The fee is the value a spend does not hand to an output. ZIP-317 prices it by structure, not computation.
Zcash prices transaction structure, not computation: there is no VM metering opcodes, so no gas_limit × gas_price. Where the fee comes from: an Orchard action is one spend and one output combined, which gives arity hiding; valueBalanceOrchard is an int64 field holding the net value of Orchard spends minus outputs, present whenever nActionsOrchard > 0 and signed by bindingSigOrchard. The protocol specification then defines the fee the Bitcoin way: the remaining value in the transparent transaction value pool of a non-coinbase transaction is available to miners, and the sum over the block is an implicit input to the coinbase. So the fee is not a field at all, and the v6 transaction format of ZIP-229 lists an explicit fee field among its non-requirements. How much: ZIP-317 sets conventional_fee = marginal_fee × max(grace_actions, logical_actions), with marginal_fee 5000 zatoshi and grace_actions 2, so a minimal transaction pays 10,000 zatoshi = 0.0001 ZEC. logical_actions sums one contribution per pool: nActionsOrchard, max(nSpendsSapling, nOutputsSapling), 2 × nJoinSplit for Sprout, and for the transparent pool max(ceiling(tx_in_total_size / 150), ceiling(tx_out_total_size / 34)) in bytes. Two design points matter for this deck. First, ZIP-317 says it is not a consensus requirement that fees follow the formula; it is wallet and node policy. Second, the fee must be computable from only the public data of the transaction, precisely so that the amount does not leak the private witness, and wallets SHOULD pay the convention to reduce information leakage. Inclusion is then policy: nodes relay transactions paying at least the conventional fee, and the recommended block-template algorithm counts unpaid_actions = max(0, max(grace_actions, logical_actions) − floor(fee / marginal_fee)) and admits underpaying transactions only while the block total stays within block_unpaid_action_limit = 50, picking candidates weighted by fee ratio up to weight_ratio_cap = 4; zcashd exposes -txunpaidactionlimit, default 50. An underpaying transaction can be consensus-valid and still be deprioritized or evicted under ZIP-401. Status: Revision 0 is Active; Revision 1, which adds nActionsIronwood for the Ironwood pool, is Draft for NU6.3, and Revision 2 for memo bundles depends on undeployed ZIP-248. Relevance: fee settlement is part of shielded validity, so no separately funded transparent account is needed. The fee amount itself stays public, and for an Orchard-only shielded transaction without transparent value flows it equals valueBalanceOrchard.
PRIVACY WITH NATIVE AA III · Aztec
Aztec: many users, one public fee payer Each user’s credit is a private note inside the same contract. The chain records the contract paying, not the user.
Aztec meters in mana, its equivalent of gas, across two dimensions: fee = (daMana × feePerDaMana) + (l2Mana × feePerL2Mana), where DA mana pays for publishing transaction data and L2 mana for execution. The SDK and protocol code spell these as gas in variable names: daGas, l2Gas, feePerDaGas, feePerL2Gas. Fees settle in Fee Juice, bridged from Aztec tokens on Ethereum, held as a public balance, non-transferable, and deducted only by the protocol. The payer can be the account itself or a fee-paying contract, which the docs compare to an Ethereum paymaster. Privacy does not come from hiding Fee Juice; it comes from separating the payer identity from the user. A fully private FPC has no public functions, no owner, no constructor and no offchain agent, so no deployment transaction is needed. Aztec privacy rests on indistinguishability: a private call reveals only its onchain footprint, the number of nullifiers and note commitments, logs and public gas, not which contract or function produced them. A fee adds exactly one further observable, the fee-payer address recorded by set_as_fee_payer(). That is why the address should be shared: every app derives the same address from the class hash and a shared deployment salt, by convention Fr.ZERO, which is a community convention rather than a protocol default. An app that deploys its own copy tags its users’ payments instead. The next page shows how a user funds and proves the credit behind one of those notes. Reviewed guide: Alpha / Testnet, v5.2.0.
PRIVACY WITH NATIVE AA III · Aztec
Aztec: proving and spending that credit mint proves the bridge claim against the nullifier tree. pay_fee() deducts from the note during setup.
Funding. The L1 deposit calls FeeJuicePortal.depositToAztecPublic with the FPC as recipient and the user as claimer, carrying secretHash = computeSecretHash(bridgeSecret) where bridgeSecret = poseidon2([bridgeSalt, claimer], DOM_SEP__FPC_BRIDGE_SECRET). Two salts must not be confused: the deployment salt fixes the contract address, the bridge salt is random per deposit and only the user knows its preimage, which is what FeeJuice.claim requires to consume the L1-to-L2 message. Proving. PrivateFPC.mint(amount, salt, leaf_index) takes the bridge salt, the leaf index and the claimer address, which is msg_sender, to reconstruct the Fee Juice claim nullifier, asserts that nullifier exists in the nullifier tree as proof the deposit was consumed, then emits its own FPC-scoped nullifier so the same bridge credit cannot be minted twice, and credits the claimer’s private balance. The balance itself is an internal note-based BalanceSet of Fee Juice per user, with no constructor, admin or public surface. Spending. FPCFeePaymentMethod wraps PrivateFPC.pay_fee(), which runs in the setup phase, deducts max_gas_cost from that private balance and calls set_as_fee_payer(). There is no refund of unused gas, so read gasUsed from a simulation to right-size limits. Cold start bundles FeeJuice.claim and PrivateFPC.mint_and_pay_fee into the setup phase of a single transaction, crediting amount − max_gas_cost; the three-step claim, mint, pay path is preferable for top-ups because it credits the full amount and decouples the L1 bridge from the first application transaction. Why an internal balance at all: public token functions were removed from the default setup allowlist, so a custom FPC may only call protocol-contract setup functions, and the older reference FPC that collected payment through token calls is rejected on public networks with its PrivateFeePaymentMethod deprecated. Neither pay_fee nor mint_and_pay_fee makes a public cross-contract call in setup, so the allowlist never blocks them. Phases: setup is non-revertible and ends at end_setup(), so payment is committed before the revertible app phase; an optional teardown phase exposes the transaction fee to public functions for refund logic and is billed its full teardownGasLimits.
PRIVACY WITH NATIVE AA III · Starknet
Starknet: a paymaster decouples payer from sender AVNU’s paymaster submits apply_action through its forwarder and pays the STRK gas. That hop is also the censorship point.
Reviewed AVNU private flow · sponsored_private fee mode · API marked Preview
Wallet proves the pool action client-side
→ Paymaster forwarder submits apply_action and pays gas
→ Pool fee reimburses the paymaster
Two costs: STRK gas to the network, paid by the paymaster; the pool fee, paid from the private balance in poolFeeToken.
The decoupling No public account is linked to the private balance The fee payer is the paymaster; the actor is a shielded note No personal STRK top-up, and apply_action carries no user signature The cost Inclusion runs through one permissioned service x-paymaster-api-key is required for sponsored_privateOnly a whitelisted pool; a blacklisted call returns UNKNOWN_ERROR No documented unsponsored path: PRIVACY_REQUIRES_SPONSORING
The decoupling is the point of the design: AVNU’s overview says the relayer submits so that the user never exposes a public account to pay gas and link back to the private balance, and the paymaster bundles the proof through its forwarder contract. The price is censorship resistance. Both the build and execute endpoints require x-paymaster-api-key for the sponsored fee modes, the pool address must be the one whitelisted by the paymaster, error 163 UNKNOWN_ERROR explicitly covers a blacklisted call alongside an invalid key and service unavailability, and error 159 PRIVACY_REQUIRES_SPONSORING rejects a private transaction sent without a sponsored fee mode, so the reviewed documentation describes no self-submitted private path. The tip parameter (slow, normal, fast) puts inclusion priority in the service’s hands too. One documentation tension to keep visible: the paymaster overview says private transactions can be sponsored with an API key "or let users pay it in gasless mode", while the privacy page and the error table say the private path runs in sponsored_private. Scope the claim carefully: this is service availability and gatekeeping in the reviewed flow, not a statement about Starknet sequencer censorship. The API is marked Preview. STRK20 launch descriptions are project claims, not an independent audit. SNIP-36 Phase 1 text still defers full ZK and SHARP/L1 proof verification; keep that source qualification separate from the concrete fee flow.
PRIVACY WITH NATIVE AA IV · First principles
Three solution families Who converts private value into a fee the protocol can collect?
Architecture How it pays What it requires Service fronts gas User emits a private fee output; a relayer pays native gas A submitting service and a reimbursement policy Native contract vault A proof authorizes a shared native reserve; the user settles privately Admissible validation and a durable fee entitlement Native shielded fee validity The protocol accepts a proof of conserved private value that includes the fee A shielded value / fee state transition in the protocol
For Ethereum native AA, the contract vault is the incremental design target.
This is an architectural comparison, not a deployment roadmap. Ordinary ERC-4337 still has a bundler submit and prefund the outer Ethereum transaction. A native contract payer removes that mandatory transaction wrapper if its policy is fully on chain. A protocol-native shielded balance goes further by making the private fee debit part of transaction validity, as native shielded value illustrates in Zcash. None removes the economic cost of execution.
PRIVACY WITH NATIVE AA IV · EIP-8130
8130: one transaction, two actors The same RLP list names the account that authorizes the calls and the account that pays the ETH.
Fifteen fields, in the spec’s own order: chain_id, sender, nonce_key, nonce_sequence, valid_after, valid_before, max_priority_fee_per_gas, max_fee_per_gas, gas_limit, account_changes, calls, metadata, payer, sender_auth, payer_auth. Read them as five jobs: who acts (sender, sender_auth), when the transaction is valid (the two nonce fields and the two timestamps), what gas costs (the two fee caps and gas_limit), who pays (payer, payer_auth) and what it does (account_changes, calls, metadata). The two highlighted pairs are the whole story of sponsorship. sender_auth is authenticator concatenated with data; IAuthenticator.authenticate(hash, data) returns an actorId, and the Keystore’s actor_config scope authorizes it: OPERATOR for ungated initiation or POLICY for initiation gated to the actor’s policy_manager, plus NONCE for a sequenced 2D channel. payer is the sender’s commitment to who pays, and it sits inside the sender’s signed hash: empty means self-pay, a 20-byte address binds a specific sponsor. payer_auth has the same authenticator-concatenated-with-data shape and resolves an actor on the payer account that must hold SPONSOR_PAYER when the payer differs from the sender, or SELF_PAYER when payer equals sender, which is how a dedicated gas key funds another key on the same account. Timestamps are auto-detected as seconds or milliseconds against TIMESTAMP_MS_THRESHOLD; metadata is opaque attribution bytes that do not affect validation. Next slide: the order a node applies all this.
PRIVACY WITH NATIVE AA IV · EIP-8130
8130: nothing changes until the payer is charged Four mempool checks, then four block steps. The ETH precharge opens execution, before any calls run.
Mempool acceptance, steps one to four, is the node deciding whether to hold the transaction; no state changes. It parses sender_auth, resolves the sender and the effective actor state, validates the signature and requires OPERATOR or POLICY scope, then resolves the payer from payer and payer_auth and requires SELF_PAYER or SPONSOR_PAYER, rejecting any payer_auth_cost above MAX_AUTHENTICATION_GAS. It then checks nonce_sequence against the current sequence for that (sender, nonce_key), the payer’s ETH balance, and the validity window plus the resolved actor’s expiry, and finally applies pending-count and replacement policy. Block execution, steps five to eight, is where state moves. Step five precharges the payer effective_gas_limit × max_fee_per_gas, where effective_gas_limit = gas_limit + MAX_AUTHENTICATION_GAS, and the transaction is invalid if the balance cannot cover it. Step six increments the nonce unless the key is NONCE_KEY_MAX and auto-delegates a code-less sender to DEFAULT_ACCOUNT_ADDRESS, a delegation that persists. Then account_changes are applied, actorId is set on the Transaction Context precompile, and calls run as sequentially committed atomic phases: a completed phase survives a later revert, which is what the documented sponsor pattern relies on. Settlement charges gas used plus payer_auth_cost and refunds only the unspent gas_limit, because payer_auth_cost is metered outside it. The one line to say out loud: the payer must already hold ETH at step five, and the calls that could release private value do not run until step seven.
PRIVACY WITH NATIVE AA IV · EIP-8130
8130: where a private fee falls out Three obstacles, each one a rule of the draft rather than an implementation gap.
One: charged too early. The charge is in ETH and it lands at block execution step two, before any call runs, so a shielded note, which only becomes spendable inside calls, cannot satisfy it. B cannot submit as an unfunded self-payer and rely on a later pool withdrawal. Two: charged in public. payer is a 20-byte address covered by the sender’s signature, and eth_getTransactionReceipt returns payer on every AA receipt, so each private spend publishes who paid — the same single extra observable Aztec’s shared fee-paying contract concedes, and the reason a shared payer is only acceptable when it is not a unique proxy for one hidden user. Three: proved off the path. A note-proof authenticator is not in the canonical set, so it runs only where the chain’s acceptance policy is permissive, through a single STATICCALL bounded by MAX_AUTHENTICATION_GAS (configurable, suggested 100,000); a canonical-only chain does not accept it on the 8130 transaction path at all and reaches it only through the EVM. Slide 20 is where 8288 addresses that validation budget. A quieter fourth constraint is throughput rather than validity: mempool acceptance bounds a payer’s pending count, though nodes may grant a higher tier to a locked payer whose bytecode is recognized, which is the spec’s own accommodation for sponsor services. Also worth noting: the Transaction Context exposes sender, payer, calls and gas_limit from validation and actorId from execution, but defines no maxCost(), which Base’s SimplePoolAuth example still relies on.
PRIVACY WITH NATIVE AA IV · EIP-8141
8141: a brand-new account spends from the pool Six frames, three modes. Frame 2 is where the privacy pool pays the gas, before anything executes.
The whole lifecycle of a fresh account in one transaction, and every frame tagged with its mode, because the mode is what the protocol keys its rules off. Frame 0, deploy, mode DEFAULT with flags 0x0: it creates B, typically through a deterministic factory such as the EIP-7997 predeploy, and it is the only frame allowed to install code at tx.sender or SSTORE to its storage during the prefix. If present it must be the first frame, after any leading protocol verifier frames. Frame 1, only_verify, mode VERIFY flags 0x2, target tx.sender = B: it runs as a STATICCALL, B checks Alice’s spend proof there, and it must call APPROVE(APPROVE_EXECUTION), which sets sender_approved. Frame 2, pay, mode VERIFY flags 0x1, target the pool vault P: P’s own policy accepts that proof instead of a live sponsor signature, and APPROVE(APPROVE_PAYMENT) succeeds only if sender_approved is set, payer is unset and P is solvent; it increments the sender nonce, sets payer = P and collects max_cost. That is the fee coming out of the pool rather than out of a user account. deploy | only_verify | pay is one of the four recognized public-mempool prefixes, so a node stops simulating once payer is set. Frames 3 and 4, mode SENDER with caller B: the first calls the pool to withdraw, which is where the nullifier is written, since a static VERIFY frame cannot write one; the second spends the withdrawn tokens on the application. ATOMIC_BATCH_FLAG 0x4 on frame 3 puts the two in one batch, so the withdrawal cannot land without the action. Approval flags are banned inside a batch, and unrolling a failed batch never rolls back the nonce increment or the max_cost collection. No VERIFY frame may follow the prefix. Frame 5, post_op, mode DEFAULT with caller ENTRY_POINT, is the paymaster’s optional settlement hook. What the chain publishes: sender = B, payer = P, and the pool and app calls themselves. Alice is not a field anywhere.
PRIVACY WITH NATIVE AA IV · EIP-8141 mempool
8141: three mempool rules, two proposed fixes Each rule blocks the private spend for a different reason. Two have draft EIPs, the third an unmerged PR — and none of them makes the note spendable exactly once.
All three rules are public-mempool policy, not consensus; say “the public mempool rejects”, never “8141 rejects”. Rule 1, storage: the generic validation trace rules reject a prefix whose execution reads storage outside tx.sender, including transitively through CALL* or DELEGATECALL, so the pay frame at P cannot read P’s own root or nullifiers — the exact state it exists to check. VERIFY is also static, so a validator cannot fix it by writing a nullifier before payment. EIP-8272 answers exactly this rule: the pool publishes its root to RECENT_ROOT_ADDRESS, a canonical recent_root_verify frame gets exactly two extra permissions (SLOTNUM, and SLOAD of the storage keys derived from its own tuples) while the code there matches RECENT_ROOT_CODE, and account validation binds the verified (source_id, slot, root) tuple through FRAMEPARAM and FRAMEDATALOAD without reading P’s storage. Two caveats: a recorded root proves publication and recency, not current unspentness; and that frame’s limits.execution counts toward MAX_VERIFY_GAS, so the fix for rule 1 spends the budget in rule 2. Spell the nullifier gap out, because it is the one people assume 8272 closes. Consensus treats root as an opaque bytes32, so a pool may publish a nullifier-set root through the same ring — but references MUST target slots strictly before current_slot, so even the freshest tuple is a slot old and any spend since is invisible; non-membership against that snapshot is not unspentness now. The prefix also cannot write it: the trace rules ban state writes outside the first deploy frame at tx.sender, VERIFY executes as a STATICCALL where only APPROVE may touch state, and there must not be a VERIFY frame after the prefix — the spec’s own reason is that its revert would invalidate the whole transaction. So the nullifier SSTORE happens in a SENDER frame, after the pay frame has already incremented the sender nonce, set payer and collected max_cost, and settlement returns payer_refund = max_cost − charged_fee. A reverted SENDER frame is an ordinary frame revert, not an invalid transaction. Concretely: two transactions spending one note both pass the prefix; the loser is included, reverts, and the pool eats charged_fee for it. In the public mempool the blast radius is bounded by the rules that already make the pool unusable — the rule 3 cap of one pending transaction per non-canonical payer, per-payer reservation accounting against the payer’s balance, and one pending transaction per sender — while through a builder or private relay nothing bounds it. Reading nullifiers in the prefix at all requires putting the vault at tx.sender, which serializes every user of the pool behind that single pending transaction. Rule 2, budget: the sum of limits.execution across the validation prefix, plus the intrinsic cost of validating tx.signatures, must not exceed MAX_VERIFY_GAS 100,000, and the summed limits.state must not exceed MAX_VERIFY_STATE_GAS 500,000. Whether a spend verifier fits inside a VERIFY frame depends on its circuit and the full prefix cost; no end-to-end measurement is established here. EIP-8288 answers it by moving verification out of the EVM entirely: a dependency verification frame, DEP_VERIFY_FRAME_MODE = 3, with target None, value 0 and flags 0, carries 96-byte (scheme, data_hash, verification_key_hash) triples that are never executed as code, and its gas_limit must equal LEANSPHINCS_VERIFICATION_GAS 3,000 or LEANSTARK_VERIFICATION_GAS 30,000 summed over the triples. The proofs themselves are verified off-chain by mempool nodes and builders, which aggregate them recursively; a block is valid only if its header’s recursive_stark covers every dependency it contains. The account still has to bind its own public inputs, reading the declared triples back through FRAMEPARAM, FRAMEDATASIZE and FRAMEDATACOPY. Caveats worth saying aloud: the schemes are leanSPHINCS and leanSTARK only, so an arbitrary pool circuit does not qualify as drawn; MAX_STARKS_PER_TX is 1 and MAX_SIGS_PER_TX is 16; AGGREGATED_VK is still TBD; the fetched 8288 text never says how a mode-3 frame classifies against 8141’s four recognized validation prefixes, where 8272 does state that its verifier frames are skipped for prefix matching; and mode 3 collides with EIP-7906’s allocation in the retrieved texts. Rule 3, concurrency: a non-canonical paymaster is admissible but MAX_PENDING_TXS_USING_NON_CANONICAL_PAYMASTER is 1, so a proof-checking vault backs one pending spend at a time across the whole pool. The way out of that cap in the current text is the canonical-paymaster exception, which waives the generic trace rules for a pay frame whose runtime code exactly matches the canonical implementation — and that implementation authorizes with a single secp256k1 signer via ecrecover, a live per-transaction signature, which is the sponsor dependency the proof policy exists to remove. Moving the vault into the sender position only trades the cap for the one-pending-frame-transaction-per-sender rule. Nothing merged addresses this. PR 12328, open and unmerged as of 16 September 2026, proposes an egress-decidable paymaster class: a node decides from the target’s runtime code alone that ether can leave only through an APPROVE(APPROVE_PAYMENT) collection it has already reserved — one candidate form is a whole-code scan finding no CALL, CALLCODE, DELEGATECALL, CREATE, CREATE2, SELFDESTRUCT or code re-pointing at any instruction position — and such a payer MUST NOT be subject to the cap. Its own open items include whether a recent-root frame plus a membership proof fits inside MAX_VERIFY_GAS for the pay frame, which is unmeasured. Keep the spec’s wording tension in view: one canonical-paymaster sentence reads as though only canonical paymasters are eligible, while the policy summary and the non-canonical section describe capped admission. And rejection is not invalidity — a builder or private relay can include the transaction — but that changes the public-propagation and service-availability assumptions.
PRIVACY WITH NATIVE AA IV · EIP-8272
8272: the root arrives as frame data Mempool rule 1 blocks the read. 8272 removes the need for it.
This is the answer to mempool rule 1 from the previous page, and only to that rule. The mechanics behind the three steps: a root source calls RECENT_ROOT_ADDRESS with 64 bytes of calldata, salt ‖ root; source_id is keccak256(source_address ‖ salt) and the entry is stored at ring index slot mod 8192 as an entry hash, so RECENT_ROOT_LENGTH 8192 is the depth of the ring and a root written during slot S becomes referenceable from S + 1. The verifier frame, named recent_root_verify for public-mempool classification, is VERIFY with target RECENT_ROOT_ADDRESS and flags, value and state limit all zero; it carries 1 to 16 packed 72-byte (source_id, slot, root) tuples whose slots are strictly before current_slot, it is frame 0 or frame 1 after expiry_verify, and a transaction may contain at most one. Its two extra permissions apply only while it runs at the top level with code at RECENT_ROOT_ADDRESS exactly equal to RECENT_ROOT_CODE: SLOTNUM inside that runtime, and SLOAD of the storage keys derived from the frame’s own tuples. Nothing passes to a nested call or to the later pay frame, which reads the result through FRAMEPARAM — target, mode, flags, limits.state, data length and status == 1 — and FRAMEDATALOAD or FRAMEDATACOPY; the proof’s public root must equal that exact tuple. Consensus treats root as an opaque bytes32, so the application defines what it commits to. What this does not fix: a recorded root proves publication and recency, not current unspentness, source honesty or payer solvency; a static VERIFY frame still cannot write an application nullifier, so current-spend enforcement lives in user_op; and mempool rules 2 and 3 are paymaster admission policy, entirely untouched. Two costs to keep in view: eviction is by age, a pending transaction dropped once current_slot − slot reaches 8192, a usable window of 8191 slots; and the frame’s limits.execution counts toward MAX_VERIFY_GAS, which is rule 2. RECENT_ROOT_CODE is still TBD in the fetched Draft, so the exact runtime a node must match is not yet fixed. One exception worth naming: if the funded vault is itself tx.sender, its storage is already readable under the generic rule and 8272 is not required — but that variant meets the one-pending-frame-transaction-per-sender limit instead.
PRIVACY WITH NATIVE AA IV · EIP-8288
8288: make proof-heavy validation affordable Aggregation is a cost mechanism; zero knowledge comes from the underlying proof.
Proposed aggregation path
DEP_VERIFY_FRAME_MODE frame (mode 3) declares (scheme, data_hash, verification_key_hash) triples
→ Mempool wrapper per AGGREGATION_INTERVAL: direct proofs or one recursive STARK
→ Block validity: one recursive STARK proves every dependency
Privacy relevance Amortize repeated verification Schemes: LEANSPHINCS_SCHEME signatures and LEANSTARK_SCHEME proofs against a shared verifier Aggregation lowers the per-transaction cost, not the disclosure Remaining work Fit the whole validation prefix Root checks, proof checks, calldata and state must fit MAX_VERIFY_GAS = 100,000 and MAX_VERIFY_STATE_GAS = 500,000 A cheap Merkle-proof estimate is not a ZK-verifier benchmark
EIP-8288 proposes in-mempool signature and proof aggregation: dependency verification frames declare (scheme, data_hash, verification_key_hash) triples and a recursive STARK proves them per block. It does not hide a public witness or define a privacy pool. In base EIP-8141 the validation prefix’s declared execution limits plus signature verification must fit MAX_VERIFY_GAS, 100,000 gas, and its declared state limits MAX_VERIFY_STATE_GAS, 500,000. Draft composition is unsettled: 8288’s DEP_VERIFY_FRAME_MODE is 3, the same value EIP-7906 gives POST_TX and EIP-8209 gives its COMMIT frame, and it uses stale introspection fields; pending PRs 12309 and 12322 change these. A cheap ordinary Merkle proof estimate is not a benchmark of a complete ZK spend verifier.
PRIVACY WITH NATIVE AA IV · Compatibility status
These drafts are a design space, not a ready stack Resolve the conflicts before claiming a working public-mempool protocol.
Issue Why it matters Required resolution Frame mode 3 claimed three times 8288 DEP_VERIFY_FRAME_MODE, 8209 COMMIT and 7906 POST_TX cannot all be mode 3 Agree a registry / mode allocation (PR 12253) Stale introspection fields Proof and intent adapters may inspect different data Align payloads and frame accessors (PRs 12309, 12322) Failure semantics 7906 prose conflicts; repayment preservation is pending Reconcile PR 12304 exemptions and PR 12266 approval effects Unfinished admission details 8272 runtime (RECENT_ROOT_CODE TBD), sealed payer and proof budget unproven Implement, measure and test the composed rules
This is a specification review, not an interoperability test. Drafts should be pinned at exact revisions for implementation. Pending PRs 12309 and 12322 address mode and introspection alignment; 12324 and 12326 adjust aggregation placement and atomic batching. The base frame specification recognizes only modes zero through two until extensions are activated. No slide treats a PR or Informational document as a deployed capability.
PRIVACY WITH NATIVE AA IV · Final comparison
8130 vs 8141: the privacy-relevant differences Both separate sender and payer. The validation and payment interfaces differ.
Capability 8130 · Keystore Accounts 8141 · Frame Transaction Authenticate / authorize authenticate(hash, data) → actorId; Keystore scope grants OPERATOR / POLICYonly_verify → APPROVE(APPROVE_EXECUTION)Authorize gas payment payer + payer_auth; SPONSOR_PAYER; ETH prechargepay → APPROVE(APPROVE_PAYMENT); collect max_costExecute the operation calls: sequentially-committed atomic phasesuser_op in SENDER mode; optional atomic batchesCustom private fee proof Permissive acceptance policy: any authenticator via bounded STATICCALL; canonical-only path: no Programmable VERIFY; public-mempool trace and payer rules still apply Shared-root validation No spec mechanism; a custom dependency policy is needed 8272 recent_root_verify: an explicit, narrow exception
8130: depends on the chain’s authenticator acceptance policy (the L1 profile pairs with permissive). 8141: explicit frames and a clearer extension path, with fee safety and public-mempool admission unresolved.
This is the final slide. A canonical-only acceptance policy does not supply arbitrary private-note authenticators; a permissive policy admits bounded custom authentication, so privacy cannot be ruled out categorically, but admissibility of the authenticator and payability of the fee from private value remain separate questions because the payer is still precharged in ETH. 8141 exposes explicit validation and payment frames, with 8272 addressing admissible recent-root reads. Both still need solvent native fee backing and private fee accounting. Historical roots do not prove current unspentness, and no reviewed base proposal establishes the complete scalable, relayer-free public-mempool flow. Draft status and unmerged edits are not deployment claims.