DEV Community

loading...
Cover image for EthersJS - Signing EIP712 Typed Structs

EthersJS - Signing EIP712 Typed Structs

Soham Zemse
I could call myself a self-taught programmer but I learned coding through online tutorials created by generous programmers. Created account on this site trying to pass it on.
・2 min read

Heya! In this post we're going to go a quick look into using EthersJS API for the EIP712 standard.

Also, before we dive in, I would like to point out that if your use case is simple as a single message, you likely do not need to complicate it with EIP712. You can simply use signer.signMessage("hello") API (EIP191). While if you have multiple values (like complex structs) to sign, then EIP712 can be useful.

Setting up signer

Consider a use case where a user want to sign a complex struct. They also need to include a signature to prove that they own the private keys of an account containing some funds.

You can use Metamask. But if you're just trying for demo, you can just generate a random wallet.

const metamaskProvider = new ethers.provider.Web3Provider(window.ethereum);
const signer = metamaskProvider.getSigner();

// or

const signer = ethers.Wallet.createRandom()
Enter fullscreen mode Exit fullscreen mode

Defining Domain Separator

Now we define our domain separator. This is useful for the case when there is a fork/clone of your app on your chain or even a different one, and some user using both the apps. The domain separator ensures different signatures for same messages by same user.

const domain = {
  name: 'My App',
  version: '1',
  chainId: 1,
  verifyingContract: '0x1111111111111111111111111111111111111111'
};
Enter fullscreen mode Exit fullscreen mode

Defining Struct Types

const types = {
  Mail: [
    { name: 'from', type: 'Person' },
    { name: 'to', type: 'Person' },
    { name: 'content', type: 'string' }
  ],
  Person: [
    { name: 'name', type: 'string' },
    { name: 'wallet', type: 'address' }
  ]
};
Enter fullscreen mode Exit fullscreen mode

Signing struct objects

const mail = {
  from: {
     name: 'Alice',
     wallet: '0x2111111111111111111111111111111111111111'
  },
  to: {
     name: 'Bob',
     wallet: '0x3111111111111111111111111111111111111111'
  },
  content: 'Hello!'
};
Enter fullscreen mode Exit fullscreen mode

To sign the above struct, we use _signTypedData method on the signer.

const signature = await signer._signTypedData(domain, types, mail);
// 0x74ff2b1850dfa49f825a29760cf7f8194465190481afb49dd81f0ef7783d7b9638180110bdbf5d85ab8d9f39d2d4f4bc63f017c5b53e90bf915cf22c2b7125901b
Enter fullscreen mode Exit fullscreen mode

This contains an underscore prepend because of the fact that it's recently introduced and is is in public beta. Once enough confidence in it, it will be moved out of beta by renaming to signTypedData (don't worry the underscore alias will also exist for backwards compatibility).

Verifying signatures

You can use the verifyTypedData util to verify if a given signature is valid.

const expectedSignerAddress = signer.address;
const recoveredAddress = ethers.utils.verifyTypedData(domain, types, mail, signature);
console.log(recoveredAddress === expectedSignerAddress);
// true
Enter fullscreen mode Exit fullscreen mode

Summary

That's pretty much it for this post. To summarise, we saw

  • how to define a domain separator
  • struct types
  • signing our struct object
  • verifying a given signature

Discussion (0)