The use case of Web3 off-chain signatures intended to be used within on-chain transaction is gaining traction and being used in multiple leading protocols (e.g. OpenSea) and standards (EIP-2612), mainly as it offers a fee-less experience. Attackers are known to actively and successfully abuse such off-chain signatures, leveraging the fact that users are blindly signing off-chain messages, since they are not humanly readable.
The idea: EIP-712 already formally binds an off-chain signature to a contract, with the âverifyingContractâ parameter. We suggest adding a âviewâ function (âstateMutabilityâ:âviewâ) to such contracts, that returns a human readable description of the meaning of this specific off-chain buffer.
We are looking to get feedback from the community and especially from Smart Contract implementors, Wallets developers and security practitioners.
Some non-editorial comments (meaning youâre free to ignore them and your PR will still be merged):
- Iâm really not in love with the name
evalEIP712Buffer
. To me, âevalâ implies âevaluateâ which is exactly what this function doesnât do. Maybe something likedescribeEIP712
, ordescriptionOf
? - How does this differentiate between two or more functions on the same contract that accept an EIP-712 signature? Imagine an ERC721 token with a
permit
function (for approval), and agaslessMint
function (so the buyer pays gas fees.) - Should this interface be exposed with EIP-165?
- What should the implementing contract do if it doesnât understand the signature? Revert?
- I assume thereâs some way to tell if the contract doesnât implement this EIP? Probably just try to call the function. What if thereâs a selector id collision?
- Good suggestion per name. Letâs leave it open for a period, to collect other people ideas on best name.
- differentiation of two or more functions on the same contract that accept an EIP-712 signature is done by using the
primaryType
parameter that is passed to the function - Seems like a good suggestion. we will update the EIP to reflect it
- reverting makes sense. we will update the EIP to reflect it
- We will use EIP-165 as suggested
As part of our process to encourage peer review, we assign a volunteer peer reviewer to read through your proposal and post any feedback here. Your peer reviewer is @mayowa! Please note that this review is NOT required to move your EIP through the process. When youâthe authorsâfeel ready, just open a pull request.
If any of this EIPâs authors would like to participate in the volunteer peer review process, shoot me a message!
@mayowa please take a look through EIP-6384 and comment here with any feedback or questions. Thanks!
I think it would be a good idea to implement this as an EIP-712 extension where the âlocalization contractâ can be specified explicitly. When the frontend calls eth_signTypedData
, the address to the localization contract is passed explicitly as an additionalProperty
, ie:
[...]
additionalProperties: [
{
"name": "EIP-6384-Localization",
"type": "address",
"value": "0xdeadbeef"
}
]
[...]
This would benefit the proposal in two ways;
- The wallet software can determine whether the request is EIP-6384 without sending an RPC request/EIP-165
- The logic for localization wonât have to live in the same contract as the verifier address. I suspect a lot of protocols would want to update their localization without having to update a core contract or add localization logic to dozens of EIP-6384-compliant contracts. I suspect most implementations would just proxy
evalEIP712Buffer
requests to a single contract, so using additionalProperty will simplify things for implementers.
This an option we had considered. But we were worried extending EIP-712 will cause backward compatibility issues in all parties that used to get the older version of EIP-712.
Changing the EIP-712 format will require changing the current verifyingContract
, as the signature verification will change.
Having said that, your point #1 is definitely an advantage. We are pragmatic and very open to get the implementersâ feedback and if they feel EIP-712 extension is the easier way to go, we will change to that course.
Thanks for submitting this EIP! It addresses a very important problem that doesnât receive enough attention.
I love the idea being able to get a human readable description of an EIP-712 payload from the verifying contract, but Iâm concerned about returning natural language messages from the evalEIP712Buffer
method. Iâd like to propose returning structured data instead of strings, which would have the following advantages:
- Wallets could build their own display format based on the structured data to ensure consistency for UX and security reasons.
- Structured data would enable localization (display in different languages) on the wallet side.
- Receiving token contract addresses and quantities in the structured data would let wallets display balances and dollar values of tokens when approving the signature.
I imagine using the standard token transfer and approval events as the basis of the structured data format. If this is too limiting or too complex to implement, an alternative option could be for the verifying contract to provide a single entry point where all EIP-712 messages can be submitted for execution which would enable running transaction simulation on the payloads.
The transaction simulation approach could make sense, since wallets have to do transaction simulation anyway to learn the outcome of on-chain signature requests, and it might be easier to implement for smart contract developers than the alternatives. As an added benefit, the transaction simulation approach would eliminate the potential for mistakes in mapping EIP-712 payloads to explanations. Iâm unsure about the security implications though.
Looking forward to hearing your thoughts!
Returning structured data is indeed a good idea, for all the reasons mentioned.
We thought about it, but we were worried that would add additional complexity and would make it even harder to adopt by smart contract devs.
If smart contract devs are open to such extra complexity we would love to include it.
BTW, we can make this extensible by adding versioning, setting this to be version â1â and allow future upgrades in a backward compatible manner.
We can also have both: a string as a fallback that includes some data and added structured data, that the wallet can parse and show if the wallet supports it.
As for executing and simulating, I believe it would make the function not a âviewâ and will also must revert in the end to avoid actually executing (some idea as implemented in EIP-4337), which requires changes to simulation provider logic and might be dangerous if not implemented correctly in smart contract.
Thanks for bringing up this discussion.
I was wondering if a more âoff-chainâ based solution was considered. Building strings (especially for more complex EIP-712 types) can be quite complex.
It would be possible to include it as part of the ABI/Natspec documentation. Then it could be possible to use it via the âSourcifyâ approach.
Another alternative could be something like the tokenlist standard where for each primary type you can find a decoding function (i.e. a wasm module). This would also allow to be more flexible in the future for adding multi-language support. Also it would make it possible that a âtrustedâ party (i.e. like for the tokenlists Uniswap) extends/maintains the list.
I have a pretty strong preference for the token-list approach, since itâs (1) compatible with all existing contacts using ERC-712 and (2) allows users to choose which list curators they trust.
The solution is âoff-chainâ in the sense this is a âviewâ function that does not need to be executed on chain, but locally on node.
Natspec-like solutions can only give static information (âyou are going to list something on OpenSeaâ), and not actually tell what would the specific message would do (âyou are going to list your BAYC #4562 on OpenSea for 0.001 ETHâ)
As for curated list by a âtrustedâ party, sounds like a centralized solution that weâd like to avoid, if possible.
With our current suggested solution you donât need to trust anyone except the contract your are signing to, that you already trust. Adding additional âtrustedâ curators is not helpful and adding another possible point of failure. Also see the reply above.
Nice suggestion @agostbiro
When using structured data, what if the data to be displayed isnât necessarily related to token approvals?
Iâm assuming wallets will check if the return data is of that type and if not just display the plain string?
That is not true (see NatSpec Format â Solidity 0.8.17 documentation) .
That is not really off-chain as the whole string building and type conversion is done on evm level (so the logic is stored on chain).
I learned something new about NatSpec today. Thank you for sharing. I am not sure it will be expressive enough for our needs, but I will give it a try.
That is not really off-chain as the whole string building and type conversion is done on evm level (so the logic is stored on chain).
I assume most of this code already exists in the smart contract as itâs needed for the actual evaluation of the signature. And having the code stored on chain is more of a feature than a bug, in my eyes
Have you checked out tokenlists?
Iâd say itâs anything but centralized. Rather, it allows anyone to create and curate a list (individuals, organisations, smart contracts, etc) and then itâs up to apps and users to decide which lists they trust and want to use.
Hey, Iâm looking into Tokenlists, not sure I understand how it can help with the suggested problem, it seems like it aims to curate a âWhitelistâ for legit tokens
If I understand the high-level goal of this EIP, it is to provide canonical, human-readable, descriptions of what a given EIP-712-like signature does, to make it more secure for users to interact with systems requesting these signatures.
Essentially, a userâs wallet needs to be able to display a human-readable interpretation of what a given signature will do on-chain.
As you said, @Vazi, Tokenlists is a system for curating lists of tokens. Not just which tokens are legit, but also the correct metadata for each token. @rmeissner and I were not proposing to use Tokenlists for this use-case, rather that a tokenlists-like approach could be used to solve for the problem identified.
Essentially, rather than storing this information on-chain in the verifyingContract
, which is not backward compatible with existing contracts leveraging EIP-712, this information would be independently curated in a similar fashion to how tokenlists works. There would be many competing lists, curated with different mechanisms, and it would be up to each app and user to decide which list of EIP-712 metadata they would like to make signature requests in their wallet more readable.
With all due respect, these problems are not similar and hence require different solutions.
With tokens the contract cannot attest for itself, as the contract might be malicious (this is the original problem to be solved). Therefore, we must resort to solution in which others should attest to it and in this context tokenlist-like solution makes a lot of sense.
In the case of EIP-712 signatures, the contract is already trusted as itâs the verifyingContract
. Therefore it is best suited to attest for itself and not require another list that has to be curated and maintained by 3rd parties.
I recommend checking out Trustless Signing UI Protocol ¡ Issue #719 ¡ ethereum/EIPs ¡ GitHub for additional discussion/ideas on how something like this could be designed. IIUC, it requires less data on chain than what is proposed here, and is a bit more flexible when it comes to things like localization.