EIP-3074: AUTH and AUTHCALL opcodes

Congrats to @SamWilsn on a great presentation of EIP-3074 today on the All-Core Devs call. I think you provided the correct answer to most challenges that were presented, and I hope you’re not discouraged by the sentiment to take more time to prove its safety. (1/x thread)

This call made me think of a theme that has become tragically common in my mind in the last few years, as Chip Morningstar says, “You can’t tell people anything” (we might need to demonstrate the value to get the actual buy-in)
http://habitatchronicles.com/2004/04/you-cant-tell-people-anything/

In violation of that advice, I’m now going to take a shot at explaining the safety again, in response to a repeated misunderstanding on the call.

I was pretty disappointed to see multiple smart people, (and every objector!) comparing this proposal to SUDO. It’s a catchy objection, but I think it’s pretty easy to demonstrate why that equivalence is dramatically off, and I think drilling into exactly why it’s different might help others build an intuition for how this EIP works.

This is Alice. She has lots of different valuable things in her account.
Screen Shot 2021-04-02 at 9.36.44 AM

In actuality, her account is just a metaphor for control, and each of these assets is defined by a different smart contract that obeys her account to control her assets. These arrows represent a flow of control from their fundamental source of truth to an intended recipient.

Today, when interacting with a smart contract like an exchange that needs one of Alice’s coins, she has to first ask that contract for permission, and then inform the other contract (like BobDEX), she first needs to tell the token’s contract to grant an allowance to this new contract, and if this is granted to a malicious contract, guess what happens? (Follow the arrow!)


As we can see from this new “allowance” arrow, Alice used the allowance function to symbolically delegate control of one of her tokens to this new dex. If Bob is evil, he can now steal all of her BlueCoin. People sometimes grant their whole token balance to an untrustworthy contract, and that’s a form of phishing that is real and people do it.
There are different approaches to solving that problem, and they are rooted in multiple problems that are at play:

  • The user got phished into wanting to use an exchange that was malicious.
  • For convenience the user delegated their whole token balance, when “just enough” would be safer.

Fundamentally, if a user wants to engage with a smart contract, they need to deposit assets into its trust. Even platforms that are prioritizing “offer safety” like Agoric’s Zoe (we guarantee you will receive at least X in exchange for Y) requires that you trust the offer-safety contract itself. Without that initial risk, there is no contract to be had.

This is the tension of a smart contracting platform: Squeezed between letting the user do whatever they want, and keeping them safe. It is natural to want to avoid introducing new insecurity, even at the benefit of new features, and so we should strive to prove that a new mechanism introduces no new insecurity to the platform, and I think this can be proven.

Coming back to EIP-3074. An invoker contract allows for a user to delegate FULL CONTROL OF THEIR ACCOUNT. The most concerned people say this sounds like SUDO, and probably imagine it looks like this to interact with an application:

But actually, this new proposal never suggests delegating messages to new applications. It’s not a tool for that at all, and its “invokers” are better understood as part of the wallet’s own code.

While the addition of an invoker is sensitive, and should be treated as carefully as an infinite allowance permission, this risk is not for regular interactions with new applications, and most wallets probably won’t even allow users to perform this delegation (and at least should never without extreme caution).

Once a wallet trusts an Invoker, it is able to perform batching operations with the user’s keys, or whatever other features the invokers offer and the wallets feel is worth the risk of adding. Exactly like the risks that wallet developers already take every day when evaluating and adding new features.

One of the reasons of infinite allowances today is that gas is expensive, and a user might want to avoid an increased-approval transaction later by approving a higher allowance now. If we had support for batching, wallets would actually have tools to facilitate fewer, cheaper user approvals that allow them to delegate LESS authority to outside parties.

When invoking SUDO, a UNIX user grants the highest authority of the entire system to an application they are invoking. Heck, in UNIX, every invocation grants your full user account authority to any program you run.

In UNIX, every time you run a program you’re basically handing it your private key, and no one is more concerned about those kinds of unintentional delegation risks than wallet developers, and that’s why at MetaMask we’re building tools to allow running scripts with reduced system access:

