DEV Community

Cover image for EthersJS - Signing EIP712 Typed Structs
zemse
zemse

Posted on • Updated on

EthersJS - Signing EIP712 Typed Structs

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.providers.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

Top comments (3)

Collapse
 
alinobrasil profile image
ali kim

What if your struct contains an array of another struct?
eg. In your "mail", what if "to" contains multiple Persons like Bob and Charlie.

Can you show an example?

I've been trying this all day and can't get it to work.

Collapse
 
klee727 profile image
Klee727

Try this:

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

Collapse
 
iamgauravpant profile image
iamgauravpant

I am using ethersv6 ( with Hardhat ^2.19.4 )
signer._signTypedData didn't worked for me , signer.signTypedData() is working .
ethers.utils.verifyTypedData is now ethers.verifyTypedData .