在智能合约的开发中,验证签名是非常常用的一种身份验证手段,那么该如何进行签名,又该如何进行签名验证呢?本篇文章将会涉及到一些密码学的知识以及 solidity 部分代码。
开始读文章之前需要了解的两个小知识点
encode 和 encodePackage的区别?
两个都是solidity自带的打包方法,不同的是encodePackage打包结果中不会补 0,encode 打包之后会补 0,这意味着,如果使用 encodePackage 打包【“aa”,”bb”】的得到的结果与 【“a”,“abb”】打包之后的结果是一样的。这是我们不想看到的,但是encode 不会出现这种情况。所以在使用时要注意这个点,防止出现漏洞。
非对称加密是什么?
公开密钥密码学(英语:Public-key cryptography)也称非对称式密码学(英语:Asymmetric cryptography)是密码学的一种演算法,它需要两个密钥,一个是公开密钥,另一个是私有密钥;公钥用作加密,私钥则用作解密。使用公钥把明文加密后所得的密文,只能用相对应的私钥才能解密并得到原本的明文,最初用来加密的公钥不能用作解密。由于加密和解密需要两个不同的密钥,故被称为非对称加密。
将消息签名
将消息进行哈希运算 → hash(message)
将私钥和经过哈希运算之后的消息进行签名 → sign(hash(message), private key)
恢复签名 → ecrecover(hash(message), signature)
Tips:文章结尾有代码
在合约部署完之后开始测试
首先我们需要使用 【getMessageHash】方法对 “secret message”进行第一次 hash运算,接下来我们就可以对这个hash值进行签名了。

打开浏览器的控制台按【F12】
在控制台中输入 ethereum.enable();
结果如图所示,想要用这个命令,你的浏览器必须装了 metamask 的插件

然后我们定义两个变量 【account 和 hash 并对其赋值】
account:就是我们上一步看到的地址
hash:就是我们上一步通过 hash 运算得到的结果

接下来我们就可以对 hash 签名,运行命令如下:
ethereum.request({method: "personal_sign", params: [account, hash]})
当我们运行这个命令后 metamask 会弹出签名请求。

签名后的结果如图所示

接下来验证签名是否正确
首先将第一次运行的结果放到 【getEthSignedMessageHash】中得到第二次hash运算结果

然后使用recover 恢复签名获取签名的地址
将第二次计算的得到的hash值和浏览器控制台中签名得到的结果放到recover 中,最终会获取到签名的地址结果,我们用这个结果去和之前在浏览器中的 account 值比较。
发现地址一样,签名验证通过。

以上是把一个签名到验证的过程分解,相当于拆解了验证「verify」方法,那么接下来演示一下完整的「verify」是如何运行的。
我们需要将签名人的地址,签名信息,以及浏览器中计算之后的结果传进去。然后通过【verify】比对最终是否通过验证。

示例代码
// SPDX-License-Identifier: MIT
pragma solidity >=0.7.0 <0.9.0;
/*
1. 如何对一个消息进行签名
2. 对签名结果进行数据恢复
3. 验证签名和签名人是否是同一个人
*/
contract VerifySig {
/*验证函数
_signer: 签名人的地址
_message: 消息的原文
_sig:签名的结果
*/
function verify(address _signer, string memory _message, bytes memory _sig) external pure returns (bool)
{
//给输入消息进行 hash 运算
bytes32 messageHash = getMessageHash(_message);
//再把消息结果进行 eth 消息运算
bytes32 ethSignedMessageHash = getEthSignedMessageHash(messageHash);
//判断 hash 运算之后的结果和 传进来的hash值是否是同一个地址
return recover(ethSignedMessageHash, _sig) == _signer;
}
function getMessageHash(string memory _message) public pure returns (bytes32) {
return keccak256(abi.encodePacked(_message));
}
//进行两次hash 运算是因为在今天的数学界,进行一次hash运算的结果已经有可能被破解
function getEthSignedMessageHash(bytes32 _messageHash) public pure returns (bytes32) {
return keccak256(abi.encodePacked(
"\x19Ethereum Signed Message:\n32",
_messageHash
));
}
function recover(bytes32 _ethSignedMessageHash, bytes memory _sig)
public pure returns (address)
{
//r,s,v 属于非对称加密算法里的知识,文章中有扩展
(bytes32 r, bytes32 s, uint8 v) = _split(_sig);
//ecrecover 是solidity自带函数,会反回还原之后的地址
return ecrecover(_ethSignedMessageHash, v, r, s);
}
//分割 r,s,v变量
function _split(bytes memory _sig) internal pure returns (bytes32 r, bytes32 s, uint8 v)
{
require(_sig.length == 65, "invalid signature length");
//签名bytes是用rsv三个元素拼接的,所以第一个r占前32位,第二个s占后32位,最后一个v占最后一位
assembly {
r :=mload(add(_sig, 32))
s :=mload(add(_sig, 64))
v :=byte(0, mload(add(_sig, 96)))
}
}
}
在成为科学家的道路上一起成长,在赚钱的道路上并肩前行,你好我是Jack_Zhang.