Unlike the unix permissions model, A 3074 invoker adds a system for wallets to add new trusted modules that can allow for new ways of key-managed accounts to delegate authority.

Once delegated, the most those invokers can do is send messages from the user’s account, meaning if a contract is secure against current user behavior today, it should continue to be secure regardless of any delegation actions the user performs outside of that contract’s system. If another account’s behavior can put a contract you care about at risk, then that contract is not secure.

9 Likes

It was compared not to Unix sudo, but to SUDO opcode proposal, which was April fool’s joke, of course. I think comparison with SUDO opcode is actually quite useful to understand what EIP-3074 does. As I mentioned on the call, it does it with restrictions, and I also explained what these restrictions are, from my point of view.

I am sorry for not engaging into the discussion on this topic on the call (simply because I was engaging into discussion in the Discord channel, and I also I had to leave the call earlier), but I did want to voice my objection because, as it happens, objectors have to object on every single call for the objections to be heard (and this is unfortunate). I am Ok with you being disappointed, but I wanted to say that I was not trying to be difficult. I was willing to put work in understand what the EIP was, pretty early on, and go against the “community sentiment” to voice my concerns. Consider this as a contribution and not as trolling please.

I do understand that this may be necessary security compromise for better usability, but I think I correctly judge that most people don’t really understand what is being changed, and why this is a compromise.

For the record, the joke proposal is here, and allows anyone to send messages from any account without any proof:

I think comparing these is ludicrous, as one lets anyone do anything, and the other allows key holders to delegate their own powers, and no more.

I disagree that this proposal makes security compromises. If we assume two security properties, it is safe:

  • Users and wallets do not sign invoker messages without intending to delegate account powers.
  • Existing smart contracts are secure under unrestricted behavior of other accounts.

I was not disappointed that you expressed concerns (and I agree they should not need presenting on every call), but it’s pretty shocking to me that you’re still insisting 3074 is anything like SUDO.

On the All Core Devs Call, @vitalikbuterin mentioned that long term all accounts should be tending towards being contract accounts anyways, and I think this was maybe a point for why this EIP isn’t needed.

I think we all agree that from a feature perspective, this is a desirable future, but from an implementation perspective, this introduces lots of complexity related to the migration. EIP 3074 provides a concrete migration path from a private key controlled account to a contract account, so that existing EOAs can continue operating even if its original keys are no longer on hot wallets, and they are transitioned to a mostly contract-style use

I disagree that it is ludicrous. When I myself tried to understand what EIP-3074 actually does, one of the iterations of my understanding was the same functionality as SUDO opcode, a completely unrestricted version of AUTHCALL. Then, of course, I realised that such opcode is dangerous, and how exactly EIP-3074 restricts this.

I also insists that it does make security compromise, which needs to be acknowledged, if this EIP goes in (which, of course, may happen despite my opposition). The main problematic thing I see in this EIP is that it creates a possibility of a very lucrative scam, which does need to involve wallet providers, willingly or not, and which is not really possible with today’s meaning of signatures. If someone manages to inject code into wallet provider’s release in some way, and introduce a “sleeper bug”, which will not attack users straight away, but work as expected, but harvest signatures to an upgradeable invoker contract. Once the time is right (enough signatures are harvested, containing enough value to steal), the attacker upgrades the contract and sweeps the tokens, at the time of their choosing. I know you would say this is unlikely, but, given the reward, such scheme is not outside of realm of possibility. And, as far as I know, such scheme would not work today, because one signature cannot give access to arbitrary number of operations in the future, and also the signature can be made invalid by issuing a transaction with the same nonce. In the harvesting signatures scenario, the only revocation possible is to create new address and issue lots of transactions to move tokens over.

And to conclude, there is alternative to this, which I unfortunately could not put a lot of work in. It is through the new transaction type allowing multiple signatures. I do posit that transaction relay is not the use case where we HAVE to introduce the property that one signature authorises unlimited number of future actions. Such property may be required for other things, but not for sponsored transactions. From my point of view, it is overshooting, i.e., a more security-conservative solution would be enough.

