Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move EIP-712 to Last Call #5271

Merged
merged 21 commits into from
Jul 25, 2022
Merged
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 27 additions & 41 deletions EIPS/eip-712.md
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
---
eip: 712
title: Ethereum typed structured data hashing and signing
frangio marked this conversation as resolved.
Show resolved Hide resolved
title: Typed structured data hashing and signing
description: A standard for hashing and signing of typed structured data as opposed to just bytestrings.
author: Remco Bloemen (@Recmo), Leonid Logvinov (@LogvinovLeon), Jacob Evans (@dekz)
frangio marked this conversation as resolved.
Show resolved Hide resolved
discussions-to: https://ethereum-magicians.org/t/eip-712-eth-signtypeddata-as-a-standard-for-machine-verifiable-and-human-readable-typed-data-signing/397
status: Review
status: Last Call
last-call-deadline: 2022-08-08
type: Standards Track
category: Interface
created: 2017-09-12
requires: 155, 191
---

## Simple Summary
## Abstract

Signing data is a solved problem if all we care about are bytestrings. Unfortunately in the real world we care about complex meaningful messages. Hashing structured data is non-trivial and errors result in loss of the security properties of the system.
frangio marked this conversation as resolved.
Show resolved Hide resolved

As such, the adage "don't roll your own crypto" applies. Instead, a peer-reviewed well-tested standard method needs to be used. This EIP aims to be that standard.
frangio marked this conversation as resolved.
Show resolved Hide resolved

## Abstract

This is a standard for hashing and signing of typed structured data as opposed to just bytestrings. It includes a

* theoretical framework for correctness of encoding functions,
Expand All @@ -36,18 +36,15 @@ This EIP aims to improve the usability of off-chain message signing for use on-c

frangio marked this conversation as resolved.
Show resolved Hide resolved
<img src="https://github.com/ethereum/EIPs/blob/master/assets/eip-712/eth_sign.png?raw=true" style="padding-bottom: 20px; padding-top: 20px" width="60%" />
frangio marked this conversation as resolved.
Show resolved Hide resolved
frangio marked this conversation as resolved.
Show resolved Hide resolved

Here we outline a scheme to encode data along with its structure which allows it to be displayed to the user for verification when signing. Below is an example of what a user could be shown when signing an EIP712 message.
Here we outline a scheme to encode data along with its structure which allows it to be displayed to the user for verification when signing. Below is an example of what a user could be shown when signing a message according to the present proposal.

<img src="https://github.com/ethereum/EIPs/blob/master/assets/eip-712/eth_signTypedData.png?raw=true" style="padding-bottom: 20px; padding-top: 20px" width="60%" />

frangio marked this conversation as resolved.
Show resolved Hide resolved
### Signatures and Hashing overview

A signature scheme consists of hashing algorithm and a signing algorithm. The signing algorithm of choice in Ethereum is `secp256k1`. The hashing algorithm of choice is `keccak256`, this is a function from bytestrings, 𝔹⁸ⁿ, to 256-bit strings, 𝔹²⁵⁶.

A good hashing algorithm should satisfy security properties such as determinism, second pre-image resistance and collision resistance. The `keccak256` function satisfies the above criteria _when applied to bytestrings_. If we want to apply it to other sets we first need to map this set to bytestrings. It is critically important that this encoding function is [deterministic][deterministic] and [injective][injective]. If it is not deterministic then the hash might differ from the moment of signing to the moment of verifying, causing the signature to incorrectly be rejected. If it is not injective then there are two different elements in our input set that hash to the same value, causing a signature to be valid for a different unrelated message.

[deterministic]: https://en.wikipedia.org/wiki/Deterministic_algorithm
[injective]: https://en.wikipedia.org/wiki/Injective_function
A good hashing algorithm should satisfy security properties such as determinism, second pre-image resistance and collision resistance. The `keccak256` function satisfies the above criteria _when applied to bytestrings_. If we want to apply it to other sets we first need to map this set to bytestrings. It is critically important that this encoding function is deterministic and injective. If it is not deterministic then the hash might differ from the moment of signing to the moment of verifying, causing the signature to incorrectly be rejected. If it is not injective then there are two different elements in our input set that hash to the same value, causing a signature to be valid for a different unrelated message.

