Skip to content

A utility for generating Solidity code for recovering signatures using the EIP-712 signTypedData schema.

Notifications You must be signed in to change notification settings

xinbenlv/eip712-codegen

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

22 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

EIP 712 Codegen

EIP 712: Sign Typed Data as of 2022 is the most human-readable way of getting signatures from user that are easily parsed into solidity structs.

The documentation has not always been the greatest, and in particular I think this method has failed to catch on because writing the verification code is a huge pain.

Well, no more. This module will generate basically all the solidity you need to let users basically sign structs and then mostly just think about the structs you have signed in your code. This should really level up your ability to keep more user actions off-chain and gas-free.

Usage

Add this module to your project: npm i eip712-codegen -D or yarn add eip712-codegen -D.

As a module:

const codeGen = require('eip712-codegen');
const yourTypes = { primaryMessage, domain, entries, types };
const solidityFile = codeGen(yourTypes);

As a module, we are exporting typescript definition files, which can help you get your types right in case the example type file isn't enough.

As a CLI tool:

You point it at a typeDef file (defined as a CommonJS module, as seen in sampleTypes.js), and it then prints out some solidity to the console. You can then pipe it into a file.

Examples:

npx eip712-codegen ./sampleTypes.js > YourTypesFile.sol

If you're using hardhat and their console.log feature, you can generate a logged version by adding log:

npx eip712-codegen ./sampleTypes.js log > YourTypesFile.sol

You'll then need to import this typefile into your contract, and inherit from EIP712Decoder.

pragma solidity ^0.8.13;
// SPDX-License-Identifier: MIT

import "./YourTypesFile.sol";
import "./caveat-enforcers/CaveatEnforcer.sol";

abstract contract Delegatable is EIP712Decoder {

You'll also need to include this one method that defines your DomainHash, which I'm leaving to you because it's pretty straightforward, you can copy paste this and change it:

  bytes32 public immutable domainHash;
  constructor (string memory contractName, string memory version) {
    domainHash = getEIP712DomainHash(contractName,version,block.chainid,address(this));
  }

  function getEIP712DomainHash(string memory contractName, string memory version, uint256 chainId, address verifyingContract) public pure returns (bytes32) {
    bytes memory encoded = abi.encode(
      EIP712DOMAIN_TYPEHASH,
      keccak256(bytes(contractName)),
      keccak256(bytes(version)),
      chainId,
      verifyingContract
    );
    return keccak256(encoded);
  }
}

There's one more thing you have to do, this part will require the most thinking. You'll have to write the method that verifies the top-level signatures. I have not written codegen for this yet, because I don't know which types you want to use as your entry points, and there are some design decisions that are up to you here (in particular, your entrypoint types are your user-facing security enforcement, but here is a sample method for verifying a SignedDelegation as defined in our sampleTypes.js file:

  function verifyDelegationSignature (SignedDelegation memory signedDelegation) public view returns (address) {

    // Break out the struct that was signed:
    Delegation memory delegation = signedDelegation.delegation;

    // Get the top-level hash of that struct, as defined just below:
    bytes32 sigHash = getDelegationTypedDataHash(delegation);

    // The `recover` method comes from the codegen, and will be able to recover from this:
    address recoveredSignatureSigner = recover(sigHash, signedDelegation.signature);
    return recoveredSignatureSigner;
  }

  function getDelegationTypedDataHash(Delegation memory delegation) public view returns (bytes32) {
    bytes32 digest = keccak256(abi.encodePacked(
      "\x19\x01",

      // The domainHash is derived from your contract name and address above:
      domainHash,

      // This last part is calling one of the generated methods.
      // It must match the name of the struct that is the `primaryType` of this signature.
      GET_DELEGATION_PACKETHASH(delegation)
    ));
    return digest;
  }

From there, you should be good! This library is tested to work with eth_signTypedData_v4 as implemented in MetaMask. I have not yet tested it with ethers.js or other wallets, but there's a good chance it works for simple types, and a chance it works for arrays and structs as well.

About

A utility for generating Solidity code for recovering signatures using the EIP-712 signTypedData schema.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • JavaScript 56.8%
  • TypeScript 43.2%