solidity验证签名分为四步:
- 打包消息
- 以太坊签名消息
- 钱包把消息和私钥进行签名
- 恢复公钥
打包消息
在以太坊的ECDSA标准中,被签名的消息是一组数据的keccak256哈希,为bytes32类型。我们可以把任何想要签名的内容利用abi.encodePacked()函数打包,然后用keccak256()计算哈希,作为消息。例子中的消息是一个字符串。
function getMessageHash(string calldata _message) public pure returns(bytes32) {
return keccak256(abi.encodePacked(_message));
}
以太坊签名消息
以太坊签名消息与打包消息代码类似,只增加了固定字符串 \x19Ethereum Signed Message:\n32,该字符串是在EIP191提出的。EIP191提倡在消息前加上"\x19Ethereum Signed Message:\n32"字符,并再做一次keccak256哈希,作为以太坊签名消息。
function getEthSignMessageHash(bytes32 _msgHash) public pure returns(bytes32) {
return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32",_msgHash));
}
钱包把消息和私钥进行签名
在浏览器控制台输入以下代码,浏览器必须安装小狐狸钱包:
ethereum.enable()
account="0xd4be041fb762980d242bba468eb51f836d18cd4b"(替换成自己的钱包地址)
hash="0x7ef7eb5e71f100c5c272816c12ebabe1d7ee29d4a7d4d23f79b4a1b6087de92b"(消息打包的hash)
ethereum.request({method:"personal_sign", params:[account, hash]})
恢复公钥
使用以太坊签名和钱包签名恢复公钥,solidity内置函数ecrecover:
function recover(bytes32 _ethSignMessageHash, bytes memory _signature) public pure returns(address) {
(bytes32 r, bytes32 s, uint8 v) = _split(_signature);
return ecrecover(_ethSignMessageHash, v, r, s);
}
// 分割签名,签名由r s v三部分拼接而成
function _split(bytes memory _signature) internal pure returns(bytes32 r, bytes32 s, uint8 v) {
require(_signature.length == 65, "invalid signature length");
assembly {
r := mload(add(_signature, 32))
s := mload(add(_signature, 64))
v := byte(0,mload(add(_signature, 96)))
}
}
然后我们对比恢复出来的地址和小狐狸钱包签名的地址是否一致,即可验证签名是否有效。
对比公钥并验证签名
function verify(bytes32 _ethSignMessageHash, bytes memory _signature, address _signer) internal pure returns (bool) {
return recoverSigner(_ethSignMessageHash, _signature) == _signer;
}