EIP-712 Link to heading
EIP-712 解决的核心问题:结构化数据签名与域分隔 Link to heading
在以太坊中,直接对任意消息签名以及对交易进行签名都使用了相同的 ECDSA 算法,但缺少标准化的“结构化数据”协议,导致:
- 离线签名的数据缺乏明确格式,易被重放或误用。
- 不同 DApp/合约签名域不一致,难以让用户直观看懂签名内容。
- 无法在多链或合约升级场景下防止重放攻击。
EIP-712 引入“Typed Structured Data”机制,通过:
- 定义 域分隔符(Domain Separator),包含合约名称、版本、链 ID、合约地址等信息,实现域隔离。
- 定义 Typed Data 结构体类型 和 TypeHash,明确字段及顺序,提高可读性和安全性。
- 将
"\x19\x01" || domainSeparator || structHash做 keccak256,作为最终签名摘要。
EIP-712 通用签名结构 Link to heading
hash = keccak256(
"\x19\x01",
domainSeparator,
structHash
)
"\x19\x01":固定前缀,保证不会与其他签名冲突。domainSeparator:EIP712Domain 类型的 hash,包括:- name:签名域名称(DApp 或协议名)
- version:域版本号
- chainId:链 ID
- verifyingContract:验证合约地址
structHash:具体业务数据结构的 hash,计算方式:其中structHash = keccak256( abi.encode( TYPE_HASH, field1, field2, … ) )TYPE_HASH = keccak256("StructName(type1 name1,type2 name2,…)")。
示例 Link to heading
Solidity 合约示例 Link to heading
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/utils/cryptography/EIP712.sol";
import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
contract OrderBook is EIP712 {
struct Order {
address seller;
address buyer;
uint256 amount;
string asset;
}
// TypeHash
bytes32 private constant ORDER_TYPEHASH =
keccak256("Order(address seller,address buyer,uint256 amount,string asset)");
constructor() EIP712("MyOrderBook", "1") {}
// 计算 structHash 并拼接 domainSeparator
function _hashOrder(Order memory order) internal view returns (bytes32) {
return _hashTypedDataV4(keccak256(
abi.encode(
ORDER_TYPEHASH,
order.seller,
order.buyer,
order.amount,
keccak256(bytes(order.asset))
)
));
}
// 验证签名
function verifyOrder(Order memory order, bytes memory signature)
public view returns (address)
{
bytes32 digest = _hashOrder(order);
return ECDSA.recover(digest, signature);
}
// 填单例子
function fillOrder(Order memory order, bytes memory signature) external {
address signer = verifyOrder(order, signature);
require(signer == order.seller, "签名者非卖家");
// ... 执行业务逻辑
}
}
前端(ethers.js)签名示例 Link to heading
import { ethers } from "ethers";
async function signOrder() {
const provider = new ethers.providers.Web3Provider(window.ethereum);
const signer = provider.getSigner();
const domain = {
name: "MyOrderBook",
version: "1",
chainId: await signer.getChainId(),
verifyingContract: "0xYourContractAddress",
};
const types = {
Order: [
{ name: "seller", type: "address" },
{ name: "buyer", type: "address" },
{ name: "amount", type: "uint256" },
{ name: "asset", type: "string" }
]
};
const value = {
seller: "0xSellerAddress",
buyer: "0xBuyerAddress",
amount: ethers.utils.parseUnits("100.0", 18),
asset: "0xTokenContractAddress"
};
// 调用 eth_signTypedDataV4 签名
const signature = await signer._signTypedData(domain, types, value);
console.log("签名:", signature);
}
小结 Link to heading
- EIP-712 通过域分隔和结构化类型,提升离线签名的安全性和可读性。
- 开发者可按需自定义数据结构,只需保证合约和前端字段定义一致。
- 广泛应用于 Permit、元交易、去中心化订单、DAO 提案等场景。