EIP191
ERC-191:数据签名标准
简要说明
该 ERC 提出了关于如何处理以太坊合约种签名数据的规范
原文章:https://eips.ethereum.org/EIPS/eip-191
动机
已经创建了几种接受presigned
(预签名)交易的多重签名钱包实现。预签名交易presigned
是一块二进制的signed_data
,以及签名(r
、s
、v
)。尚未指定signed_data
的解释,导致了以下几个问题:
- 标准以太坊交易可以作为提交
signed_data
,以太坊交易可以被解包为以下组件:RLP<nonce, gasPrice, startGas, to, value, data>
(在这里成为RLPdata
),r
、s
、v
。如果signed_data
没有语法约束,这意味着:RLPdata
可以用作语法上有效的预签名交易。 - 多重签名钱包还存在一个问题,即预签名交易并未与特定验证器(即特定钱包)绑定如:
- 用户 A、B、C 拥有 2/3 钱包 X
- 用户 A、B、D 拥有 2/3 钱包 Y
- 用户 A 和 B 向 X 提交预签名交易
- 攻击中现在就可以将预签名交易重用到 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);
}