Ethereum’s account layer sits where two transformations cross. One is account abstraction (AA from here on): freeing accounts from the primitive “one private key decides everything” model, making batching, sponsored gas, and custom signature rules possible. The other is the quantum upgrade: swapping in quantum-resistant cryptography before quantum computers can break elliptic-curve signatures. The two meet in the same place, because the pluggable verification AA provides is the most natural landing spot for post-quantum signatures on Ethereum. This series follows that crossing line; later installments will get to newer proposals such as native account abstraction (EIP-8141). This first article lays down the foundations and the security skeleton of AA itself, in one pass.
The whole article hangs on a single thread, so let me state it up front:
Every transaction on the chain gets re-executed by thousands of nodes, burning real compute, so somebody has to pay; and the question of who pays, and who guarantees they actually will, shapes the entire transaction system.
Nine years of account abstraction, with all its designs, compromises, and attack surfaces, is that one question being answered over and over. Keep this thread in hand and everything below follows from it.
The EVM charges by the instruction
To understand anything that follows, start with one plain fact. The EVM is a stack machine whose programs are sequences of opcodes, and every single opcode has a price tag set by the protocol:
| Operation | opcode | gas price |
|---|---|---|
| Addition | ADD | 3 |
| Multiplication | MUL | 5 |
| Read one storage slot (cold) | SLOAD | 2100 |
| Write one storage slot | SSTORE | up to 22100 |
| Cross-contract call | CALL | from 2600 |
The gas cost of a piece of code is not some bundled estimate. It is the sum of the price of every instruction on the path actually executed, accumulated one instruction at a time.
Gas exists to put a price on occupying the whole network’s compute. When your contract runs on chain, every node in the network runs it again to verify the result; without a price on that, one infinite loop could drag down the entire network. So here is an equation worth burning into your intuition: compute is gas, gas is ETH, and ETH is real money. Whoever makes the nodes work, someone has to pay for that work.
As a side effect, this explains a glaring number that will keep coming back throughout this series. Post-quantum signature verification on chain runs to millions of gas, not because the algorithms are mysterious, but because verifying Falcon or ML-DSA means executing a huge number of modular multiplications, NTT transforms, and hash expansions, each one a priced opcode. Instruction count times unit price, and the number is simply that large. For reference, a classical ECDSA check through the ecrecover precompile costs 3000 gas. A verification a thousand times more expensive has to be squeezed into a system that is defensive everywhere. Where it fits and how is a question this series will keep coming back to.
Two kinds of accounts, one four-field tuple
Ethereum’s global state is one giant mapping from addresses to accounts. And every account, regardless of kind, is the same four-field tuple:
(nonce, balance, storageRoot, codeHash)The nonce is a counter (for an externally owned account, the transactions it has sent; for a contract, the contracts it has created), balance is the ETH balance in wei, and storageRoot points to the root of the account’s private storage tree. The only field that distinguishes the two kinds of account is the last one: a contract account’s codeHash points to deployed bytecode, while an externally owned account (EOA, the ordinary kind controlled by a private key) has always had codeHash = keccak256(""), the hash of the empty string. Note the wording: the field is not missing, it is filled with an empty value. That empty slot becomes the protagonist later, when we get to EIP-7702.
Two iron rules fall straight out of this model, and both get used repeatedly below:
- An EOA’s identity is welded to its key at the protocol level. Its address is the last 20 bytes of the keccak hash of its public key. An EOA cannot even rotate its ECDSA key, let alone swap in a different signature scheme. That is the true shape of the problem AA sets out to solve.
- Only EOAs can initiate transactions. A contract has no private key and is forever passive; it runs only when called by a transaction whose ultimate origin is some EOA. This rule is the final answer to “why does 4337 need bundlers,” which we’ll get to.
The life of an ordinary transaction: validation happens outside the EVM
What’s written on the envelope of an ordinary transaction? nonce, gasLimit, maxFeePerGas, to, value, data, plus a secp256k1 ECDSA signature (v, r, s). Notice that value (how much ETH to transfer) and to (whom to call) are printed plainly on the envelope, statically readable. That detail becomes crucial when we contrast this with 4337.
When a node receives the transaction, before the EVM is ever touched, the client software (geth and friends, written in Go or Rust) does three things:
- recover the public key from the signature, derive the address, and confirm it matches the sender;
- check that the transaction’s nonce is exactly the account’s current value;
- check that the balance covers
value + gasLimit × maxFeePerGas.
All three are static reads against the state database, microsecond-fast, with deterministic conclusions. Only if all pass does the transaction enter the mempool (the pool of pending transactions) and become eligible for a block. Only once it’s in a block does the EVM start for the first time, executing the call given by to and data, metering gas instruction by instruction. Execution can fail; if it does, the state changes roll back, but the gas already burned is charged anyway, because the compute was genuinely consumed.
One point deserves heavy emphasis: protocol-level validation is hard-coded in the client, happens outside the EVM, involves no contract whatsoever, and executes not one line of on-chain bytecode. Two application-layer tools are easy to conflate with it: the ecrecover precompile at address 0x01 and EIP-1271’s isValidSignature. Their math is identical to protocol-level verification, but they are utilities that contract code uses during execution to check some signature internally (permit flows, multisigs, meta-transactions). Whether a transaction may enter the chain lives on a different floor of the building. Same hammer, one held by the protocol, one held by contracts.
And here is a seed worth planting early: a world where one public contract dispatches signature verification does exist. It just is not the Ethereum of today; it is the world that exists after 4337 is built, and EntryPoint is exactly that public verification dispatcher. Hold that thought; it connects up later.
One more property from this section deserves its own box. Because the payer is hard-wired by the protocol to be the signer, a node can be one hundred percent certain that “this transaction’s gas will be paid, and can be paid” without executing any code, using three table lookups. The judgment is cheap, static, and reliable. Hold onto that property. It is precisely the thing about to be broken.
Account abstraction in one sentence
With the groundwork laid, the definition takes one sentence:
Account abstraction moves the authority to decide “what counts as a valid transaction” out of protocol hard-code (client software) and into the account’s own contract code.
Your account is a contract with a validateUserOp function, and whatever you write there is what validity means: switch to P-256 so a phone passkey can sign directly, switch to Falcon or ML-DSA for post-quantum resistance, add daily limits, add multisig. Batching, sponsored gas, swappable signature schemes: all of it is corollary to that one sentence.
Three places where intuition tends to go wrong are worth flagging right beside the definition:
- Native 4337 involves no EOA at all. The account is a contract deployed via CREATE2, its address derived from no private key. The intuition that “AA binds an EOA to a contract” comes from EIP-7702, which is a bridge built for existing EOAs, not the core of AA.
- The bundler never verifies signatures. It simulates your account’s validation code off chain, which is economic self-protection for deciding whether to accept your order; the authoritative judgment happens on chain, where EntryPoint calls the same code. The same code runs twice, for two different purposes.
- AA is not quantum-resistant in itself. What it provides is crypto-agility: the verification logic becomes pluggable code, and quantum resistance comes from the scheme you plug in. This distinction matters enormously, and the 7702 section below shows exactly where it breaks down.
msg.sender and the batching deadlock
Why go to the trouble of turning validation into code? Start with what exactly was stuck.
The EVM has a basic quantity during execution called msg.sender: the direct initiator of the current call frame. When an EOA calls contract X directly, inside X, msg.sender is that EOA. When X then calls Y, inside Y, msg.sender becomes X. It changes at every hop. The top-level msg.sender is determined by the transaction signature; without the private key you cannot forge it, which is why require(msg.sender == owner) inside a contract is safe. This is identity’s cryptographic root at the protocol level.
But the same mechanism deadlocks batching. A transaction has exactly one top-level call (one to, one calldata), while a huge class of operations (moving your tokens, approve, voting, any owner-only function) requires msg.sender to be you personally. To do several things in one transaction, you’d have to call some batching contract and let it make the calls on your behalf, but then every inner call’s msg.sender is the batching contract, not you. The classic example: routing an approve through multicall grants the allowance to the wrong owner. This is exactly why approve-then-swap has always been two signatures.
“Fine, then pre-approve the contract.” That works, and it is in fact the status quo: approve the Uniswap Router, let it transferFrom on your behalf. The router pattern of DeFi. But price this pattern out honestly and there are three layers of cost:
- A standing allowance is a blank check. Allowances have no expiry, no per-transaction cap, no conditions, and the industry habit of infinite approvals has made approval phishing and router-bug drains one of the largest loss categories on chain. Permit2 exists specifically to bolt expiry and caps onto approvals, and the existence of that patch is itself a confession that the underlying model is too crude.
- You sign a check, not an operation. Your signature endorses a spending limit, while the actual execution path lies outside what you signed.
- Most fundamental of all: an allowance can only delegate transferable rights, never identity. ERC-20 pulls are a hole the protocol deliberately opened; but voting, claiming an airdrop bound to your address, owner-only parameters, anything that checks msg.sender, has no approve mechanism to hand over.
A smart account erases all three at once. The account contract is the identity: inside its execute loop, every inner call’s msg.sender is the account address, and the tokens and approvals all live at that address. The signature covers the hash of the whole UserOperation, so every call in the callData is inside the endorsement; what you sign is what executes. You can even do approve, use, and revoke atomically, leaving no standing allowance behind. One sentence: multicall plus pre-approval solves “batching token transfers,” a subset, at the price of blank checks; AA solves “batching acts of identity,” the full set, with no residue.
The original sin of working around the protocol: GSN and _msgSender()
Before AA took shape there was an even earlier workaround worth recording separately, because it was the first exposure of the original sin of the “work around the protocol” school.
Meta-transactions set out to solve a real onboarding problem: a new user has no ETH and cannot pay for their first transaction. The scheme: the user signs offline (free), and a relayer submits the transaction with its own ETH. But now the transaction’s top-level initiator is the relayer, so contracts see msg.sender equal to the relayer, and the real user’s identity is lost. ERC-2771’s remedy: the user signs their real address into the message, the relayer appends it to the end of calldata, and the target contract stops reading msg.sender directly, calling _msgSender() instead, whose logic is “if this call came through a forwarder I trust, ignore msg.sender and read the real address off the calldata tail.”
Look carefully at what just happened. The source of identity moved from “a top-level msg.sender guaranteed by cryptography” to “a piece of plaintext in calldata, plus faith that the forwarder behaves.” The cracks arrive on schedule. If the trust check is even slightly wrong, anyone can call the contract directly, paste your address onto the calldata tail, and act as you. If the trusted forwarder itself is compromised, compromising it equals impersonating everyone.
This pattern will repeat. Every time you bypass a protocol-native guarantee, you must substitute a layer of application-level assumption, and assumptions break. GSN’s _msgSender() forgery surface was the first time; 4337’s reliance on honest bundler simulation is the second; 7702’s ECDSA backdoor is the third. Tracking where the trust root of identity and authorization slides, generation by generation, is the main thread for reading AA’s entire security history. We’ll gather that thread into one picture at the end.
The pivot: programmable validation opens the DoS door
Now we reach the hinge of the whole story.
Recall why the mempool is safe under fixed rules: “will this transaction pay” is three static table lookups, cheap, deterministic, reliable. Once validation becomes the account’s own code, what does that judgment turn into? Into “run a Turing-complete program and predict its result.” The validation code might say “pay only if the block number is even,” or “pay only if some oracle price is above X,” or it might simply be extremely expensive to run. And there is no way to know what arbitrary code outputs except to execute it. That is the precise meaning of “you only know by running it,” and it is the source of all the trouble.
Denial of service emerges from this structure automatically, in two variants.
Variant one is the basic form. Submitting an operation to the mempool is free (it hasn’t touched the chain; nobody has paid gas for it), yet to decide whether to accept it, a node must first run its validation code, burning the node’s own CPU. An attacker mass-produces operations whose validation is guaranteed to fail (deliberately broken signatures, or code that always reverts) and pours them in for free. Each one forces every node to do real work just to discover “this will never pay, discard.” The attacker spends nothing; the nodes burn real money.
Variant two is nastier: the invalidation attack. The attacker first submits a thousand operations whose validation all passes right now. Nodes verify them, happily admit them to the mempool, CPU already spent. Then the attacker sends one cheap transaction that mutates a piece of state all those validations depend on (drain a balance they read, flip a shared storage bit), and a thousand operations go invalid simultaneously. All the earlier verification work is wasted, and the node has to sweep them out too. One transaction’s cost leverages away a thousand validations’ worth of work, an amplification of several orders of magnitude.
This problem is structural, and there is no way around it. One summary line deserves to stand on its own:
Every AA design since, at its core, is a patch on this newly opened hole.
EIP-2938 puts shackles on the validation phase, forcing it back to being cheap and predictable. ERC-4337 leaves the protocol untouched and outsources the risk to bundlers who volunteer to carry it. EIP-8141 (native AA) takes that risk back into the protocol’s own mempool. Nine years ago this same problem killed EIP-86, the first attempt at in-protocol AA, and today’s designs still orbit it.
EIP-2938: PAYGAS and the three shackles
2938 was the first systematic answer. It ultimately died of scheduling politics around the Merge, but its design lives on almost unchanged inside today’s 4337, so it deserves a full teardown.
First, a piece of the model worth nailing down. The picture of “validate first, and only after passing does the EVM start” is true only of ordinary EOA transactions, where validation really does live in client code outside the EVM. The entire innovation of 2938 and 4337 is precisely that validation moves into contract code, and contract code can only be executed by the EVM. So in these two worlds, the validation phase is not something “before the EVM”; the validation phase is a stretch of code the EVM is running. It is all one continuous EVM execution from start to finish, merely split down the middle by one special instruction.
That instruction is PAYGAS. It is not a gate you pass to enter the EVM; it is the boundary marker between the front half and the back half of the same run. Pseudocode makes it obvious:
// ---- validation segment ----sig_ok = verify_signature(tx.signature) // verification opcodes, running in the EVMif (!sig_ok) { REVERT }if (nonce != expected) { REVERT }// ...other authorization checks...PAYGAS(gas_price, gas_limit) // reaching this line means everything above passed// ---- execution segment ----do the actual work...PAYGAS is a real instruction. The moment the EVM executes it, gas money is locked and deducted from the account’s balance, and payment is settled from that instant; even if the execution segment later fails completely, that gas is charged. Its position in the code encodes a contract: execution reaches me if and only if every check above me passed. If any check fails, REVERT kills the run on the spot and rolls back state; the code never reaches PAYGAS, and payment never happened. The CPU the node burned simulating that failure is the node’s own loss. That is exactly the DoS core from the previous section: before payment lands, the cost of failed validation falls on someone else.
So 2938 puts three shackles on the validation segment, each aimed at a specific threat:
- Ban environment opcodes (TIMESTAMP, NUMBER, COINBASE and friends). If validation results depend on “what time is it, which block is this,” then passing now does not mean passing next block; the environment shifts and the result flips. Ban them, and a validated operation stays trustworthy.
- No reading external state; validation may only read the account’s own storage. If my validation depends on your state, you mutate it and I go invalid; that is exactly where the invalidation attack’s leverage lives. Locked into my own storage, no outsider can void my validation.
- A gas cap on the validation segment. Validation is work the node does for free on your behalf; uncapped, an attacker writes a validation segment that burns tens of millions of gas and grinds the network down.
On top of the protocol shackles there is one mempool rule as a finishing move: each account may have only one pending operation at a time; to send another, wait for the last one to land. That closes the path of stuffing a hundred mutually conflicting operations from one account so nodes validate ninety-nine of them for nothing.
Each shackle is worth remembering by name, because all of them get reincarnated:
| 2938 design | Reincarnated as |
|---|---|
| Shackle one: ban environment opcodes | ERC-7562’s banned-opcode list |
| Shackle two: own storage only | ERC-7562’s storage whitelist |
| One-pending-op mempool rule | Bundler admission policy |
Read any line of 7562 and you can find its birth certificate here. And shackle three, the validation gas cap, is a wall the quantum upgrade cannot route around: post-quantum verification at millions of gas is precisely what slams into that ceiling. “Validation gas cap versus heavyweight signatures” is a structural collision, and this series will run into it again.
ERC-4337: don’t change the protocol, hire a translator
After 2938 died, the community flipped the approach: change not one character of the protocol, and rebuild the whole system in userland. That is 4337. Start with its most frequently asked question: why must the bundler exist at all?
Because the protocol accepts exactly one kind of transaction: initiated by an EOA, signed with ECDSA, gas paid in ETH by the sender. A UserOperation (4337’s “transaction,” UserOp from here on) violates all three: arbitrary signature scheme, initiated by a contract account, gas payable by someone else. In the protocol’s eyes a UserOp is contraband, and someone has to translate it into a legal transaction. That is the bundler’s job: collect a batch of UserOps from a dedicated alt-mempool, wrap them in one real transaction signed with its own EOA, front all the ETH, and get reimbursed afterward from an escrow mechanism. It is simultaneously translator, creditor, and risk-bearer. In one sentence: the bundler is the price of wanting AA without a hard fork.
The on-chain dispatch hub is a public contract called EntryPoint. The bundler’s wrapper transaction calls EntryPoint.handleOps(ops), and EntryPoint then calls each account’s validateUserOp in turn. 4337 cannot mint a new opcode, so where did PAYGAS go? It is replaced by an ordinary in-contract transfer:
function validateUserOp( UserOperation calldata userOp, bytes32 userOpHash, uint256 missingAccountFunds) external returns (uint256) { // 1. validation segment: everything before PAYGAS require(_checkSignature(userOp.signature, userOpHash)); // revert on failure // 2. payment lands: the PAYGAS moment if (missingAccountFunds > 0) { (bool ok, ) = msg.sender.call{value: missingAccountFunds}(""); // msg.sender is the EntryPoint; this is a gas deposit, not business value }}(Simplified; nonce handling and the return value are omitted.) Do not get the direction backwards: PAYGAS does not trigger validateUserOp. EntryPoint calls validateUserOp, and inside it, after the signature check passes, the account actively pushes missingAccountFunds (the worst-case gas money for this operation) into EntryPoint’s escrow. That transfer is PAYGAS reborn: it happens if and only if every require above it passed, the position-encoded contract intact. The failure consequences line up too: in 2938, a validation revert burns the CPU of every node, so the protocol clamps it with shackles; in 4337, a validation revert burns the bundler’s CPU, so the bundler protects itself with off-chain simulation plus the 7562 sandbox.
1 ETH in the account, a 100 ETH transfer: validity split into four questions
One case study illuminates 4337’s whole validation philosophy. A smart account holds 1 ETH, and its owner signs a UserOp transferring 100 ETH. Who steps in to stop this doomed transaction? Answer: nobody checks “is there really 100 ETH,” and that is deliberate.
The traditional world can stop it because the 100 ETH is written on the envelope’s value field; the protocol statically checks the balance and rules the transaction invalid before it ever enters a block. In 4337, “transfer 100 ETH” is buried deep in callData as an argument to the account’s execute function, opaque bytes to the protocol, to the bundler, and to EntryPoint alike. EntryPoint never parses callData’s meaning. So the question “is this transaction valid” splits into four questions, each with its own owner:
- Is the envelope transaction valid? The protocol checks the bundler’s wrapper transaction, its signature, nonce, and balance. The 100 ETH is invisible at this layer. Pass.
- Will the gas be paid? In validateUserOp the account already prepaid
missingAccountFunds, which is gas money only (roughly the gas limit times maxFeePerGas), and 1 ETH covers it easily. Pass. - Was the operation authorized? The account’s verification code checks whether the owner signed this UserOp, not whether it can succeed. The signature is impeccable. Pass. Here is a sentence worth framing on the wall: a doomed transfer can be perfectly authorized.
- Do you actually have 100 ETH? No layer treats this as an admission condition. It surfaces for the first time during execution: the inner CALL carries a 100 ETH value, the balance is short, execution reverts. But the gas money locked into escrow during validation does not roll back. It is charged, the event log records
success=false, the bundler loses nothing, and the only person paying is the signer.
Why is the fourth gate deliberately absent? Two reasons:
- It is impossible. State can change at any moment between simulation and inclusion, so nobody can promise “execution will succeed.” Forcing bundlers to guarantee execution results would push unbounded risk back onto them, and eliminating precisely that risk is what the validate-then-execute split is for.
- It is unnecessary. Escrow has already turned execution failure into an event that is harmless to the system and billed only to the signer, the same philosophy as the traditional world, where calling a contract that reverts is still a valid transaction that still burns gas.
In practice your wallet does simulate execution during eth_estimateUserOperationGas and warns you the transfer will fail, but that is UX politeness, not a validity gate. A bundler is entirely free to include an op that is guaranteed to fail at execution. It gets paid either way.
The ERC-7562 sandbox: sealing the unpaid window
Now focus on the genuinely dangerous territory. To decide whether to accept an order, the bundler must run validateUserOp first, and this happens before anyone has paid anything. If validation reverts, who reimburses the CPU burned on that simulation? The initiator is a contract account: it signed no EOA transaction, touched no chain, left no trace. There is no one to bill. A failed validation is a pure loss with no recourse. This unpaid window before payment lands is the Achilles’ heel of the entire system, and ERC-7562 exists to seal it, along two fronts matching two kinds of attacker.
Front one: make free failure predictable
This targets the invalidation attack’s 4337 incarnation. A malicious account writes its validation as “pass only if external storage slot X equals 1.” At submission time X is 1; the bundler simulates, passes it, admits it to the mempool. The attacker then sends one cheap transaction flipping X to 0, and this op, along with every op that depends on X, goes invalid at once, wiping out the bundler’s prior work.
The countermeasure is 2938’s shackles reincarnated in userland. During validation an account may essentially only touch storage slots under its own address (the storage whitelist), so an op depends only on its own storage and nobody but the op itself can mutate state to invalidate it; the attacker’s lever vanishes. Alongside that, the environment opcodes TIMESTAMP, NUMBER, COINBASE, and BLOCKHASH are banned, so a result that holds this second cannot flip next block. Together these buy one property:
A single off-chain simulation by the bundler reliably predicts the op’s validation result on chain.
Do not undersell that sentence. The entire economic viability of 4337 rests on it. Without “simulation predicts inclusion,” no bundler dares package anything, for fear of simulating a pass, watching it revert on chain, and eating the fronted funds.
Front two: make the unaccountable accountable
The whitelist handles ordinary accounts, but shared infrastructure inherently needs to cross the line. A paymaster (a contract that sponsors users’ gas) must read its own storage during validation to answer “how much sponsorship quota does this user have left”; a factory (the contract that deploys new accounts) is in the same position. Ban them outright and these roles cannot exist; wave them through and an attacker writes a malicious paymaster, mutates its own global storage, and mass-invalidates every op that depends on it. The invalidation attack rides back in through the service entrance.
7562’s answer: you may access broader storage during validation, but first you stake ETH at the EntryPoint. The stake is not a purchased pass; it is collateral that can be punished. Repeatedly cause invalidations and bundlers throttle and blacklist you, and the staked ETH has an unbonding delay, so it cannot run away. Staking converts an anonymous, unreachable attacker into a named entity with something to lose.
The payment line: why the execution phase is lawless
Against all that, one fact looks bizarre at first sight: the execution phase, the part where callData does its actual work, is bound by none of 7562. Read any external state, use TIMESTAMP freely, revert at will. Why is validation locked down that hard while execution runs wild?
Because the money was already locked into escrow at the prefund moment. Whatever chaos the execution phase produces, even a total revert, gas is deducted from the locked deposit and the bundler is reimbursed in full. Execution failure is harmless to it, so no rules are needed. The dangerous thing was never execution; it is the free, unpaid window before payment lands. This is the master sentence of the whole design:
The entire security design of ERC-4337 is organized around one payment line. Before the line is the window where failure has no payer, guarded to the teeth by the sandbox; after the line is the safe zone where failure burns only the signer’s money, left almost entirely alone.
Understand where that line is and why, and every mechanism in 4337 becomes a corollary.
The line has a precise physical location on chain. Inside EntryPoint’s handleOps are two independent loops. Loop one, for every UserOp in the batch, does two things in order: call validateUserOp to verify the signature (post-quantum verification would live exactly here, inside the sandbox), then collect the prefund into escrow. When loop one finishes, everyone’s gas money is locked. Only then does loop two run, executing each op’s callData; the 1-versus-100 ETH shortfall surfaces here for the first time, and failure rolls back business effects only, gas still charged. Then settlement: actual usage is metered, surplus refunded to accounts, and the bundler is compensated from escrow for its fronted funds plus a tip. The payment line falls exactly between the two loops: loop one entirely before it, loop two entirely after.
While we’re here, record the off-chain half too, completing a UserOp’s full life. The user first estimates gas by simulating with a dummy signature of the same length and shape as the real one (Falcon’s signatures are variable-length, and constructing the worst-case dummy is a real engineering wrinkle in its own right), then signs the hash of the entire UserOp for real and submits it to the alt-mempool. The bundler traces the validation opcode by opcode with debug_traceCall, checking 7562 rules and dropping violators; runs an execution simulation to weed out guaranteed failures, which is pure courtesy, not admission; packs ops up to maxBundleGas; and submits with its own EOA signature and its own ETH fronted, calling handleOps. Through the entire off-chain phase, not one wei has been paid by anyone, and every risk sits on the bundler alone. That is why it simulates so carefully, and it is the justification behind how it prices preVerificationGas.
EIP-7702: a bridge for existing EOAs, with a welded-in backdoor
4337 accounts are freshly deployed contracts. What about the hundreds of millions of existing EOAs? Enter EIP-7702.
Recall the account tuple: an EOA’s code field holds an empty value, not nothing. What 7702 does, mechanically, is write 23 bytes into that empty slot. The user signs an authorization tuple with their ECDSA key (chain_id, delegation target address, nonce), ships it in a type-4 transaction, and once the protocol verifies the signature, it writes 0xef0100 ‖ target_address into the EOA’s code field. The 0xef prefix is carefully chosen: since EIP-3541, no normally deployed contract code may begin with 0xef, so these 23 bytes can never be confused with real bytecode. They are a pointer marker the protocol understands, not executable code.
From then on, any call landing on that EOA makes the EVM follow the pointer, load the target contract’s code, and run it in the EOA’s own context: address(this), storage, and balance are all the EOA’s. It is a delegatecall proxy installed at the protocol level; the EOA is the proxy, the delegation target is the implementation.
A few audit-facing details:
- a delegated EOA’s EXTCODESIZE returns 23;
- EXTCODECOPY copies those 23 bytes themselves, not the target code, yet calls execute through the pointer;
- the delegation can be repointed or cleared at any time with a fresh authorization.
Now the paragraph with the highest security content in this whole piece. The admin right of that proxy, the power to repoint it, belongs forever to the original ECDSA private key. You can set the implementation to the finest post-quantum verifier on earth and route all daily operations through quantum-safe signatures; but the moment a quantum attacker recovers the private key from the exposed public key, they sign a fresh authorization, repoint the delegation to a malicious contract, and your post-quantum shell evaporates. This backdoor cannot be fixed at the application layer, because it lives in the protocol’s own authorization mechanism; the fix has to come from the protocol, which is what the family of ECDSA-deactivation proposals is about (restricting or migrating the authorization power of legacy ECDSA; details still under debate). The earlier claim that “AA provides crypto-agility” needs one precise patch for 7702 accounts: the verification logic became agile, but the delegation power is still welded to ECDSA.
Aggregators: the last escape hatch, and one more slide
Back to handleOps loop one, where a multiplication problem directly relevant to post-quantum work is hiding. Loop one runs signature verification once per UserOp. In the ECDSA era one ecrecover cost 3000 gas and nobody cared; with post-quantum signatures at millions of gas each, a batch of N ops means millions times N, and loop one bursts. This is the second-order difficulty of putting PQ on 4337, on top of the unit price itself.
The escape hatch 4337 reserved is the aggregator. Some signature schemes (BLS natively, some lattice schemes under exploration) allow N signatures to be compressed into one aggregate signature whose single verification vouches for all N. The protocol interface is IAggregator: for ops tagged with an aggregator, EntryPoint stops verifying each one inside validateUserOp and instead calls the aggregator’s validateSignatures once for the whole group. The most expensive line item in the system flips from times N to divided by N.
But the trust root slides one more notch. Under per-op verification, each op’s validation is self-contained: the account verifies its own signature, dependent on no one. With an aggregator, verification becomes a collective judgment made by an external entity on behalf of the whole group, and the object of your trust shifts from “my own verification code” to “the aggregator aggregates honestly, and its validateSignatures has no bugs.” A malicious or buggy aggregator could let unsigned ops slip through inside the aggregate, an attack that simply cannot exist in the per-op model. The protocol’s way of pinning it down has no novelty whatsoever, and that is exactly the point: staking again. An aggregator is the same class of entity as a paymaster or factory (shared infrastructure that many ops depend on), so it gets the same weapon.
This route ends in a blank nobody has yet filled: the real economics of aggregation have never been systematically measured. EntryPoint does not meter the validateSignatures call separately, there is no clean mechanism to allocate the collective verification cost back to individual ops, and bundlers can only recoup it by padding each op’s preVerificationGas. How large N must be to break even, how much each op actually saves after amortization, whether bundler pricing covers the cost: all blank.
The full skeleton: a sliding trust root and two weapons
Let the whole article settle and what remains is one picture and two weapons. The picture first: the complete migration track of the trust root for identity and payment across nine years.
Every slide is a trade: hand over a piece of protocol-native cryptographic guarantee, receive a new capability, and take delivery of a new class of attack surface. And the protocol’s responses, over and over, are just two weapons:
- Shackle the validation segment. 2938’s three shackles reincarnate as the 7562 sandbox, storage whitelist plus banned environment opcodes, buying the property that simulation predicts inclusion and dismantling the invalidation attack’s lever.
- Collar the line-crossers with collateral. Paymaster, factory, aggregator: whoever wants privileges inside the unpaid window stakes first, turning anonymous attackers into named entities with something to seize.
One sentence for the whole skeleton:
The security history of account abstraction is the history of a trust root sliding again and again to buy capability, each slide birthing a class of attack surface, and the protocol patching each one with either shackles or collateral.
Where post-quantum signatures fit
Finally, back to the theme of this series. What does a whole article on account abstraction have to do with the quantum upgrade? Everything, because the map marks exactly where the pain lives.
Post-quantum verification resides in the validation segment, and the validation segment is the most tightly constrained narrow gate in the entire system: the hardest gas ceiling, storage access locked by whitelist, environment opcodes banned, and in batches, one verification per op inside loop one. A multi-million-gas PQ verification has to walk through precisely that gate. This is not coincidence but structure: all of the system’s fear of failure is piled up before the payment line, and signature verification happens to live on the before side.
The map also hands over several open questions, each growing out of a structure covered above:
- How do you construct the worst-case dummy for Falcon’s variable-length signatures so gas estimation doesn’t undershoot?
- The 7702 ECDSA backdoor is only fixable at the protocol layer, so what does the design space of ECDSA-deactivation proposals look like?
- The economics of aggregation have never been measured, and nobody knows the break-even N.
- EIP-8141, which dissolves the bundler by teaching the protocol to accept programmable-validity transactions natively, takes the simulation DoS the bundler used to carry back into the protocol’s own mempool: better, or harder?
Those are topics for later installments in this series; for now they are just pinned to the map.
Summary
Compress the whole piece back into one paragraph. Ethereum’s transaction system orbits one question: who pays for gas, and who guarantees they actually will. The old world answers with the crudest and sturdiest design available: the payer is hard-wired to be the signer, three static table lookups, settled before execution begins. Account abstraction turns “what counts as a valid transaction” into the account’s own code and unlocks everything: batching, sponsorship, swappable signature schemes, write whatever you want. The price is that “will this pay” degrades from a table lookup into “run arbitrary code and find out,” a free unpaid window opens, and denial of service grows out of the structure itself. Everything after that is patchwork. PAYGAS, and its reincarnation as the prefund transfer, drives a payment line through the middle of a single EVM run; the validation segment before the line is locked down by shackles and sandbox, buying “simulation predicts inclusion”; shared infrastructure that must cross the line posts stake for the privilege; the execution segment after the line is completely free because the money is already locked. 7702 bridges hundreds of millions of legacy EOAs into this world at the price of an admin key welded forever to ECDSA; aggregators turn the most expensive verification from times N into divided by N at the price of one more notch of trust-root slide. And every difficulty of post-quantum signatures on Ethereum traces back to a single fact: they happen to move into the most heavily defended room on this map.
The next article in this series follows the map forward to native account abstraction, where EIP-8141 dissolves the bundler into the protocol itself, and takes some old problems back in along with it.