EIP-3074: AUTH and AUTHCALL opcodes

Follow the development update, Puxi testnet progress & a demo with other useful discussions from the EIP-3074 community call #2

5 Likes

I’m sorry if these questions were asked already, but:

  1. Why isn’t chainid part of the signed data for AUTH? i.e. keccak256(MAGIC || paddedChainId, paddedInvokerAddress || commit). yParity seems to be restricted to 0/1, so it is not utilising EIP-155-style chainid inclusion.

  2. Is there a strong reason to keep retOffset/retLength in AUTHCALL? If the only reason is “being similar to other CALL*s”, then I would suggest to not have. If the reason is that contract developers would prefer using that as opposed to returndatacopy, then of course it makes a lot of sense to keep it.

  3. Why is AUTH/AUTHCALL separated? Is it motivated by a) simplicity; b) allowing multiple AUTHCALLs; c) having the ability to check if a signature is valid without executing a call?

I believe the answer is mostly b) (and c) comes as a benefit), but it could be much better explained.

1 Like

If the authorized contract wants to check chain ID, it can. It is not up to the EVM to do all possible validations, and anywhere that validation can be delegated to the contract it is.

Is retOffset/retLength just a legacy holdover from before returndatacopy was introduced? If so, then I think we should remove them.

There are a number of use cases where the signature may not sign over the calldata. A few examples:

  1. one auth, multiple calls (approve and transfer)
  2. permanent auth (one signature that gives contract 100% perpetual control)
  3. one auth with multiple code paths that lead to different calls
1 Like

We have come to be very excited about this EIP over at statechannels.org as it allows for a novel contract architecture we might call synthetic-EOA-per-channel. We believe this is likely to be the most elegant and efficient architecture for state channels, offering a large reduction in blockchain state usage as well as an improved user experience.

The EIP authors introduced us to the idea of a “synthetic, externally-owned” account. It is somewhat like a contract account in that the associated private key is not known or used, but very different to a contract account in that it cannot have any code or storage. The address of the account is found by recovering the public key from a fixed message digest. For example, using an EIP-3074 digest:

digest = keccak256(MAGIC || padTo32Bytes(Invoker) || padTo32Bytes(0))

and a “synthetic signature”, constructed without the use of a private key*. Using the EIP-3074 digest hands complete control of the synthetic EOA to the Invoker, as explained in the EIP.

State channels may then be executed by participants sending funds directly to a synthetic EOA of their choice — one derived from the channelId, for example (which is roughly speaking the hash of the participants’ accounts). The Invoker can then release funds from the synthetic EOA conditional on all of the usual checks defined by the state channel protocol (e.g. the channelId itself, signatures on off-chain states, etc).

This avoids the need to i) have an additional “layer 2 accounting system” for state channels (i.e. no need to use contract storage to record which channel has how many tokens because the ethereum accounting system suffices), or ii) deploy a contract per channel using CREATE2 (which is expensive and will shortly become more so as SELFDESTRUCT refunds are removed) and also avoids the need to iii) approve and transferFrom tokens into the state channel in two separate transactions.

The bottom line from a state channels point of view is: improved state hygiene by avoiding writes to storage and/or code deposits and improved UX. Both imply increased gas efficiency. Projects like Connext are executing a high volume of state channels in production, making these improvements all the more significant.

*We would like a deterministic, reliable algorithm for this: it seems somewhat nontrivial and worthy of some more research.

4 Likes

I wanted to bubble up an interesting idea from Jeff Coleman on Twitter:

To flesh out the idea just a bit more, we could write an invoker that takes the normal transaction inputs, but replace to: Address with to: ENS. This way, the signature of the AUTH msg would be over MAGIC || INVOKER || hash([nonce, to_ens, gas_limit, gas_price, value, data]).

Doing this binds the msg to the to_ens handle and allows the hardware wallet to actually show to: someperson.eth rather than 0x12..34. The invoker can then trustlessly resolve the Ethereum address associated with the ENS domain during execution and route the rest of the call to that address.

Resolving the ENS name on-chain is the only way to trustlessly interact with ENS. Even if you run your own node, there is nothing binding standard transactions to a certain ENS domain. If you secure your assets with the hardware wallet (instead of with a smart contract wallet), you won’t be able to interact with ENS address trustlessly. You’ll always need to verify the Ethereum address on the device against what the ENS name should resolve to in order to be certain no part of the stack was compromised.

1 Like

Dropping a link to a follow-up post:

Am I right, that making one wrong signature would allow attacker to drain whole wallet: all tokens and ETH from the EOA balance?

EIP-3074 cannot move ETH from EOA balances, but technically yes. However that’s only half the story. To even get to that point, your wallet would have to allow you to create a bad signature. We trust our wallets to not do this every time we send a transaction, why think about EIP-3074 differently?

@matt but you can’t spend all your ERC20 balances within 1 transaction now.

Not today, but tx batching will come. EIP-3074 will expedite it, but it’s coming regardless, and at that point the risk profiles will be the same.

A possibility to ease security concerns is to add an AUTHRELEASE opcode. This opcode could be called by any contract that had previously received an AUTH from an EOA and would relinquish the authorization (for a particular commit of course).

This allows for contracts to implement two new kinds of functionalities. First, they could create a batchExecuteThenRelease function, which would allow users to send a signed AUTH message for batch execution, but could call AUTHRELEASE at the end of the execution to ensure that the contract does not retain indefinite control of the EOA. The second feature that can be built with this is a revoke function for an invoker, which would simply call AUTHRELEASE for a particular commit, allowing a user to safely remove authorization from a contract that they no longer trust.

