EIP-712 structured data hashing and signing explained

EIP-712 structured data hashing and signing explained

Ethereum for identity verification

Cryptography is at the heart of blockchain technology. An identity in ethereum is represented by public-private keypair. Using this keypair and asymmetric cryptography, the origin of a transaction and its integrity are established. How? Let me try to explain it quickly.

Let’s say that I have a public-private key pair. While sending the transaction to the network, I will first sign the transaction payload/object using my private key. This signing results in a digest that is unique. There is a reason why it is called a signature! Now, in order to make sure that my transaction is legitimate and is not coming from my spoofed identity, my public key, and thereby, my public account address can be recovered from this signature. Amazing right?

But, who said, we can only sign transaction payloads using a private key? After all, it’s the ECDSA cryptographic algorithm. The same concept can be extended to generate signatures from different types of objects/messages. Now, this opens a door for a large set of applications and possibilities. For authenticating the user identities, randomly generated byte strings can be signed, and then signatures can be used for verification.

One more thing to be noted, signing and verification can happen completely off-chain without interacting with the Ethereum network. Signing and the verification of ECDSA-signed messages allow tamper-proof communications outside of the blockchain.

Traditional signature scheme drawbacks

The legacy way of signing messages uses method exposed by ethereum JSON-RPC clients. The approach only allows the signing of byte-strings. This obviously, messes up with the user experience. Why? In particular, the method eth_sign is an open-ended signing method that allows signing of an arbitrary hash, which means it can be used to sign transactions or any other data, making it a dangerous phishing risk.

As a quick example, let’s use the metamask to sign the random string. Copy and paste the following snippet in your browser’s console. Of course, make sure that you have metamask installed and configured.

window.ethereum.enable();

const message = 'This is the random message to be signed';

const signer = window.ethereum.selectedAddress;

window.ethereum.request({ method: 'eth_sign', params: [signer, message] })

The string that we wanted to sign was ‘’This is the random message to be signed’’. This gets converted to a hex-formatted byte string which is then displayed to the user. Since this is exactly the way, transactions are signed in Ethereum, signing just anything without verifying the byte-string which is being signed, can lead to some tricky and undesired scenarios. Exactly why, metamask displays this red colored warning.

EIP-712 and eth_signTypedData

EIP-712 proposes the standard structure of the data and the defined process of generating the hash out of this structured message. This hash is then used for generating signatures. This way there is a clear distinction between the signatures generated for sending the transactions and signatures generated for verifying the identities or for any other purposes. EIP-712 draft states the motivation behind the signature scheme as:

To improve the usability of off-chain message signing for use on-chain. We are seeing growing adoption of off-chain message signing as it saves gas and reduces the number of transactions on the blockchain. Currently signed messages are an opaque hex string displayed to the user with little context about the items that make up the message.

EIP-712 is a standard for hashing and signing of typed structured data as opposed to just byte strings. It includes a

  • A theoretical framework for the correctness of encoding functions,

  • specification of structured data similar to and compatible with Solidity structs,

  • safe hashing algorithm for instances of those structures,

  • safe inclusion of those instances in the set of signable messages,

  • an extensible mechanism for domain separation,

  • new RPC call eth_signTypedData, and

  • an optimized implementation of the hashing algorithm in EVM.

See the last point? Exactly why, the message verification on-chain is extremely efficient and thereby requires minimal gas.

What is typed data?

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.

{
  type: 'object',
  properties: {
    types: {
      type: 'object',
      properties: {
        EIP712Domain: {type: 'array'},
      },
      additionalProperties: {
        type: 'array',
        items: {
          type: 'object',
          properties: {
            name: {type: 'string'},
            type: {type: 'string'}
          },
          required: ['name', 'type']
        }
      },
      required: ['EIP712Domain']
    },
    primaryType: {type: 'string'},
    domain: {type: 'object'},
    message: {type: 'object'}
  },
  required: ['types', 'primaryType', 'domain', 'message']
}

Using Metamask to sign EIP-712 typed data

So, let's create the typed data object to be signed using the above JSON schema.

let domain = [
    {name: "name", type: "string" },
    {name: "version", type: "string"},
    {name: "chainId", type: "uint256"}
]

let mail = [
    {"name": 'content', "type": 'string'},
]

let domainData = {
    name: "My Dapp",
    version: "1",
    chainId: parseInt(web3.version.network, 10)
}

let message = {
    content: 'This is the mail content'
}

let eip712TypedData = {
    types: {
        EIP712Domain: domain,
        Mail: mail
    },
    domain: domainData,
    primaryType: "Mail",
    message: message
}

Cool, let's send this object to metamask for signing this data:

let data = JSON.stringify(eip712TypedData)

signer = window.ethereum.selectedAddress

window.ethereum.request({
  method: 'eth_signTypedData_v4',
  params: [signer, data],
  from: signer
}

Metamask will open a prompt asking for signature:

Once this promise is resolved, metamask will return the signature which looks something like:

0x3c7dd1d7366d96b2935d25e92734b32e0497f708c7013fc99354b96d96fa39332f6e78c7f5a2e8ed2209af9b29b8d046a7a98e02d80463aa5ea69137a414545d1c

Now, wherever we want some functionality related to proving identity and ownership, we can leverage this signing scheme.

Generating EIP-712 typed message hash

I have created a javascript module to generate the typed hashes out of typed objects. Call generateEip712Hash() function and pass a typed object as a parameter. It will return the hash of the typed object as per the EIP-712 standard. This hash can then be signed using the private key to generate a unique signature.

% [ https://gist.github.com/ashwinYardi/90d2b8801c52a60796999c2642fa9550 ]

https://gist.github.com/ashwinYardi/90d2b8801c52a60796999c2642fa9550

Recovering EIP-712 signatures

What does recovering the signature mean exactly? In a nutshell, after signing the original message hash, a signature is generated. Using the signature and original message hash, we can extract the public key of the signer. Pretty cool, right?

The address is rightmost 160-bits or 20 bytes of the Keccak hash of the corresponding public key. So, we can have a particular user sign a unique message using the private key ( which is usually protected by identity vaults like Metamask ) and then can use the corresponding public key or address to verify the ownership and validity of the signature.

Metamask team has developed the eth-sig-utils module which exposes the API to recover the signature. If the message has been signed using signTypedData_v4 method, then we can use recoverTypedSignature_V4. If a message has been signed using signTypedData method, then we can use the recoverTypedSignature.

Message signature examples given in this article are as per the latest EIP-712 specification and we will need to use recoverTypedSignature_V4. This API takes the following object as an argument

{
    data: 'xxxxxxxx', // This is the EIP-712 message hash
    sig: '0x1hnvn8787wen......' // 0x padded signature
}

All right folks! EIP-712 signatures are also very efficient to be verified on-chain i.e. through smart contracts. I will try to cover how it can be done in another article. Great stuff! So, in this article, we quickly glanced through what are signatures, potential drawbacks, risks associated with traditional signature schemes, how EIP-712 solves this problem and how EIP-712 signatures are generated/recovered off-chain. I hope, you will find this article helpful. Shoot me with all your questions, feedback, and concerns if you have any. Take care and stay safe!

Originally published at https://bitsbyblocks.com/ on October 9, 2020.