mirror of https://github.com/status-im/EIPs.git
Automatically merged updates to draft EIP(s) 712
Hi, I'm a bot! This change was automatically merged because: - It only modifies existing Draft or Last Call EIP(s) - The PR was approved or written by at least one author of each modified EIP - The build is passing
This commit is contained in:
parent
822bc28f66
commit
f29527ab39
|
@ -67,7 +67,7 @@ A good hashing algorithm should satisfy security properties such as determinism,
|
|||
|
||||
### Transactions and bytestrings
|
||||
|
||||
An illustrative example of the above breakage can be found in Ethereum. Ethereum has two kinds of messages, transactions `𝕋` and bytestrings `𝔹⁸ⁿ`. These are signed using `eth_sendTransaction` and `eth_sign` respectively. Originally the encoding function `encode : 𝕋 ∪ 𝔹⁸ⁿ → 𝔹⁸ⁿ` was as defined as follows:
|
||||
An illustrative example of the above breakage can be found in Ethereum. Ethereum has two kinds of messages, transactions `𝕋` and bytestrings `𝔹⁸ⁿ`. These are signed using `eth_sendTransaction` and `eth_sign` respectively. Originally the encoding function `encode : 𝕋 ∪ 𝔹⁸ⁿ → 𝔹⁸ⁿ` was defined as follows:
|
||||
|
||||
* `encode(t : 𝕋) = RLP_encode(t)`
|
||||
* `encode(b : 𝔹⁸ⁿ) = b`
|
||||
|
@ -93,7 +93,7 @@ Both determinism and injectiveness would be trivially true if `len(b)` was left
|
|||
|
||||
The `eth_sign` call assumes messages to be bytestrings. In practice we are not hashing bytestrings but the collection of all semantically different messages of all different DApps `𝕄`. Unfortunately, this set is impossible to formalize. Instead we approximate it with the set of typed named structures `𝕊`. This standard formalizes the set `𝕊` and provides a deterministic injective encoding function for it.
|
||||
|
||||
Just encoding structs is not enough. It is likely that two different DApps use identical structs. When this happens, a signed message intended for one DApp would also be valid for the other. The signatures are compatible. This can be intended behaviour, in which case everything is fine as long as the DApps took replay attacks into consideration. If it is not intended, there is a security problem.
|
||||
Just encoding structs is not enough. It is likely that two different DApps use identical structs. When this happens, a signed message intended for one DApp would also be valid for the other. The signatures are compatible. This can be intended behaviour, in which case everything is fine as long as the DApps took replay attacks into consideration. If it is not intended, there is a security problem.
|
||||
|
||||
The way to solve this is by introducing a domain separator, a 256-bit number. This is a value unique to each domain that is 'mixed in' the signature. It makes signatures from different domains incompatible. The domain separator is designed to include bits of DApp unique information such as the name of the DApp, the intended validator contract address, the expected DApp domain name, etc. The user and user-agent can use this information to mitigate phishing attacks, where a malicious DApp tries to trick the user into signing a message for another DApp.
|
||||
|
||||
|
@ -157,7 +157,7 @@ If the struct type references other struct types (and these in turn reference ev
|
|||
|
||||
### Definition of `encodeData`
|
||||
|
||||
The encoding of a struct instance is `enc(value₁) ‖ enc(value₂) ‖ … ‖ enc(valueₙ)`, i.e. the concatenation of the encoded of the member values in the order that they apear in the type. Each encoded member value is exactly 32-byte long.
|
||||
The encoding of a struct instance is `enc(value₁) ‖ enc(value₂) ‖ … ‖ enc(valueₙ)`, i.e. the concatenation of the encoded member values in the order that they apear in the type. Each encoded member value is exactly 32-byte long.
|
||||
|
||||
The atomic values are encoded as follows: Boolean `false` and `true` are encoded as `uint256` values `0` and `1` respectively. Addresses are encoded as `uint160`. Integer values are sign-extended to 256-bit and encoded in big endian order. `bytes1` to `bytes31` are arrays with a beginning (index `0`) and an end (index `length - 1`), they are zero-padded at the end to `bytes32` and encoded in beginning to end order. This corresponds to their encoding in ABI v1 and v2.
|
||||
|
||||
|
@ -262,7 +262,7 @@ 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 EIP712 [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
|
||||
|
||||
|
@ -326,9 +326,9 @@ The domain separator prevents collision of otherwise identical structures. It is
|
|||
|
||||
The domain separator also allows for multiple distinct signatures use-cases on the same struct instance within a given DApp. In the previous example, perhaps signatures from both `from` and `to` are required. By providing two distinct domain separators these signatures can be distinguished from each other.
|
||||
|
||||
**Alternative 1**: Use the target contract address as domain separator. This solves the first problem, contracts coming up with identical types, but does not address second use-case. The standard does suggest implementors to use the target contract address where this is appropriate.
|
||||
**Alternative 1**: Use the target contract address as domain separator. This solves the first problem, contracts coming up with identical types, but does not address the second use-case. The standard does suggest implementors to use the target contract address where this is appropriate.
|
||||
|
||||
The function `hashStruct` starts with a `typeHash` to separate types. By giving different types a different prefix the `encodeData` function only has to be injective for within a given type. It is okay for `encodeData(a)` to equal `encodeData(b)` as long as `typeOf(a)` is not `typeOf(b)`.
|
||||
The function `hashStruct` starts with a `typeHash` to separate types. By giving different types a different prefix the `encodeData` function only has to be injective within a given type. It is okay for `encodeData(a)` to equal `encodeData(b)` as long as `typeOf(a)` is not `typeOf(b)`.
|
||||
|
||||
### Rationale for `typeHash`
|
||||
|
||||
|
@ -339,11 +339,11 @@ bytes32 constant MAIL_TYPEHASH = keccak256(
|
|||
"Mail(address from,address to,string contents)");
|
||||
```
|
||||
|
||||
For the type hash several alternatives where considered and rejected for the reasons:
|
||||
For the type hash several alternatives were considered and rejected for the reasons:
|
||||
|
||||
**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` revers 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 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 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;}`).
|
||||
|
||||
|
@ -392,7 +392,7 @@ function hashStruct(Mail memory mail) pure returns (bytes32 hash) {
|
|||
}
|
||||
```
|
||||
|
||||
The in-place implementation makes strong but reasonable assumptions on the memory layout of structs in memory. Specifically it assume structs are not allocated below address 32, that members are stored in order, that all values are padded to 32-byte boundaries, and that dynamic and reference types are stored as a 32-byte pointers.
|
||||
The in-place implementation makes strong but reasonable assumptions on the memory layout of structs in memory. Specifically it assumes structs are not allocated below address 32, that members are stored in order, that all values are padded to 32-byte boundaries, and that dynamic and reference types are stored as a 32-byte pointers.
|
||||
|
||||
**Alternative 6**: Tight packing. This is the default behaviour in Soldity when calling `keccak256` with multiple arguments. It minimizes the number of bytes to be hashed but requires complicated packing instructions in EVM to do so. It does not allow in-place computation.
|
||||
|
||||
|
@ -400,7 +400,7 @@ The in-place implementation makes strong but reasonable assumptions on the memor
|
|||
|
||||
**Alternative 8**: Leave `typeHash` out of `hashStruct` and instead combine it with the domain separator. This is more efficient, but then the semantics of the Solidity `keccak256` hash function are not injective.
|
||||
|
||||
**Alternative 9**: Support cyclical data structures. The current standard is optimized for tree-like data structures and undefined for cyclical data structures. To support cyclical data a stack containing the path to the current node needs to be maintained and a stack offset substituted when a cycle is detected. This is prohibitively more complex to specify and implement. It also breaks composability where the hashes of the member values are used to construct the hash of the struct (the hash of the member values would dependent on the path). It is possible to extend the standard in a compatible way to define hashes of cyclical data.
|
||||
**Alternative 9**: Support cyclical data structures. The current standard is optimized for tree-like data structures and undefined for cyclical data structures. To support cyclical data a stack containing the path to the current node needs to be maintained and a stack offset substituted when a cycle is detected. This is prohibitively more complex to specify and implement. It also breaks composability where the hashes of the member values are used to construct the hash of the struct (the hash of the member values would depend on the path). It is possible to extend the standard in a compatible way to define hashes of cyclical data.
|
||||
|
||||
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.
|
||||
|
||||
|
@ -408,7 +408,7 @@ Similarly, a straightforward implementation is sub-optimal for directed acyclic
|
|||
|
||||
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.
|
||||
|
||||
A field `string eip719dsl` can added and be rejected if the value does not match the hash of the [EIP-719][eip719] DSL interface string.
|
||||
A field `string eip719dsl` can be added and be rejected if the value does not match the hash of the [EIP-719][eip719] DSL interface string.
|
||||
|
||||
[eip719]: https://github.com/ethereum/EIPs/issues/719
|
||||
|
||||
|
@ -424,7 +424,7 @@ The Solidity expression `keccak256(someInstance)` for an instance `someInstance`
|
|||
|
||||
<!-- Test cases for an implementation are mandatory for EIPs that are affecting consensus changes. Other EIPs can choose to include links to test cases if applicable. -->
|
||||
|
||||
An example contract can be found in [Example.sol][ex-sol] and an example implementation of signing in Javascrtip in [Example.js][ex-js]
|
||||
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
|
||||
|
|
Loading…
Reference in New Issue