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.
Now that the merge is complete, is now the right time to discuss how to handle tx.origin? The range of possibilities:
Alias to msg.sender
Set as “first actor”
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.
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?
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).
I have a quick question about AUTHCALL not resetting AUTH. From a security point of view, I would argue that it should reset AUTH; the reason is that now it opens ways for “attacks” similar as to re-entrancy attacks. (First AUTHCALL into some contract, do some nasty stuff over there to trick the calling contract to invoke another AUTHCALL when it returns from the authcall frame later…?)
You should only be signing AUTH messages to contracts (invokers) you explicitly trust to have 100% control of all of your funds. The idea here is that you can delegate control of your EOA to a contract, and you shouldn’t be AUTHing some random contract. Wallets should be incredibly restrictive about allowing this opcode, likely only allowing it for their own contract wallet or community trusted ones.
I think you mean to say you should only signing AUTH-messages to contracts (invokers) which you trust 100%. Once the AUTHCALL happens, it’s just the standard security model of interacting with smart contracts - e.g. they have control over only what you explicitly give them.
It could even effectively deprecate ERC20 approves/allowances usage especially if AuthContract will be additionally introduced compatible with EIP-1271.
Wish we had unification among EOA and smart contract based authentications (aka signatures)
I have a question about calculating the dynamic gas portion (dynamic_gas ), and the gas available for execution in the sub call (subcall_gas ) for AUTHCALL.
According to the provided pseudo-codes, if the value is not zero, the cost of gas should add 6700, it is not 9000 because no gas stipend even if the value is not zero. The value added to gas is CallValueTransferGas - CallStipend, which means stipend gas is included in 9000 value transfer gas.
if value > 0:
dynamic_gas += 6700 # NB: Not 9000, like in `CALL`
But in go-ethereum, the stipend gas is not included in 9000.
When executing the opcode CALL, if value is not 0, firstly add 9000 gas in function gasCall() :
Then value will be checked in function opCall(), if it is not 0, 2300 stipend gas will be added :
Therefore, I think the dynamic_gas should add 9000 not 6700 in provided pseudo-codes.
It’s been a long while and I’m not extremely familiar with geth, but I think the first link (core/vm/gas_table.go) is where the gas is deducted from the parent, and the second link (core/vm/instructions.go) calculates how much is available to the child.