EIP-7702: Set EOA account code for one transaction

If the bytecode contained in a 7702 operation is deployed unconditionally, then any old signature can be re-included in a later 7702 transaction to have the bytecode deployed. This causes problems for ERC-1155 token (and similar) transfers because the bytecode might not implement onERC1155Received.

Ah so you are proposing that we include initcode but not do any of the revocation proposals such as maxNonce right? Since any of the revocation proposals would address this issue.

I was confused since @yoavw seemed to want to include both maxNonce and initcode, but maybe I misunderstood.

This is incorrect. The nonce is in the sig hash. You can’t compute the sender unless you know the nonce. Therefore to implement the max nonce scheme, you will marginally increase tx size.

Yes this is true. I don’t know how people are using this invariant, but I feel a bit hesitant to allow bumping where the cost to bump doesn’t increase linearly with size of the bump.

Besides that, we limited the nonce size in EIP-2681 and I don’t think we should remove it. Many clients are written with this assumption by using uint64 to store nonce values.

If it isn’t then I’ll answer seriously. I think you’re proposing for accounts to have two identity systems, so that each account will effectively be a 1-of-2 multisig. The user has to maintain keys for both, and a compromise of one can compromise the account. That seems to degrade security rather than improving it, which I don’t think can be said for maxNonce in 7702.

If you want to propose an improvement for 4337, preferably one that doesn’t break the existing 4.3M deployed accounts (any more than maxNonce breaks existing EOAs), please do it in the 4337 thread or a separate one and we can discuss it there. No point in hijacking the 7702 thread for 4337 discussions, as people already complain that it’s hard to follow the current thread.

Adding a security feature at zero cost is different from adding it at the cost of degrading functionality/security. I don’t see use cases that become impossible or even different with maxNonce, and it doesn’t degrade account security, but strictly improves it.

Anyway, I shouldn’t be the only one arguing for it. Let’s wait for all core devs and for the community to bring their perspective. This issue does not interact with the AA roadmap and if everyone is ok with not having protocol level revocation, I’m not going to be the only one to insist.

I think we agrees on the pros and the cons of maxNonce vs. not having protocol level revocation, just not on what the right trade off is. At this point we should let the community decide on the right trade off.

That’s a good question. From 4337 perspective they’re equivalent - the bundle will revert whether it happens at the transaction level or inside EntryPoint when it fails to call the account’s validateUserOp. So DoS resistance depends on revocation being expensive. For non-4337 use cases it might matter, so let’s wait for feedback from some. Going with the latter seems fine as a default.

I also think it should be an expensive operation but I’m curious why you think it should increase linearly rather than a fixed cost that is significantly higher than the cost of invalidating 63 pending transactions (or a 4337 bundle for that matter). Can it cause more work than invalidating the max number of pending transactions per EOA?

1 Like

No, initcode based revocation and maxNonce serve different purposes and I’m not suggesting that one should remove the other. The former is a way for the contract to implement arbitrary revocation logic, which can mitigate the attacks that @SamWilsn and @taek.eth suggested. The latter is a way for the user to revoke access unilaterally even if the contract turned out to be buggy or malicious.

In my opinion both as valuable independently, but @matt would argue that implementing revocation in the contract itself is sufficient. At this point it would be valuable to get feedback on this from you and other community members on whether we need both.

Revocation logic is extremely simple. Are we really this pessimistic about our ability to implement it correctly? This community has done harder things.

We should be able to implement a simple revocation contract and include a call to check revocation status as a prelude in EIP-7702 signed code, in a standard way that is easily statically checkable.


It would be good to hear if there is consensus about switching to initcode. It seems necessary to protect against attacks like the one on ERC-1155 transfers.

1 Like

Yes, technically, I suggested to introduce a separate “revocation nonce” managed by each account.
Since EIP-2681 limited the nonce to 64-bit, it made the upper 192 bits “reserved” and currently must be zero.
My suggestion is to reuse these as “revocation nonce”, used only by 7702 signature.
It does require a separate “bump revocation nonce” mechanism.
The one scenario this model can’t handle (and “maxNonce” can) is “maxNonce = nonce+1” - that is, a signature that is valid for exactly one transaction: This is a scenario where the user signs each transaction twice (once as account signer, once as transaction signer (and payer)), but is guaranteed the signature can’t be replayed (this mode also prevents using gas abstraction)

I’m not pessimistic about our ability to implement it correctly, but I’m very pessimistic about everyone implementing it correctly (or using the same implementation). As a security researchers I’ve seen some really bad contracts on mainnet. Doing it at the protocol level ensures that users will be able to revoke buggy implementations. I wouldn’t suggest it if it broke important use cases but it seems not to break any.

