A History of eth_sign in MetaMask
Ah, eth_sign
. I can tell that story. I may look like a fool for part of it, but I’ll try to represent the historical contexts that made the sequence of decisions make sense. This’ll be a personal perspective, closely tied to my position as a person of significant influence at MetaMask.
eth_sign
here means the method that allows signing a plain hash. It is opaque by definition, and so should be assumed to be capable of doing anything a single signature on the eth curve can do.
eth_sign
was originally a geth feature, back when it was the only signer. Of course a CLI signer has a signing method to do anything with, right?
Then Mist, the original web3 browser which contained a full node added geth’s RPC to its web3 API injected into every page. Back then they’d just sign anything the site asked it to without user interaction. This was safe for a moment because there were basically no users or Ðapps, and phishing was limited to pranks.
MetaMask originally cloned Mist’s API in the name of being compliant with what we perceived as a nascent standard, but we added confirmations before any signing interaction. We eventually added some important improvements like only revealing a user’s accounts upon a site initiated confirmation.
Before any readable signature standards existed, any contract that implemented a slightly novel signing scheme would depend on eth_sign
to innovate. personal_sign
added string signing, but was not efficient to parse onchain. EIP-712 signTypedData
improved machine efficiency, but still struggles for sufficient readability to be considered safe.
Early on we identified the risk to eth_sign
and added our most severe warning text of any confirmation, basically saying the signature could potentially transfer away all of your funds, even though at the time such an attack was theoretical (unlike post EIP-3074).
Early on we also:
- Had examples of Ðapps who depended on eth_sign, like a site's admin panel (I think it was Gnosis SAFE), posting offers on some DEXes, and (maybe ironically) advanced methods for sweeping funds from an otherwise compromised account.
- Did not have data showing it was successfully being used for harm.
At the time I had high optimism that users could learn to take confirmations (and their warnings) seriously. I have since transitioned to more interest in alternative interaction patterns that encode consent in the user’s behavior rather than depend on reading, like a file picker. I gave a talk about a proposed next step for readable wallet-site connections at Devconnect 2023 in Istanbul.
I defended keeping eth_sign
longer than many. At that stage, I felt preserving the functionality of the existing ecosystem was critical, and had high faith the warning was doing its job. I argued against Pedro from walletconnect. I argued against my own cofounder Kumavis. I probably defended it too long. I often cited this email by Linus Torvalds.
I still believe the principle of non-breaking APIs is under-appreciated in web3. I collected notes on how different platforms drew the line. Security was clearly the top justifying reason, but I hadn’t seen the proof that our warnings were insufficient.
Largely driven by Harry Denley, we eventually added enough metrics that I was convinced there was enough phishing activity and not enough legitimate usage of the method that it was time to sunset it. We initially have it soft-deprecated, and it’s buried in settings behind another heavy warning. Our metrics show this has been sufficient to all but eliminate its usage. Some phishing kits still hit the method, but they are auto-rejected and tracked for flagging.
Now that 3074 is looking likely for inclusion, we’re finally completely removing the option to enable this method, even for advanced users going into settings. They’ll need to use a command line tool if they really need to sign messages that are formed outside the bounds of the existing signing methods.
I'd also like to note that even "readable signature methods" can be misused. OpenSea's Wyvern 1.3 contracts used the plain-text personal_sign
signature method to sign an opaque hash as text, which is a good example that these methods only work if it's obvious how to use them safely to every member of the interaction. The lazy path needs to be the safe path.
I hope if anything stands out here, it’s the gradualness of the process that we have been involved in. I think we’re finally starting to see some eth adoption of more coherent readability (I mean onchain safety, not simulation), but I don’t think true safe interaction patterns can be back-ported to EOAs. I think users need to be able to enforce their own policies around what they own, and that requires the power of code.
I’d hoped for some years that the existing smart account wallet teams would uncover some safer usage patterns that we could integrate via our Snaps plugin system, and we are now seeing the first SCAs pursue a standardized permissions system for connecting to Ðapps. I’ve long advocated for 3074 because I saw it as a path for EOAs to eventually enjoy some of the same assurances that SCAs unlock (so old accounts have some safety, not so we can prevent new users from using safer accounts).
I hope that's at least an interesting history. I haven’t been omniscient, and none of us have been, but at least you can see how the safety requirements of an ecosystem may change over time, along with our abilities to reason about them. I hope you don't judge me too harshly for my part.