跳到主要内容

Summary

一种签名数据标准,专门针对以太坊智能合约中的签名数据。其背后的动机是为所有签名数据建立通用格式。 签名主要有三个作用:

  • 身份认证
  • 不可否认
  • 完整性

格式

根据 ERC-191 ,数据按以下方式排列:

0x19 <1 byte version> <version specific data> <data to sign>.
  • 0x19是钱嘴
  • 一个字节的版本好
  • 版本好特有数据
  • 签名数据本身 目前有三个版本号:
Version byteEIPDescription
0x00191Data with intended validator
0x01712structured data
0x45191personal_sign messages

0x00

格式一般为:

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

这种一般是合约地址,这样做的好处是签名只对某个合约有效,能一定程度的避免重放攻击 比如有一段数据"ABC"要签名,在合约地址0xffff中使用,则常见的步骤是:

  1. 拼接EIP191的数据格式:data= 0x19 0x00 0xffff abc
  2. 把拼接好的数据做哈希运算 hash = keccak256(data)
  3. 签名数据
  4. 把数据发送给合约,合约调用ecrecover来计算出签名人的数据
  5. 验证签名人是否合法

0x45

格式一般是:

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

需要注意的好似0x45对应的ascii编码是字母E,这个版本的就是吧personal_sign 方案加入到了EIP191中

0x01

就是EIP712的签名格式

如何签名

  1. 首先我们打包消息
    function getMessageHash(address _account, uint256 _tokenId) public pure returns(bytes32){
return keccak256(abi.encodePacked(_account, _tokenId));
}
  1. 计算以太坊签名消息
    /**
* @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));
}
  1. 计算签名 如果使用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]

  1. 验证
    // @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?

  1. Take Private Key + Message(data,function selector , parameters)

Refer