The case I have in mind is when you start with a wallet that wasn’t as diligent as you’d like when writing its contract. When you accumulated assets and became more savvy, you realized it and decided to switch to a better wallet. But if the old one didn’t implement its own revocation (whether for lack of knowledge or in order to be “sticky”), you’re stuck with your old wallet forever.

On the other hand, as Matt pointed out, the same is true if you select a bad contract wallet, so the difference is that with 7702 it is possible to have a recovery mechanism “for free” whereas with a contract wallet it’s not.

Maybe the community decides that it’s not needed and everyone will use good implementations. If everyone is ok with that, I’m not going to be the only one to insist otherwise.

I don’t know. Maybe @SamWilsn can comment on where we stand with initcode.

Yes, any 7702 contract can implement revocation very simply. That’s true. The problem is when a user signs such a contract that doesn’t implement a revocation mechanism: In that case, that contract is authorized to be used on this account forever, without being able to revoke.
e.g: assume a cool 7702-contract that supports some specific feature, but has a buggy revocation.
Now there is a signature “out there” that can prevent you from receiving tokens, if you use decentralized relayer (e.g 4337): Any time you submit a transaction, they will front-run it, and re-relay it - with the above signed code, which will cause the TX to run but revert on the token callback.
Yes, you could use private mempool/relayer, or send a direct TX (and paying with ETH) - but this break the UX for many users, who are not sophisticated enough.

I am against any operation like bumpNonce or similar. The yellow paper defines nonce as equal to the transaction count. Arbitrary jumps of the nonce would break this definition. Further would it break eth_getTransactionCount as this also just return the nonce, we have no other way of tracking the number of transactions currently. eth_getTransactionCount is used by many (all?) libraries and wallets to get the next nonce when creating a transaction. We would need another RPC endpoint (eth_getNonce ?) and all libraries and wallets would need to switch to that. That is a huge hassle and would surely break many apps.

All this can be avoided by using the same mechanism as 3074: signing with the current nonce and revoking by increasing it by 1. This keeps all the current definitions and behavior.

1 Like

It’s not true for either case. You can always transfer your assets over to a new wallet. I think you’re aware of this but it’s worth making explicit for others.

It’s also worth repeating that this problem is not exclusive to EIP-7702 or EIP-3074. The same can happen if you choose a wallet and later realize it used bad randomness or that the private key was leaked.

We mitigate the risk for private keys by building excellent libraries that implement the tricky parts, and complement it with a culture of caution and skepticism. We can always improve on both fronts but they’re concerns of the application and social layers, not the protocol layer.

1 Like

Would this be the first and only time an EIP breaks something in the yellow paper? Ethereum evolved a lot since.

Easily fixable by masking the nonce in eth_getTransactionCount and making bumpNonce only increment the high bits. I.e. if we use 32 bits then the transaction count remains accurate for the first 4 billion transactions.

I think this would break a lot of use cases and invalidate signatures needlessly, whereas maxNonce doesn’t.

But again - this is just one possible implementation. Maybe there are better ones, or maybe the community decides it doesn’t need any. You’re welcome to propose others and we can compare them (or make a case for why we don’t need one).

I disagree on this one. Not everything is transferable and it’s indeed worth making it explicit for others. A couple of examples:

  • Soulbound tokens
  • Airdrops eligibility - if someone has control of your account, they can snatch your future airdrops.
  • Vesting contracts - the tokens are locked, the attacker grabs them as soon as they vest.
  • Validator withdrawal address - you can’t change it without exiting the validator and withdrawing your 32 ETH, at which point it gets snatched before you restake it. If you don’t exit, your rewards get snatched regularly so you’re operating your validator for someone else’s benefit.
  • Governance delegation - you’re a delegate at the governance of a popular protocol, with thousands of users delegating to you. Now you need to ask them to immediately redelegate to a different address. Good luck getting all of them to act in a timely manner.

Do you disagree that these cases are effectively non-transferable or that they represent valuable assets?

Yes, I agree and I quoted Matt on this earlier. The difference is that here we can protect the use cases above “for free”, whereas with an EOA with bad randomless or with a not-so-smart smart account it isn’t the case. Should we not protect users when we can (at no cost) because there are other cases where we can’t?

Unrelated to 7702, but the lines actually get blurry in some cases. Consider validators withdrawal address for example. Suppose a liquid staking protocol holds 35% of the staking power and its withdrawal contract is compromised. The attacker can extort them to support a certain protocolchange. Is it an application/social layer or could it affect the consensus layer? But hopefully no one will use a 7702 contract for that, so we can debate this philosophical question elsewhere.

1 Like

