EIP-712 Link to heading

EIP-712 解决的核心问题:结构化数据签名与域分隔 Link to heading

在以太坊中,直接对任意消息签名以及对交易进行签名都使用了相同的 ECDSA 算法,但缺少标准化的“结构化数据”协议,导致:

  • 离线签名的数据缺乏明确格式,易被重放或误用。
  • 不同 DApp/合约签名域不一致,难以让用户直观看懂签名内容。
  • 无法在多链或合约升级场景下防止重放攻击。

EIP-712 引入“Typed Structured Data”机制,通过:

  1. 定义 域分隔符(Domain Separator),包含合约名称、版本、链 ID、合约地址等信息,实现域隔离。
  2. 定义 Typed Data 结构体类型TypeHash,明确字段及顺序,提高可读性和安全性。
  3. "\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 提案等场景。