合约代码
// 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);