EIP-3074: AUTH and AUTHCALL opcodes

New version published: https://github.com/ethereum/EIPs/blob/master/EIPS/eip-3074.md

Highlights:

And most importantly:

  • Only sign over type, invoker, chainid, and extra (@adietrichs)

Thanks to everyone who has reviewed so far, and please don’t hesitate to let me know if I’ve missed some of your concerns (or made something else worse!)

1 Like

I have spent today writing a rough first implementation in geth. It is not yet fully compliant with the EIP, I will list the differences & some small questions that came up during implementation tomorrow.

Link to the implementation (of course very much WIP, but I am currently able to successfully execute contracts that use CALLFROM on my little local testnet):

1 Like

New version published (again, sorry)

This time:

  • Removed type from the stack arguments since it’s a constant anyway (@adietrichs)
  • Added a depthLeft argument since exhausting the call stack is a griefing vector (me)
  • Re-added the balance check to the pre-conditions (me)
  • Expanded the invoker security considerations (me)

Before talking about my implementation details, there is one last somewhat substantial change that I have been thinking about, namely the separation of the signature verification and the call. My motivation:

  • The opcode doing both seems overloaded, with 13 arguments right now
  • It is non-trivial to implement gas pricing that only charges for the (outer part of the) call portion if the signature is correct
  • In its combined form there is no native support for multiple calls from the same sponsee - they each require an additional signature verification overhead
  • Passing in the sponsee address, while a sensible gas saving, feels like a bit of a hack
  • The opcode returning two stack elements is unusual

My alternative idea would be:

  • AUTHORIZE
    • takes in: extra, v, r, s
    • returns: sponsee or 0
    • validiates the signature
    • sets a new context variable authorizedSponsee
  • AUTHORIZEDCALL
    • takes in: gas, addr, value, in_offset, in_length, ret_offset, ret_length
    • returns: success
    • throws if authorizedSponsee is 0
    • otherwise, behaves exactly like CALL, just with msg.sender set to authorizedSponsee

This has several advantages:

  • Native bundled calls from the same sponsee (one AUTHORIZE, any number of AUTHORIZEDCALL)
  • Cleaner opcodes, AUTHORIZECALL almost identical to CALL
  • Sensible way to recover sponsee address without need to pass it in from the outside
  • It allows for clear separation of invoker logic, where calls are only attempted in the case of a valid signature

Note that extra can be used to commit to a specific list of transactions, so no functionality is lost.

1 Like

This would effectively be saying, “this caller can do whatever it wants from my account” right? I’m assuming the authorization would only be valid for the call frame where AUTHORIZE was called? Presumably, any user doing this would essentially fully trust the contract it is giving permission to?

From a cleanliness and future capabilities perspective, I really like the idea. However, it means that signing tools will have significantly less information available to present the user with as to what exactly they are signing. From the signing tool’s perspective, the user is just giving blanket power to a particular address.


Note: The signature would need to be over invoker.

I think that is a correct summary. Regarding the implications, I think it is important to distinguish between the two main use cases I see for this EIP:

  • In a trusted context (for example for significantly reducing gas usage of centralized exchanges, or for users controlling multiple addresses from a smart contract wallet and/or without the need for ETH in every account), this invoker trust assumption is not an issue, as the sponsor and the sponsee are the same person. I would expect a multitude of implementations, that all only have to pass the usual smart contract security scrutiny.

  • In a trustless context (mainly transaction relaying), trust into the specific invoker implementation is important. However, I think this has already been true for all of the latest revisions of the EIP, once the signed fields became minimal. I would not expect wallets to expose functionality to sign over arbitrary invokers (or if they do, word it in a “delegate full account control to contract” way). Instead / in addtion, there can be a handful of community vetted ERCs for different flavors of relayer systems, with wallets implementing more precise native support for the specific extra hash used by the ERC. So for those ERC invokers, the wallet would not ask for full account control delegation, but instead would display the specific transaction (bundle) to be authorized, with specific information like expiry and other functionality.

Correct. Discussing this with @SamWilsn and @matt we felt like this authorizedSponsee should also not be passed along for delegate calls, to enforce all authorization-related execution to be in one place.

Correct, the signed data would remain identical to the current EIP. No need for the invoker address to be an opcode argument to AUTHORIZE though, because it would be the address of the currently executing contract.

Overview of the proposal with specs explained in PEEPanEIP with @SamWilsn

2 Likes

