ERC-191:数据签名标准

简要说明

该 ERC 提出了关于如何处理以太坊合约种签名数据的规范

原文章:https://eips.ethereum.org/EIPS/eip-191

动机

已经创建了几种接受presigned(预签名)交易的多重签名钱包实现。预签名交易presigned是一块二进制的signed_data,以及签名(rsv)。尚未指定signed_data的解释,导致了以下几个问题:

  • 标准以太坊交易可以作为提交signed_data以太坊交易可以被解包为以下组件:RLP<nonce, gasPrice, startGas, to, value, data>(在这里成为RLPdata),rsv如果signed_data没有语法约束,这意味着:RLPdata可以用作语法上有效的预签名交易。
  • 多重签名钱包还存在一个问题,即预签名交易并未与特定验证器(即特定钱包)绑定如:
    1. 用户 A、B、C 拥有 2/3 钱包 X
    2. 用户 A、B、D 拥有 2/3 钱包 Y
    3. 用户 A 和 B 向 X 提交预签名交易
    4. 攻击中现在就可以将预签名交易重用到 X。并提交给 Y。

规范

我们建议 signed_data 采用下面的格式:

0x19 <1 bytes version> <version specific data> <data to sign>

初始0x19字节旨在确保signed_data不是有效的 RLP

对于值在 [0x00, 0x7f] 范围内的单个字节,该字节是其自己的 RLP 编码

这意味着任何 signed_data 都不能是一个 RLP 结构。而是一个 1 字节的 RLP payload,后跟其他内容。因此,任何 EIP-191 signed_data 永远不可能是以太坊交易。

此外,选择0x19是因自 ethereum/go-ethereum#2940 以来,在 personal_sign 种的哈希之前添加了以下内容:

"\x19Ethereum Signed Message:\n" + len(message).

因此,使用 0x19 可以通过定义版本 0x45(E) 来扩展该方案来处理此类签名。

版本字节表

版本字节 EIP 描述
0x00 191 具有预期验证器的数据
0x01 712 结构化数据
0x45 191 personal_sign消息

Version 0x00

0x19 <0x00> <intended validator address> <data to sign>

这个版本具有用于版本特定数据的<intended validator address>。对于基本传递的签名执行的多重签名钱包,验证器地址是多重签名本身的地址。要签名的数据可以是任意的

Version 0x01

这个版本适用于 EIP-712 种定义的结构化数据。

Version 0x45(E)

0x19 <0x45 (E)> <thereum Signed Message:\n" + len(message)> <data to sign>

该版本有 <thereum Signed Message:\n" + len(message)>用于版本特定数据。要签名的数据可以是任意数据。

注意:以太坊签名消息中的 E 指的是版本字节 0x45。字符 E 是十六进制的 0x45,它的余数为 Siigned Message:\n + len(message),即版本特定数据

例子

下面的代码片段是用 Solidity 0.8.0 编写的

Version0x00

function signatureBasedExecution(address target, uint256 nonce, bytes memory payload, uint8 v, bytes32 r, bytes32 s) public payable {

	// 计算哈希验证时的参数
     // 1: byte(0x19) - 初始 0x19 字节
     // 2: byte(0) - 版本字节
     // 3: address(this) - 验证器地址
     // 4-6 : 应用程序特定数据
     
     bytes32 hash = keccak256(abi.encodePacked(byte(0x19), byte(0), address(this), msg.value, nonce, payload));
     
     // 从哈希值和签名中恢复签名者
     addressRecovared = ecrecover(hash, v, r, s);
     
     // 钱包逻辑
     // if (addressRecovared == owner) executeOnTarget(target, Payload);
}