Is the attack vector that you’re concerned about here that the wallet UI could be modified into allowing signatures to an untrusted invoker then the signatures could be reused later once the invoker is modified?

Regardless, once you can inject code into a wallet, you can encrypt and exfiltrate the account’s private key as extra bytes in calldata. Yes, I know hardware wallets exist, but they should prevent signing EIP-3074 messages anyway.

I always use hardware wallets for signing anything of value. But checking what you are signing is not easy, perhaps unless you have something like Grid lattice. That’s why I do not usually like working with tokens :slight_smile:

This is exactly why wallets won’t leave it to users to hopefully review which EIP-3074 invokers they’re signing over. It will be hard coded that the wallet only supports these specific invokers that the wallet team has reviewed and that have community trust/support.

Thanks for sharing a concrete example.

Thanks in part to CREATE2, I think you’re right that an invoker could be changed at the same address. There are simple ways to completely avoid this, like only trusting invokers that are published with CREATE, since their address generation cannot be reproduced.

This is a good example of a serious consideration that needs to be taken when delegating to an invoker, but I don’t think this is new. Wallets are effectively already constantly exposed to this kind of attack (new code could silently undermine user trust at a later date), it’s just that it’s client-side, not always on-chain (although contract-based wallets are basically already vulnerable to the exact scenario you describe). I don’t think this changes the threat model of wallet development in any way.

Fortunately, no one is exposed to these risks by 3074 without someone at a later date making a deliberate choice to integrate the invoker into a wallet, or to sign a delegation message, and even then, the risk is limited to the negligent parties (or that wallet’s users).

Fortunately, this conversation would not end at the inclusion of EIP-3074, instead it would represent the beginning of the design of secure invokers. I have no doubt that good invokers can be built that are widely vetted and easy to trust, and will leave this particular concern comfortably to rest.

1 Like

I’m involved in the development of the Minerva Wallet and was pretty happy when reading about this EIP.
3074 reminds me of the Proxy pattern which separates state from logic.
In a way, it would provide all EOAs with the optionality of behaving like a proxy contract which can freely choose its implementation contract with every transaction. To me that sounds appealing both from a wallet provider and from a user perspective.

3074 vs contract wallets

I agree that in theory everybody should just use contract wallets.
In practice however they have their own set of tradeoffs:

  • they seem to still not be first class citizens in large parts of the ecosystem.
    There’s (still) a lot of tooling and contracts out there assuming the sender to be an EOA. I don’t have any stats supporting that claim, just my subjective impression.
  • they can pose an entry barrier.
    Creating an EOA is a trivial off-chain activity which can take place on any device, doesn’t require connectivity and doesn’t require any funds or sponsor. Creating an account with a contract wallet requires either a funded EOA to start from or a sponsor.
    I guess however that disadvantage was at least partially mitigated with CREATE2.
  • contract wallets create a kind of vendor lock-in.
    As a user, I was always hesitant to commit to a contract wallet. That’s because I know that migrating from a wallet using a contract account (often a contract specific to that wallet) to another wallet is a non-trivial operation.

Intuitively I’d say that 3074 kind of retrofits EOAs with optional contract wallet functionality.

Security

I’m probably not qualified enough to comment about the overall tradeoffs in terms of complexity/attack surface/security etc. on a protocol level. Just wanted to comment on the specific concern voiced by @akolotov about potential signature harvesting.
I hardly see a significant difference btw. a conventional EOA and an 3074 enabled EOA there. If I were a blackhat able to inject code into a wallet today which allowed me to sign arbitrary messages, I’d just harvest a lot of signatures, varying nonces, token addresses (most popular first), amounts (not knowing what the balance will be when I’m ready to steal). That may not allow me to steal and break everything, but would likely come pretty close in many cases.
That’s assuming that I couldn’t just steal the PK itself anyway (as was the case e.g. in the 2018 Copay incident).

2 Likes

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

4 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.

3 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.

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.