I think that require(tx.origin != signerAddress) should be removed. Without this limitation, this EIP provides significantly more value because it would allow people to do the “approve and call” flow with ERC20 tokens in a single transaction. In such cases, they are paying for their own gas and they shouldn’t have to go out and find a relayer and pay them extra or fund/use a second account just to get around this limitation.

Also, I don’t see any meaningful value in enshrining the require(tx.origin == signerAddress) behavior because it was already a broken protection that can be bypassed today.

I think the value for the call should be deducted from the signer’s account, rather than the invoker’s account. This would allow users to use this functionality to do transaction batching (i.e., execute the following series of operations in sequence) where one or more of the transactions they want to batch attaches ETH.

If there is fear that such a change would delay the launch of this EIP, then I propose we launch with require(value == 0) and then after a more careful evaluation we can remove that constraint and deduct from the signer.

Hi @jpitts, could we have this thread renamed to “EIP-3074: AUTH and AUTHCALL opcodes” to match the latest EIP title?

From shemnon on GitHub:

I would want to see how this interacts with account abstraction before I would be comfortable moving forward with it. I concur with alexey and martin in that it is too early in the design to freeze it for implementation, which moves it out of London.

Pretty much the whole reason I started investigating this EIP was because EIP-2711 is not compatible with account abstraction. There’s an (admittedly small) section on compatibility in EIP-3074.

In a bit more detail:

With mempool rules, we ban using AUTHCALL before PAYGAS. AUTH should be fine, as far as I can tell. After PAYGAS, an account abstraction contract behaves the same as a regular contract, and would be able to AUTHCALL to its heart’s content.

There are certainly some concerns with creating an ungriefable abstract account sponsor. The AA sponsor wouldn’t be able to rely on external contracts for swapping, or token balances, or pretty much anything, but that’s no different than AA without EIP-3074.

That said, I don’t see anything that would preclude an AA contract that receives ETH or WETH deposits, and then lets you use that to pay for transactions from your EOA.

From Alexey on Discord:

Sponsored Transaction Precompile - not to include to London - is there a way to do it without precompile (these are ugly things and should not be regarded as normal artefacts of EVM development), also consider who exactly will need it and what are the alternatives.

We’ve switched off the precompile (now two opcodes.)

As for interested parties, we have several! @stonecoldpat (who has been active on this thread) is from Infura Transactions/any.sender. I’ve had a bit of contact with OpenZeppelin about this EIP as well, though no strong endorsement (yet!)

The alternatives are:

Natively Set msg.sender

Avoid msg.sender Entirely

I posted a thread on twitter giving a high-level overview of how this EIP will be used in practice: https://twitter.com/lightclients/status/1371911245561917441?s=20

1 Like

No problem, I’ll update it now…

1 Like

I’ve written a document to discuss some different threat models EIP-3074 should be considered under and how they compare to EOAs and smart contract wallets today: https://hackmd.io/@matt/BknnAnyNu

EIP-191 specifies a safe format for signed messages, which already contains a subformat for messages of the type (ID || validator || payload). Could you use that rather than reinventing the wheel?

Per @timbeiko’s request, here’s a link to a twitter thread I’ve written on this EIP.

Probably my most important comments from the thread are:

Okay, so top question this is going to provoke, especially for “should we commit to shipping this tomorrow?”: IS IT SECURE? I believe the answer is YES, and for a simple reason:
EIP-3074 signatures always have a 0x3 prefix, which currently no Ethereum wallet will sign (at least without a warning that you may be signing everything away). For this simple reason, we can already safely say “no user can become vulnerable to this without accepting the risk.”

Also:
Fun challenge: What other features could you implement for any account with this pattern? While these invokers all derive their power from a signature, they can have state, and are replay-strategy agnostic! You could have a cold wallet that delegates to an m-of-n multisig!

191 is just a draft while 2918 is in review and will almost certainly be included in Berlin (brining it to final). Also (probably a better discussion for 191 discussion), it is unclear to me why 191 is so prescriptive about the layout of the bytes after the version, and why the first byte is wasted on always being 0x19 instead of just having the first byte be a version (constrained to not compatible with legacy transactions).

To be fair EIP-191 has been in production for about 5 years now. I think EIP-191 is a more proper standard to use for non-tx signing and commonly supported by most wallets. We can introduce a version explicitly for this EIP so there is no change of signature collision and wallets must opt-in support. I don’t really see a downside to switching to EIP-191.

Assuming the authors of 191 decide to move forward with the standardization process, what benefits does it provide over 2718 transaction types?