# 智能合约中的签名验证 **Published by:** [Jack_Zhang](https://paragraph.com/@yunmo/) **Published on:** 2022-05-10 **URL:** https://paragraph.com/@yunmo/k6bTzvkbOcG5zyODlv00 ## Content 在智能合约的开发中,验证签名是非常常用的一种身份验证手段,那么该如何进行签名,又该如何进行签名验证呢?本篇文章将会涉及到一些密码学的知识以及 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值进行签名了。 第一次 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. https://twitter.com/Bowei52748580 ## Publication Information - [Jack_Zhang](https://paragraph.com/@yunmo/): Publication homepage - [All Posts](https://paragraph.com/@yunmo/): More posts from this publication - [RSS Feed](https://api.paragraph.com/blogs/rss/@yunmo): Subscribe to updates