No, I agree with that, though I think we should ask if these are good application-layer design decisions, or good user choices! For example, for soulbound tokens Vitalik has discussed the problems with non-transferability, and suggested binding them to ENS names rather than addresses.

If we had a way to add native revocability for free I’d be supportive of it, but all the solutions seem to have some cost, either in complexity to the protocol or in killing use cases people are interested in.

From an app layer perspective I’m not against maxNonce, but protocolwise I hear concerns about it.

Soulbound tokens - right, better bind them to ENS. But I’m not sure how the user could be making better choices in most of the other cases. There’s no way to change validator withdrawal address, change ownership of a vesting contract, change airdrop eligibility address (except rare cases), etc.

One could argue that some of these things should be bound to ENS but in practice they’re not and they’re outside the user’s control.

The trade offs around maxNonce have been discussed above. I think it’s practically free and doesn’t break any use case. But I acknowledge that it’s an additional thing the protocol needs to implement. These pros and cons should be weighed by the community against protecting use cases such as the above.

Would it be the first time something has changed, no. Would it be the first time we redefine basic terminology, I think so. At least I can’t think of any other example.

Changing RPC responses would be a first AFAIK. At least we have no concept of them changing with hardforks. We can’t know how every library is parsing the JSON hex strings so this still could lead to bugs and unintended side effects. Changing RPC responses is too dangerous in my opinion.

I understand and this unfortunate, but as I understand the motivation section of the EIP the goal is to add short-term functionality improvements to EOA. This is still given. If the trade-off is between more functionality and being more secure because of a simple in protocol revocation I opt for the latter. In-protocol revocation was a must for 3047 for many client teams, I think this has not changed, but I might be wrong here.

We had this discussion for 4 years for 3047. Given the tight timeline I have my doubts that we will come up with something better considering that the first devnet is already spinning up this week. I rather have 7702 with limited, but still sufficient functionality than having nothing. A lot of this discussion here seems to be focused on adding features which is not the scope of the EIP IMO. I prefer to keep it simple and make sure to ship it in the next hardfork.

Looking through the commit history of the yellow paper (yes, it’s a live document and is getting updated frequently), I can dig a few. For example we removed the term “mining” changed changed the way block production works, and we changed the definition of gas with EIP-1559 and introduced priority fee. I’m not sure what term we are changing by allowing nonce to be bumped but it seems like a smaller change than some others.

I didn’t say the returned JSON would be changed. The implementation could change to mask the high bits out in the node itself. The change is not observable by users, except those who sent more than 4 billion transactions from their EOA. Do you think there is one?

If being the operative word. I think we’ve established that it doesn’t disable any functionality.

I don’t think it has changed and I don’t see why it would. Hence I proposed a mechanism that satisfies that requirement without breaking any use case. But if they did change their minds and it’s not needed, that’s fine too.

The first devnet won’t include any version of 7702 so we have time to come up with better options.

I agree, I just don’t see the limit on functionality.

The protocol also don’t allow a single address to have constantly changing code that may be approved my multiple wallets.

If you want to talk smart contract wallets, they often include a modular (safe) or upgradeable (argent) design. Unlike a non revocable 7702, a buggy module can be removed. With a non revocable 7702, a buggy module can last forever, and stay unnoticed (unused) for years before it’s exploited. It like not being able to upgrade your install of metamask after a bug is discovered.

7702 is enabling new workflows, far from anything that exists today (and that is what we want). Assuming the the existing security models cover these new workflows is absurde if you ask me. We have to ask ourself what new attack vectors may result from this new transaction type … and eventually include safety measures that did not exist in the past.

Code executing in your account with sudo permission is not the same as a plugin, so this comparison is moot. A better question is how can you revoke a smart account if the plugin system itself has a bug? The accepted answer is you should have deployed a proxy to your account and must now change the target. If you deploy smart account code into your account (e.g. run it with sudo permission) and it has a bug, there is no mechanism to revoke the code. We accept this as safe in that context, I don’t see a reason to not accept it for EOAs.

Yes 7702 introduces the ability to run different codes in your account. I think that’s okay and maybe their will be some interesting use cases from it. The best way to use it will still be to sign one 7702 message forever which delegates to a proxy contract that executes on your behalf. Then you can change the target at will. Revocation ends up looking the same as smart accounts today: just change the target.

For reference, the EIP-7702 is very similar to EIP-2803: Rich Transactions.

1 Like

Just flagging early that EIP-7702 could pose an interesting alternative to the EOF creation transactions EIP-7698, as well as the previous designs with TXCREATE and new InitcodeTransaction type. It seems however to require the “sign over contract_code” version to function as such.

2 Likes