### Transactions and bytestrings

Expand All @@ -56,20 +53,15 @@ An illustrative example of the above breakage can be found in Ethereum. Ethereum
* `encode(t : 𝕋) = RLP_encode(t)`
* `encode(b : 𝔹⁸ⁿ) = b`

While individually they satisfy the required properties, together they do not. If we take `b = RLP_encode(t)` we have a collision. This is mitigated in Geth [PR 2940][geth-pr] by modifying the second leg of the encoding function:

[geth-pr]: https://github.com/ethereum/go-ethereum/pull/2940
While individually they satisfy the required properties, together they do not. If we take `b = RLP_encode(t)` we have a collision. This is mitigated in ethereum/go-ethereum#2940 by modifying the second leg of the encoding function:

* `encode(b : 𝔹⁸ⁿ) = "\x19Ethereum Signed Message:\n" ‖ len(b) ‖ b` where `len(b)` is the ascii-decimal encoding of the number of bytes in `b`.

This solves the collision between the legs since `RLP_encode(t : 𝕋)` never starts with `\x19`. There is still the risk of the new encoding function not being deterministic or injective. It is instructive to consider those in detail.

As is, the definition above is not deterministic. For a 4-byte string `b` both encodings with `len(b) = "4"` and `len(b) = "004"` are valid. This can be solved by further requiring that the decimal encoding of the length has no leading zeros and `len("") = "0"`.

