“与其感慨路难行,不如马上出发。”
EIP-712 Example

合约代码

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

struct Mail {
    address from;
    address to;
    string content;
}

contract EIP712Example {
    bytes32 private DOMAIN_SEPARATOR;
    // keccak256("Mail(address from,address to,string content)")
    bytes32 private constant TYPEHASH = 0xa91a5eed6664cc06f977dacf513eaa9b859c813f1b8fc8002c04b493bff7df1b;

    constructor() {
        uint chainId;
        assembly {
            chainId := chainid
        }
        DOMAIN_SEPARATOR = keccak256(
            abi.encode(
                keccak256('EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)'),
                keccak256(bytes('Mail')),
                keccak256(bytes('1')),
                chainId,
                address(this)
            )
        );
    }

    // 验证签名
    function verifyMessage(Mail memory message, uint8 v, bytes32 r, bytes32 s) public view returns (bool) {
        bytes32 digest = keccak256(abi.encodePacked(
            "\x19\x01",
            DOMAIN_SEPARATOR,
            keccak256(abi.encode(
                TYPEHASH,
                message.from,
                message.to,
                keccak256(bytes(message.content))
            ))
        ));

        address recoveredAddress = ecrecover(digest, v, r, s);

        return (recoveredAddress == message.from);
    }
}

前端代码

import { ethers } from "ethers";

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

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

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

// 签名
const signature = await signer.signTypedData(domain, types, mail);

// 验证签名
const expectedSignerAddress = signer.address;
const recoveredAddress = ethers.verifyTypedData(domain, types, mail, signature);
console.log(recoveredAddress === expectedSignerAddress);