EIP-3074: AUTH and AUTHCALL opcodes

Put up a change to auth to make it more future compatible. Comments welcome!

Had an interesting discussion today where it might be theoretically possible to create a single “meta-invoker” that enforces replay protection/revocable signatures/etc, which then delegate calls into app-specific “sub-invokers”. If that ends up being possible, the whitelisting process would only need to approve the one “meta-invoker” to cover 90% of use cases.

This is now merged. Instead of pushing everything on the stack, auth now takes an address and a memory range.

Currently the memory range contains the commit and signature, and the address from the stack must match the address recovered from the signature.

In the future, should EIP-3074 get merged, auth can be modified to work with EOAs and smart contract wallets!

In the current EVM, tx.origin is the account from which the transaction originates (logically, the account “doing the thing”) as well as the account that pays the gas to execute the tx. They are synonymous.

With EIP-3074, this is no longer the case; these identities get split up: The logical originating account account “doing the thing” is the AUTH’d account, but the account paying the gas to initiate the transaction is the account making the AUTHCALL to remote control the AUTH’d account into “doing the thing”.

I think (correct me if I’m wrong), your implementation leaves tx.origin as the account paying the gas where I believe tx.origin should be the AUTH’d account performing, in spirit, the action.

I’m not sure if this is the proper venue to discuss, but what are your (and others’) thoughts on which account should be tx.origin… the account paying the gas or the account logically originating the action/“doing the thing”?

Edit: Way out of my depth, but what about a new “tx.gaspayer” identity to distinguish the two?

That is correct.

I’m mostly indifferent to what tx.origin represents. I think there are arguments for it to be the “gas payer”, or the “first actor”, or just an alias to msg.sender. If we set tx.origin to authorized when AUTHCALLing, it would certainly make it harder to distinguish smart contract wallets from EOAs, which I suppose I support.

2 Likes

On balance, what msg.sender/tx.origin/gaspayer scheme do you (or anyone reading this) think makes the most sense in a post-merge, 3074-adopted world?

I’d love to see tx.origin aliased to msg.sender, personally. Elegant. Simple. Not sure how realistic.

Seems like there are two implementations of this so far: quilt go-ethereum (adeitrichs) and ethereumjs (brouwer)

Are we aware of any others? Is there some common location (aside from this thread) for implementation writers to report their work on 3074?

Can my suggestion for setting tx.origin to the account being remote-controlled, not the gas payer, be added to the EIP?

I’m worried that adding an even more controversial change to the EIP will make it even less likely to be adopted.

Once the merge is complete, I think we can reopen the discussion with core devs and see where we stand?

But it sounds like such a change is not controversial; more that nobody really seems to have an opinion one way or the other.

In this way, it’s more controversial not to make the change, isn’t it? Because I do have a very strong opinion on it.

Once the merge is complete, I think we can reopen the discussion with core devs and see where we stand?

Yes, I plan to voice my concern with the core devs group whenever it’s appropriate. Thanks for the reply.

From a perspective of secure contract design, I think having a shortcut for “remove authorisation” (= clear authorisation context) would be a nice addition, even at the expense of an extra opcode.

This can be current accomplished by sending “rubbish” to the AUTH instruction. Would auth(0, 0, 0) do the trick? The spec seems lenient at invalid input lengths.

Since the data in memory is not “versioned” or distinguished in any manner I do not think this achieves the goal of extensibility. I did found the old stack version much more “user friendly” too.

Yes, I believe so:

If the signature is instead invalid or the signer address does not equal authority, authorized is reset to an unset value.

In a future extension of AUTH, the interpretation of the memory range would be defined by authority (from the stack.) If there is code at that address, that code could parse the signature and decide to authorize or not. This is somewhat similar to EIP-1271’s isValidSignature. If no code exists at authority, the behaviour would be the same as 3074 currently specifies.

Me too, honestly.

I’d second this :+1:

@SamWilsn and authors, great EIP. I wonder if this could make Shanghai

1 Like

Now that the merge is complete, is now the right time to discuss how to handle tx.origin? The range of possibilities:

  1. Alias to msg.sender
  2. Set as “first actor”
  3. Leave as “gas payer”

I’d love to see option 1 (what are the implications of going this route?), but between choices 2 and 3, I think you are correct that option 2 gets us closer to true account abstraction and is, also, what I’m advocating for.

(I’ve joined the wallet dev discord and we can discuss there, if you like.)

Just to restate my opinion which I suspect is somewhere above: tx.origin referring to the signer/account that initiated the transaction makes the most semantic sense with “transaction origin”. However, this has caused much trouble because people use it to block “contracts” from interacting with dapps which includes contract wallets, and eventually could include account abstraction. I would feel no remorse breaking apps that have depended on this behavior as we have been advising people against this for a long time.

Respectfully but emphatically disagree.

3074 introduces something “new” - a rearward step in the transaction chain - and then moves something “old” (tx.origin) to this new first frame that never existed before. That really flies in the face of immutability and should be avoided at all costs, IMO.

I agree that we shouldn’t feel much remorse for those who were advised against tx.origin and then proceeded to use it anyway. But that’s not my case. The opposite, in fact. In 2015, I asked Chris Reitweissner about refunding the transaction sender and was told about tx.origin.

While he didn’t outright advise using tx.origin, you can see that it was floated as valid when I was previously on the msg.sender bandwagon. (The first two versions of Etheria correctly use msg.sender to determine token ownership. The last two use tx.origin, unfortunately, based on this gitter exchange.)

The danger of tx.origin wasn’t widely known until 9 months later in mid-2016 as shown here:

Indeed, leaving the EIP-3074 spec with tx.origin as the gaspayer would open a potential vulnerability: If a tile holder sponsors a transaction, any other frame in the transaction chain would have the ability to hijack these tokens. (Of course, we can advise owners to never use such an account to sponsor transactions, but I shouldn’t have to teach this nor can I ever guarantee reaching owners with this warning/education.)

If we are otherwise fine with breaking tx.origin because “people should have known better since mid-2016”, then we can both get what we want by simply aliasing tx.origin to msg.sender and being done with it. Solves my problem and eliminates tx.origin issues forever. (That said, I don’t have the expertise to know how impactful this would be throughout the entire ecosystem.)

Failing this abrupt aliasing, I strongly insist that “tx.origin” means, in spirit, “the first actor” and should remain that way for immutability reasons. This new initial frame created by 3074 should then be called “tx.gaspayer” to appropriately add a new label to a new frame and leave the old label with the old frame. Doesn’t this move us to a more complete “account abstraction” anyway?

Before thinking of altering the behaviour of tx.origin, please think about its current nice side-effect: it allows creating view-only transactions.
Assume that I want to simulate a specific code portion (either just to find if it runs at all, or some side-effects, such as creating contracts and query some data from them) - but WITHOUT really letting the user run this code on-chain.
The simple way we can use right now is to add require(tx.origin == address(0)) to the code (and document that this method has to be called only with “eth_call({from:'0x0' })

Do you know of any other way of forcing a code to run in view-mode only?

You’d need to somehow generate a valid signature from the zero address for that to be a problem. If someone can do that, we have bigger issues :wink:

Creating a fake tx using an extremely high gas price or gas limit would most likely work (I do not think eth_call checks those things). And then thus check require(gasleft() > HIGH_VALUE) or require(tx.gasprice > HIGH_VALUE).