Of course, this system has many downsides. To start, these abilities can be created entirely at the smart contract level, so it does not necessarily require a new opcode, but form often fits function, and it is likely if we added this opcode that many invokers would chose to take advantage of it. An additional downside is it would require the precompile to access storage to store who has released an authorization, which I understand has never been done before.

This sort of opcode would significantly increase the cost of AUTH, since there would need to be a state read to check if the EOA has been released or not.

Your proposal also doesn’t outline how the “release” function works. If you release the EOA itself, how do you “un-release”? If you release the signature, you’ll have to store the hash of the signature in the trie which costs ~22k gas.

I was thinking that release would work exactly as you described, where you release the signature, which should be unique. This would mean that calling auth would only have the increased gas cost of reading state. I do agree though that the increased gas costs may make my proposed solution not worth it, especially since these features can be implemented at the smart contract level.

an address is EOA,and someone uses EIP-3074 for this address

can others create this address as a contract?

for example,hash collisions occur on binance chain and ethereum

https://etherscan.io/address/0x491604c0fdf08347dd1fa4ee062a822a5dd06b5d

This likely isn’t hash collision. Just someone sent the address some BNB and CTSI on BSC.

Hey, I thought I’d post this Twitter conversation that’s being had about ERC-3074 and composability, starting from this tweet:

https://twitter.com/dmihal/status/1414579442073194498

The concern is that if application developers begin using 3074 in their contracts, then those contracts basically become EOA-only.

You can take a look at this example from @fubuloubu:

transferFrom(msg.sender, this, amt) Can just be transfer(this, amt, auth=sig)

If applications use transfer{ auth: ... }() instead of transferFrom(), then contracts can no longer use this function, since contracts can’t sign messages (@gakonst pointed out that ERP-1271 could be used, but I think it would be difficult to get much adoption of that).

It seems that these issues can be avoided if application-level use of EIP-3074 is discouraged, and AUTH is only invoked from a handful of whitelisted “invoker” contracts.

https://twitter.com/lightclients/status/1414581676890419200

So my takeaways from this conversation is that usage of EIP-3074 should be limited at these levels:

  1. Wallet layer: wallets should only allow signing 3074 messages for whitelisted invokers (and there should be a very scary warning before adding new invokers to the whitelist)
  2. Language layer: Solidity & Vyper should not add support for 3074 opcodes, invokers should be built in Yul (or using inline Yul)
  3. Social layer: discourage developers from using 3074 opcodes directly, instead encourage them to build for existing invokers.
1 Like

I’ve been under a rock for the past few months, but I was thrilled to learn about 3074 while watching the presentation at EthCC 4. It’s refreshing to finally see more discussion with a focus on improving transaction efficiency and delegation, so that mainstream adoption (especially by enterprise) can happen in the near future. Bravo!

I’m the author of EIP-2746, and so I definitely have an interest in this subject, especially in regard to batching and rules. (I’ll admit that my EIP is definitely not as thought out as this one. I’m absolutely an amateur in this space.) I apologize for not yet being familiar with the implementation, but I was especially curious about the rules being encoded in these AUTHCALL requests (particularly in terms of their flexibility) and about the assumption that these new opcodes could be invoked within a L2.

Basically, I’m wondering what considerations have been made in terms of rule complexity and whether these rules can be other contract methods. I could see an interest in the Invoker executing a standard, lengthy set of rules that are too complicated and too costly to be encoded as part of a transaction’s calldata. In one example, a company might have a complicated validation algorithm that exists as a contract method and simply returns a boolean. In another example, a government or accounting firm could post a contract method (or a EIP-2746 ruletree - shameless plug) that is the standard for calculating a VAT tax, one that could be used by any enterprise without fear, and returns a numeric value. So, my question is this: could an AUTHCALL request result in the Invoker getting input from a contract method (using only a CALL, to be safe), to either evaluate in a rule or to pass along to the sponsored transaction? If not, can the Invoker receive and then store these encoded rules (which are part of the calldata?) on behalf of the caller, so that they can be invoked again at a later time and without the cost of sending them once again? That storage might be costly, but in the case of 3074 being deployed to a L2 (like Optimism), these storage costs could possibly be mitigated. Assuming, of course, that storage might be cheaper on a L2 in the long run.

Very curious about what’s possible. And great work so far!

Whoops…I forgot one question: do you think that EIP-3074 could possibly be compatible with a batching implementation found in a L2, like the Optimism sequencer?

L2s have more flexibility, so they can define their accounts to have arbitrary functionality. But yes, it would be compatible.

I still think there is different way of thinking about contract wallet accounts that benefits significantly from EIP-3074 and L2s could support something similar, even if EIP-3074 doesn’t reach mainnet.

I agree wholeheartedly. In addition to a new paradigm for account management, an Invoker instance from EIP-3074 could be a foundation for organizations and enterprises to build upon. If the Invoker proved to be a stable, safe platform by itself that could control an account through a set of dispatched rules (which are predefined simple actions), then their investment into contract creation (of developing, of auditing, of deploying, etc.) becomes unnecessary as they would just deploy their strategies to the Invoker, accelerating the buy-in of the mainstream.

Unfortunately, mainnet acceptance will probably be difficult to achieve. Do you think that Optimism would be curious to consider 3074 and test the new opcodes in the OVM?