Summary
一种签名数据标准,专门针对以太坊智能合约中的签名数据。其背后的动机是为所有签名数据建立通用格式。 签名主要有三个作用:
- 身份认证
- 不可否认
- 完整性
格式
根据 ERC-191 ,数据按以下方式排列:
0x19 <1 byte version> <version specific data> <data to sign>.
- 0x19是钱嘴
- 一个字节的版本好
- 版本好特有数据
- 签名数据本身 目前有三个版本号:
Version byte | EIP | Description |
---|---|---|
0x00 | 191 | Data with intended validator |
0x01 | 712 | structured data |
0x45 | 191 | personal_sign messages |
0x00
格式一般为:
0x19 <0x00> <intended validator address> <data to sign>
这种一般是合约地址,这样做的好处是签名只对某个合约有效,能一定程度的避免重放攻击 比如有一段数据"ABC"要签名,在合约地址0xffff中使用,则常见的步骤是:
- 拼接EIP191的数据格式:data= 0x19 0x00 0xffff abc
- 把拼接好的数据做哈希运算 hash = keccak256(data)
- 签名数据
- 把数据发送给合约,合约调用ecrecover来计算出签名人的数据
- 验证签名人是否合法
0x45
格式一般是:
0x19 <0x45 (E)> <thereum Signed Message:\n" + len(message)> <data to sign>
需要注意的好似0x45对应的ascii编码是字母E,这个版本的就是吧personal_sign 方案加入到了EIP191中
0x01
就是EIP712的签名格式
如何签名
- 首先我们打包消息
function getMessageHash(address _account, uint256 _tokenId) public pure returns(bytes32){
return keccak256(abi.encodePacked(_account, _tokenId));
}
- 计算以太坊签名消息
/**
* @dev 返回 以太坊签名消息
* `hash`:消息
* 遵从以太坊签名标准:https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`]
* 以及`EIP191`:https://eips.ethereum.org/EIPS/eip-191`
* 添加"\x19Ethereum Signed Message:\n32"字段,防止签名的是可执行交易。
*/
function toEthSignedMessageHash(bytes32 hash) public pure returns (bytes32) {
// 哈希的长度为32
return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", hash));
}
- 计算签名 如果使用foundry :
function sign(uint256 privateKey, bytes32 digest) external returns (uint8 v, bytes32 r, bytes32 s);
(address alice, uint256 alicePk) = makeAddrAndKey("alice");w
emit log_address(alice);
bytes32 hash = keccak256("Signed by Alice");
(uint8 v, bytes32 r, bytes32 s) = vm.sign(alicePk, hash);
address signer = ecrecover(hash, v, r, s);
assertEq(alice, signer); // [PASS]
- 验证
// @dev 从_msgHash和签名_signature中恢复signer地址
function recoverSigner(bytes32 _msgHash, bytes memory _signature) internal pure returns (address){
// 检查签名长度,65是标准r,s,v签名的长度
require(_signature.length == 65, "invalid signature length");
bytes32 r;
bytes32 s;
uint8 v;
// 目前只能用assembly (内联汇编)来从签名中获得r,s,v的值
assembly {
/*
前32 bytes存储签名的长度 (动态数组存储规则)
add(sig, 32) = sig的指针 + 32
等效为略过signature的前32 bytes
mload(p) 载入从内存地址p起始的接下来32 bytes数据
*/
// 读取长度数据后的32 bytes
r := mload(add(_signature, 0x20))
// 读取之后的32 bytes
s := mload(add(_signature, 0x40))
// 读取最后一个byte
v := byte(0, mload(add(_signature, 0x60)))
}
// 使用ecrecover(全局函数):利用 msgHash 和 r,s,v 恢复 signer 地址
return ecrecover(_msgHash, v, r, s);
}
why use 0x19
因为以太坊中大量使用了RLP作为格式编码,为了和RLP编码做出区分,所以EIP191使用了0x19作为前缀。 因为RLP编码的数据,如果是0x19作为前缀,其仅代表着一个单字节。不可能像EIP191一样,0x19后面跟着一串数据。因此EIP191数据和RLP编码的数据就能区别开来。
How Dose Sign Work?
- Take Private Key + Message(data,function selector , parameters)
Refer
- https://mirror.xyz/xyyme.eth/-e1FodE7HZcwhw60VuGnbUue2SfCN4kn6JVg0JjCFS4
- Martin Holst Swende (@holiman), Nick Johnson
<arachnid@notdot.net>
, "ERC-191: Signed Data Standard," Ethereum Improvement Proposals, no. 191, January 2016. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-191.