The above definition is not obviously collision free. Does a bytestring starting with `"\x19Ethereum Signed Message:\n42a…"` mean a 42-byte string starting with `a` or a 4-byte string starting with `2a`?. This was pointed out in [Geth issue #14794][geth-issue-14794] and motivated Trezor to [not implement the standard][trezor] as-is. Fortunately this does not lead to actual collisions as the total length of the encoded bytestring provides sufficient information to disambiguate the cases.

[geth-issue-14794]: https://github.com/ethereum/go-ethereum/issues/14794
[trezor]: https://github.com/trezor/trezor-mcu/issues/163
The above definition is not obviously collision free. Does a bytestring starting with `"\x19Ethereum Signed Message:\n42a…"` mean a 42-byte string starting with `a` or a 4-byte string starting with `2a`?. This was pointed out in ethereum/go-ethereum#14794 and motivated Trezor to not implement the standard as-is (see trezor/trezor-mcu#163). Fortunately this does not lead to actual collisions as the total length of the encoded bytestring provides sufficient information to disambiguate the cases.

Both determinism and injectiveness would be trivially true if `len(b)` was left out entirely. The point is, it is difficult to map arbitrary sets to bytestrings without introducing security issues in the encoding function. Yet the current design of `eth_sign` still takes a bytestring as input and expects implementors to come up with an encoding.

Expand Down Expand Up @@ -172,26 +164,22 @@ The `EIP712Domain` fields should be the order as above, skipping any absent fiel

### Specification of the `eth_signTypedData` JSON RPC

The method `eth_signTypedData` is added to the [Ethereum JSON-RPC][json-rpc]. The method parallels `eth_sign`.

[json-rpc]: https://github.com/ethereum/wiki/wiki/JSON-RPC
The method `eth_signTypedData` is added to the Ethereum JSON-RPC. The method parallels `eth_sign`.

#### eth_signTypedData

The sign method calculates an Ethereum specific signature with: `sign(keccak256("\x19Ethereum Signed Message:\n" + len(message) + message)))`.

By adding a prefix to the message makes the calculated signature recognisable as an Ethereum specific signature. This prevents misuse where a malicious DApp can sign arbitrary data (e.g. transaction) and use the signature to impersonate the victim.

**Note** the address to sign with must be unlocked.
**Note**: the address to sign with must be unlocked.

##### Parameters

1. `Address` - 20 Bytes - Address of the account that will sign the messages.
2. `TypedData` - Typed structured data to be signed.

Typed data is a JSON object containing type information, domain separator parameters and the message object. Below is the [json-schema][jsons] definition for `TypedData` param.

[jsons]: https://json-schema.org/
Typed data is a JSON object containing type information, domain separator parameters and the message object. Below is the json-schema definition for `TypedData` param.

```JavaScript
{
Expand Down Expand Up @@ -225,9 +213,8 @@ Typed data is a JSON object containing type information, domain separator parame

##### Returns

`DATA`: Signature. As in `eth_sign` it is a hex encoded 129 byte array starting with `0x`. It encodes the `r`, `s` and `v` parameters from appendix F of the [yellow paper][yellow] in big-endian format. Bytes 0...64 contain the `r` parameter, bytes 64...128 the `s` parameter and the last byte the `v` parameter. Note that the `v` parameter includes the chain id as specified in [EIP-155][eip-155].
`DATA`: Signature. As in `eth_sign` it is a hex encoded 129 byte array starting with `0x`. It encodes the `r`, `s` and `v` parameters from appendix F of the yellow paper in big-endian format. Bytes 0...64 contain the `r` parameter, bytes 64...128 the `s` parameter and the last byte the `v` parameter. Note that the `v` parameter includes the chain id as specified in [EIP-155][eip-155].

[yellow]: https://ethereum.github.io/yellowpaper/paper.pdf
[eip-155]: ./eip-155.md

##### Example
Expand All @@ -246,19 +233,17 @@ Result:
}
```

An example how to use Solidity ecrecover to verify the signature calculated with `eth_signTypedData` can be found in the EIP712 [Example.js][example-js]. The contract is deployed on the testnet Ropsten and Rinkeby.
An example how to use Solidity ecrecover to verify the signature calculated with `eth_signTypedData` can be found in the [Example.js][example-js]. The contract is deployed on the testnet Ropsten and Rinkeby.

[example-js]: https://github.com/ethereum/EIPs/blob/master/assets/eip-712/Example.js
[example-js]: ../assets/eip-712/Example.js

#### personal_signTypedData

There also should be a corresponding `personal_signTypedData` method which accepts the password for an account as the last argument.

### Specification of the Web3 API

Two methods are added to [Web 3 version 1][web3-1] that parallel the `web3.eth.sign` and `web3.eth.personal.sign` methods.

[web3-1]: https://web3js.readthedocs.io/en/1.0/index.html
Two methods are added to Web3.js version 1 that parallel the `web3.eth.sign` and `web3.eth.personal.sign` methods.

#### web3.eth.signTypedData

Expand All @@ -274,9 +259,7 @@ Signs typed data using a specific account. This account needs to be unlocked.
2. ``String|Number`` - Address to sign data with. Or an address or index of a local wallet in :ref:`web3.eth.accounts.wallet <eth_accounts_wallet>`.
3. ``Function`` - (optional) Optional callback, returns an error object as first parameter and the result as second.

----
**Note** The 2. ``address`` parameter can also be an address or index from the `web3.eth.accounts.wallet <eth_accounts_wallet>`. It will then sign locally using the private key of this account.
----
**Note**: The 2. ``address`` parameter can also be an address or index from the `web3.eth.accounts.wallet <eth_accounts_wallet>`. It will then sign locally using the private key of this account.

##### Returns

Expand Down Expand Up @@ -325,7 +308,10 @@ For the type hash several alternatives were considered and rejected for the reas

**Alternative 2**: Use ABIv2 function signatures. `bytes4` is not enough to be collision resistant. Unlike function signatures, there is negligible runtime cost incurred by using longer hashes.

**Alternative 3**: ABIv2 function signatures modified to be 256-bit. While this captures type info, it does not capture any of the semantics other than the function. This is already causing a practical collision between ERC20's and ERC721's `transfer(address,uint256)`, where in the former the `uint256` refers to an amount and the latter to a unique id. In general ABIv2 favors compatibility where a hashing standard should prefer incompatibility.
**Alternative 3**: ABIv2 function signatures modified to be 256-bit. While this captures type info, it does not capture any of the semantics other than the function. This is already causing a practical collision between [EIP-20]'s and [EIP-721]'s `transfer(address,uint256)`, where in the former the `uint256` refers to an amount and the latter to a unique id. In general ABIv2 favors compatibility where a hashing standard should prefer incompatibility.

[EIP-20]: ./eip-20.md
[EIP-721]: ./eip-721.md

**Alternative 4**: 256-bit ABIv2 signatures extended with parameter names and struct names. The `Mail` example from a above would be encoded as `Mail(Person(string name,address wallet) from,Person(string name,address wallet) to,string contents)`. This is longer than the proposed solution. And indeed, the length of the string can grow exponentially in the length of the input (consider `struct A{B a;B b;}; struct B {C a;C b;}; …`). It also does not allow a recursive struct type (consider `struct List {uint256 value; List next;}`).

Expand Down Expand Up @@ -386,7 +372,7 @@ The in-place implementation makes strong but reasonable assumptions on the memor

Similarly, a straightforward implementation is sub-optimal for directed acyclic graphs. A simple recursion through the members can visit the same node twice. Memoization can optimize this.

## Rationale for `domainSeparator`
### Rationale for `domainSeparator`

Since different domains have different needs, an extensible scheme is used where the DApp specifies a `EIP712Domain` struct type and an instance `eip712Domain` which it passes to the user-agent. The user-agent can then apply different verification measures depending on the fields that are there.

frangio marked this conversation as resolved.
Show resolved Hide resolved
Expand All @@ -404,8 +390,8 @@ The Solidity expression `keccak256(someInstance)` for an instance `someInstance`

An example contract can be found in [Example.sol][ex-sol] and an example implementation of signing in JavaScript in [Example.js][ex-js]

[ex-sol]: https://github.com/ethereum/EIPs/blob/master/assets/eip-712/Example.sol
[ex-js]: https://github.com/ethereum/EIPs/blob/master/assets/eip-712/Example.js
[ex-sol]: ../assets/eip-712/Example.sol
[ex-js]: ../assets/eip-712/Example.js

frangio marked this conversation as resolved.
Show resolved Hide resolved
## Implementation
frangio marked this conversation as resolved.
Show resolved Hide resolved

Expand All @@ -425,11 +411,11 @@ To be done before this EIP can be considered "Final":
**Rationale for marking goals as done:**
* `eth_signTypedData`

MetaMask has had a series of `eth_signTypedData` since 2017 ([article](https://medium.com/metamask/scaling-web3-with-signtypeddata-91d6efc8b290)), and is an accepted standard in the wallet ecosystem.
MetaMask has had a series of `eth_signTypedData` since 2017, and is an accepted standard in the wallet ecosystem.

* `web3.eth.signTypedData`

While this api does **not** exist in the web3.js library as of this writing, it is included as an experimental feature in the Ethers library [here](https://medium.com/metamask/scaling-web3-with-signtypeddata-91d6efc8b290). It can also be called from the Web3.js library by using the `sendAsync` method:
While this api does **not** exist in the web3.js library as of this writing, it is included as an experimental feature in the Ethers library. It can also be called from the Web3.js library by using the `sendAsync` method:

```javascript
web3.currentProvider.sendAsync({
Expand All @@ -444,7 +430,7 @@ While the `keccak256` function in Solidity cannot be invoked directly on structs

* review of specification

In the time since EIP-712 has been proposed, it has enjoyed wide ecosystem adoption, and has become the foundation for a number of other EIPs building on top of it. (For example, ERC20 token Permits (transaction-less token approvals) in [EIP-2612](./eip-2612.md).) MetaMask uses EIP-712 for more readable output to present users, and other wallets have followed suit. The widespread traction and use of this EIP over the course of years justify regarding as sufficiently reviewed.
In the time since this EIP was proposed, it has enjoyed wide ecosystem adoption, and has become the foundation for a number of other EIPs building on top of it. (For example, EIP-20 token Permits (transaction-less token approvals) in [EIP-2612](./eip-2612.md).) MetaMask uses it for more readable output to present users, and other wallets have followed suit. The widespread traction and use of this EIP over the course of years justify regarding as sufficiently reviewed.



Expand Down