EIP-712: eth_signTypedData as a standard for machine-verifiable and human-readable typed data signing

It’s a requirement if 1959 or 1965 is adopted. It is not if 1344 is adopted instead. I think the decision to update this specification relies on the outcome of what is adopted in that case.

For certain use cases, this is acceptable. For some, it is actually more ideal.

An example of the former is the meta-transaction use case, where chainId is used as a domain separator for offline signing. These transactions are meant to be resolved in under a day, so if an older value of chainId is used, it is acceptable to ask the user to re-submit with the new value.

An example of the later is Plasma Transactions. In that use case, it is required that the value of chainId chosen aligns with the value that the operator uses in their block submission summary transaction, therefore it is important that the operator has control over what value is accepted by the contract (can be updated trustlessly, during the block submission transaction). chainId might be updated in between submissions, therefore it is important that the operator only accept submissions with the correct value of chainId that they will upload, and reject others. Having it be either/or would be more dangerous.

We can continue this conversation in the EIP-1344 thread, as it relates to prior discussion we’ve had on this exact topic. My point was not to prematurely optimize for an EIP that is not yet confirmed to be in the next hard fork, as we are still figuring out which path will be chosen.

An example of the former is the meta-transaction … it is acceptable to ask the user to re-submit with the new value.

Acceptable but not desirable

An example of the later is Plasma Transactions. …the operator has control over what value is accepted by the contract (can be updated trustlessly, during the block submission transaction).

As already mentioned in our previous conversation, such chainId information can be provided by other means. It does not need to be coupled with EIP-712 where the role of chainId is replay protection

I agree, the reason I posted my 3 points here is that I realized not everywhere is aware of the current dicussion about chainID. EIP-712 was designed before that.

I think the decision to update this specification relies on the outcome of what is adopted in that case.

My goal was not to update the spec, it was just to bring awareness to the current discussion. Did not want EIP-712 to get finalised without that info first.

1 Like

Can’t we simply re-use the EIP-712 hashStruct for byte arrays? So long as one of the contract down the line can interpret this embeded hashStruct, the original contract called doesn’t need to know what this hashStruct is. It will simply take the hash of that byte array before doing an EIP-712 hash, like specified in the spec and can then pass that bytes array to another contract, which understands what the structure of that bytes array is.

