EIP712

当然!EIP-712 是以太坊的一项标准,它定义了一个结构化的消息签名方案,以便能够安全、标准化地对复杂数据进行签名和验证。简单来说,它是一个用于签名复杂数据结构(如结构化对象、数组、嵌套对象等)并保证其不可篡改的标准。

🌟 EIP-712 概述

EIP-712 规范提出了对结构化数据进行签名的方法,解决了以下问题:

  1. 避免重放攻击:如果没有结构化数据的签名,可能存在相同的签名被滥用的风险。
  2. 易于解析和验证:签名的对象可以被预先约定和验证,避免直接签名原始字节流。
  3. 提升用户体验:用户可以明确知道自己签署的内容,减少误签和恶意签名的风险。

🔍 EIP-712 的核心概念

EIP-712 通过将数据分为两部分来创建安全的签名:

  1. Domain Separator(域分隔符):标识签名数据的来源。通过引入这个“域”概念,可以区分不同的签名场景,防止重放攻击。例如,一个签名用于交易,另一个用于账户管理,它们的域分隔符是不同的,彼此之间互不干扰。

  2. 类型哈希:对数据结构的类型进行哈希,确保签名的数据结构不被篡改。

  3. 数据结构:EIP-712 签名的数据本身是结构化的,数据包括了 typevaluedomain(域)等信息,所有内容都有严格的格式。

📜 EIP-712 的使用过程

1. 定义结构化数据

EIP-712 要求签名的数据必须是结构化的(如 struct 或 JSON 对象)。举个例子:

struct Order {
address userWallet;
uint256 amount;
uint256 nonce;
uint256 expiry;
}

2. 计算域分隔符

域分隔符(Domain Separator)通常包含以下信息:

  • 合约地址(或域名)
  • 合约版本
  • 合约链 ID
  • 其他一些身份标识信息

通过域分隔符可以确保签名只适用于特定的域,而不会被重放到其他地方。

例如:

bytes32 DOMAIN_SEPARATOR = keccak256(abi.encode(
keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
keccak256("MyDapp"),
keccak256("1"),
block.chainid,
address(this)
));

3. 计算类型哈希

每种类型的结构数据都有一个对应的类型哈希(Type Hash),它是通过 keccak256 对结构的声明进行哈希计算得出的。比如,对于 Order 结构:

bytes32 ORDER_TYPEHASH = keccak256(
"Order(address userWallet,uint256 amount,uint256 nonce,uint256 expiry)"
);

4. 组合哈希

组合域分隔符、类型哈希和数据本身,计算出最终的消息哈希:

bytes32 dataHash = keccak256(abi.encode(
ORDER_TYPEHASH,
order.userWallet,
order.amount,
order.nonce,
order.expiry
));
bytes32 hash = toTypedDataHash(DOMAIN_SEPARATOR, dataHash);

这个哈希值就是签名所需的数据。

5. 签名

使用 ECDSA(椭圆曲线数字签名算法)对上述生成的消息哈希进行签名:

bytes memory signature = ECDSA.sign(hash, privateKey);

签名完成后,消息和签名可以被发送到智能合约进行验证。

6. 验证签名

在合约中,接收到数据和签名后,使用 ECDSA.recover 恢复签名者地址,并验证是否与期望的地址一致:

address signer = ECDSA.recover(hash, signature);
require(signer == expectedSigner, "Invalid signature");

🧑‍💻 EIP-712 例子:

假设你要在智能合约中实现订单系统,订单结构为:

struct Order {
address user;
uint256 amount;
uint256 nonce;
uint256 expiry;
}

你可以按以下步骤使用 EIP-712 来签名和验证该订单:

  1. 定义数据结构和类型哈希:
bytes32 private constant ORDER_TYPEHASH = keccak256(
"Order(address user,uint256 amount,uint256 nonce,uint256 expiry)"
);
  1. 计算域分隔符:
bytes32 DOMAIN_SEPARATOR = keccak256(abi.encode(
keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
keccak256("MyDapp"),
keccak256("1"),
block.chainid,
address(this)
));
  1. 生成订单数据哈希:
bytes32 orderHash = keccak256(abi.encode(
ORDER_TYPEHASH,
order.user,
order.amount,
order.nonce,
order.expiry
));
  1. 生成最终哈希:
bytes32 hash = MessageHashUtils.toTypedDataHash(DOMAIN_SEPARATOR, orderHash);
  1. 签名和验证:
address signer = ECDSA.recover(hash, signature);
require(signer == expectedSigner, "Invalid signature");

EIP-712 的优势

  • 安全性:通过结构化的数据和域分隔符避免了签名的篡改和重放攻击。
  • 用户体验:用户可以明确看到他们签名的数据内容,避免不必要的误签。
  • 跨链和跨平台一致性:EIP-712 使得不同链或应用间能够一致地处理结构化数据的签名和验证。

🏆 总结

EIP-712 允许以太坊智能合约验证结构化数据的签名,避免了直接对原始字节流进行签名的风险。它通过域分隔符、类型哈希和结构化数据,提供了一个安全、灵活、易用的签名机制,广泛应用于 DApp、DeFi、NFT 等领域。