We could also use the functions structure in some instances, where the first 4 bytes are a “function signature” (e.g. bytes4(keccak256(buyTickets(unit256 _amount)) and the rest of the bytes array would be the encoded arguments for that “function signature”. Of course, the bytes4 don’t need to be functions per say either.

Reusing the logic of byte arrays for uint[], bytes32[] or any other array of “native types <= 1 word” is ok, but it goes against the whole proposal for arrays of struct (such as a Recipient[] where Recipient would be a struct with multiple entries).

I believe the logic would be to hash each entry, turning the Recipient[] into a bytes32[] and then hashing the bytes32[]. The drawback is that this cannot be done in place.

In the end, it’s pointless what I believe the logic should or should not be, as long as we have ONE way to do it that is part of the standard

Here’s maybe a way to do general array hashing: https://github.com/MetaMask/eth-sig-util/pull/54

Arrays of atomic types have been encoded as a hash of its contents, where its contents are word-aligned according to the type.

Arrays of dynamic types have been encoded as a hash of each element’s hash.

Arrays of structs retain their encoding as a hash of its contents’ concatenated hashStructs.

Arrays of arrays are encoded recursively, as a hash of the contained arrays’ hash encodings concatenated.


Additionally, how should clients handle missing values in an eth_signTypedData structure? eth-sig-util currently skips over fields which are missing, but it uses the same type hash, meaning a struct like Foo(uint256 bar,bytes32 baz) may only have hashStruct(typeHash(Foo) . baz) if bar is undefined on the message. In the PR, because it was simpler to do so in the rewrite, I’ve made every field specified in the typeHash required, but Dan wanted to know if there was a preference for the optional behavior.

Note that this is different from requiring fields like string name to be specified in the EIP712Domain struct. I’m asking about the behavior once the type has been specified whether or not those fields specified should be considered optional.

Personally, I think this should be an error, since using the Foo example, how do you know whether second part of the hashStruct payload refers to bar or baz?


I have a feeling that maybe asking to limit the scope of EIP-712 and introducing this in an EIP extension may be the way to go, as I may have heard of some hardware vendors claiming EIP-712 support, where for them it might actually mean something along the lines of “flat structures containing primitive types” support (I think somebody might have mentioned that being a EIP finalization target). If so, it would be good to get the opinion of relevant stakeholders in this discussion, as I would guess it wouldn’t be feasible to upgrade existing hardware that’s out there claiming EIP 712 support, but I haven’t read through the original discussion thread on Github yet, as it’s pretty long…

It could be that it’s just the crypto primitives being implemented in hardware though, and that software support for this may be added after the fact, in which making this a patch to the EIP could make sense instead.


I’ve done some looking into whether or not this extension can be added to hardware, and it seems that for the majority of the cases, hardware support is for building blocks, and support just has to be added on the Ethereum app level in the case of Ledger or at the firmware level in the case of Trezor. Since this is the case, and because the recent discussion seems to imply dynamic and nested types will be part of the final spec for this EIP, I’ll write an update to this EIP accordingly.


I’ve written a PR to ERC-712 here.

In addition to the changes quoted above, I’ve made all specified struct members mandatory except for structs. The reason why structs are not mandatory is because I’ve realized that the standard intended for recursive types to be possible. As an example, consider:

struct Node {
    bytes data;
    Node next;
}

This was a valid type in the standard, but it wasn’t clear how to encode instances of this type. With mandatory struct members, this type would not be possible to express. I’ve taken the liberty to state that given the example, the next field may either be encoded recursively as its structHash, or as bytes32(0) when expressing its absence from an instance.

2 Likes

Regarding ABIv2 for encodeData, I was investigating and asked Chris from the solidity dev team what he thought. Here is his response ;

Any insight @Recmo @dekz ?

1 Like

Link for the lazy: https://eips.ethereum.org/EIPS/eip-712

1 Like

That’s problematic. It’s a good thing there isn’t much uptake of this standard yet, although it is very useful and it would be good to clear any issues up.

1 Like

In case of a fork, the signatures would only work in the fork that didn’t changed chainId, to solve that, please use EIP-1344 in the 712 standard.

Hi everyone, I am not sure where is the best place to ask this so I’m trying here.
I’m looking at EIP-712 v4 and the scheme to encode arrays.

eth-sig-util is managing it well (and I guess correctly) on the client side but I can find no example on how to do that in solidity.

My understanding is that encoding an array of 3 strings arr = [“1”, “2”, “3”] is equialent to keccak256(abi.encode(keccak256(abi.encodePacked(arr[0])), keccak256(abi.encodePacked(arr[1])), keccak256(abi.encodePacked(arr[2])))

So first thing, is this correct? if yes, how can I implement that in solidity?

I’m sure you can do better / in place by writing it in assembly, but here is a “quick” answer:

function hash(string[] calldata array) internal pure returns (bytes32 result) {
    bytes32[] memory _array = new bytes32[](array.length);
    for (uint256 i = 0; i < array.length; ++i) {
        _array[i] = keccak256(bytes(array[i]));
    }
    result = keccak256(abi.encodePacked(_array));
}
1 Like

Hey everyone, I just wanted to bring up what I believe to be a minor issue in the eth_signTypedData JSON RPC specification. Specifically, it doesn’t go into much detail about the JSON representation of the message object and the supported Solidity primitive values.

For example, what is an acceptable encoding for a uint32? Are all 100, 1e2, "100", "0x64" accepted? Should they be treated like QUANTITIES so only "0x64" is valid? In this regard, MetaMask for example, seems to accept both hexadecimal and decimal strings for uint256.

While I don’t have any particular suggestion on what the supported formats should be, I do believe it should be specified in the EIP.

5 Likes

@Recmo @dekz Do you need help finishing this spec?

How are clients expected to know what parameters to use in the domain separator? This is easy when signing for a particular contract with a known domain separator, but there is no standard mechanism for use cases where it is not known beforehand.

For a concrete example, people have run into this problem in EIP-2612 (ERC20 permit). Some tokens include the version parameter (OpenZeppelin, USDC), others don’t (COMP), and those who do may use different values (OpenZeppelin: default "1", USDC: "2"). There is no way to query the contract if version is used and with what value. EIP-2612 includes a DOMAIN_SEPARATOR getter, but it would be wrong to use this value without verifying that it corresponds to the current chain id, for which all the parameters are necessary.

I imagine any other EIP that builds on EIP-712 signatures will run into the same issue. The only solution right now is to make it a part of those EIPs what the domain separator is supposed to look like, but I think it would be better to include a standard mechanism either in EIP-712 or in a complementary EIP.

The current draft of EIP-712 says:

Specification of the eth_signTypedData 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))).

I don’t understand the last line. Isn’t this supposed to be

sign(keccak256("\x19\x01" + domainSeparator + hashStruct(message)))

instead, as described in the “Specification” section above? The listed
definition describes eth_sign, not the new eth_signTypedData, and
does not seem consistent with Ethers’s TypedDataEncoder.encode (as
used in the Wallet implementation of Signer._signTypedData), nor
with OpenZeppelin’s ECDSA.toTypedDataHash.

Is this a copy-paste error in the spec, or am I missing something?

1 Like

Anyone has any examples of verifying eth_signTypedData_v4 in Solidity?

I’m using recoverTypedSignature_v4 from eth-sig-util and it works great but when I try to verify the signature with openzeppelin’s EIP712 draft, it returns a different address.

Draft EIPs - OpenZeppelin Docs

Of course I also tried to write it myself from the spec, but same results.

It would be nice if this proposal was finalized because then it would be much more usable.
I could only get v3 to work…

EIP-2613 (ERC20 Permit) is blocked from becoming a Final EIP because it has a dependency on EIP-712 which is still a Draft.

Are the EIP authors interested in moving EIP-712 to Final? @Recmo @dekz

1 Like

Any update about this proposal?

1 Like

I’ve gone ahead and created an EIP to tackle this issue I mentioned.

Please take a look and let me know your thoughts.

1 Like

EIP-712 is now in Last Call.

1 Like