// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/*
哈希算法具有两个特性:
1. 输入值相同,输出值一定相同
2. 不管输入值有多大,输出值是定长的,并且哈希算法是不可逆向运算的
通常把哈希算法用在签名运算,或者是获取一个特定的id
执行签名与验证签名操作步骤:
1. getMessageHash 输入"secret message" 得到: 0x9c97d796ed69b7e69790ae723f51163056db3d55a7a6a82065780460162d4812
2. 浏览器按f12打开控制台,输入ethereum.enable() 前提要装小狐狸钱包,这个函数会获取一个地址: "0xEc80445eb3363b49c93a902662bD11e3C8D197E8"
3. 在浏览器定义变量:
account = "0xEc80445eb3363b49c93a902662bD11e3C8D197E8"
hash = "0x9c97d796ed69b7e69790ae723f51163056db3d55a7a6a82065780460162d4812"
4. 执行函数得到签名
ethereum.request({method: "personal_sign", params: [account, hash]})
在小狐狸钱包点确认签名,得到签名数据: 0x686a1e541227e4edce4270a2508b486372eaa7eb63171605743b392d35f96ae721a4f58cc763497f7eb24b108e5fc9f6df1a0679c973d897c2d9d9ec0611cf0f1b
5. 将getMessageHash得到的哈希值放在getEthSignedMessageHash再次签名,得到: 0x95a786464acc06fafc0d46036515722ec35acb840ecc291f251e086ebfeb9099
6. 恢复签名进行验证在recover输入,参数1:步骤5哈希值 参数3:步骤4哈希值 点击call得到地址: 0xEc80445eb3363b49c93a902662bD11e3C8D197E8 该地址与步骤2地址相同,代表恢复完成了
7. 再使用验证方法完整的验证一遍,在verfiy输入,参数1:步骤2地址 参数2:消息原文"secret message" 参数3:签名之后的数据即步骤4哈希值,结果返回true 签名验证通过
*/
contract HashFunc {
// 哈希返回值bytes32定长值
function hash(string memory text, uint num, address addr) external pure returns (bytes32) {
// 使用keccak256计算哈希,要先通过abi打包
return keccak256(abi.encodePacked(text, num, addr));
}
// 使用abi.encode方式打包 会将结果哈希值补0
function encode(string memory text1, string memory text2) external pure returns (bytes memory) {
return abi.encode(text1, text2);
}
// 使用abi.encodePacded方式打包 不会将结果哈希值补0 不同的参数会产生相同的结果
// "AAAA","BBB" 与 "AAA","ABBB" 结果都是 0x41414141424242
function encodePacded(string memory text1, string memory text2) external pure returns (bytes memory) {
return abi.encodePacked(text1, text2);
}
// 哈希碰撞实验,输入不同的参数来得到相同的哈希值
// "AAAA","BBB" 与 "AAA","ABBB" 结果都是 0x11db58448f2a53848bef361744f19e6fdabef68b8267b1ff669de1b4c42da0da
// 避免这种错误有两种解决方案:1. 使用encode打包 2. 在两个字符串之间添加一个数字类型:"AAAA",123,"BBB" 与 "AAA",123,"ABBB"
function collision(string memory text1, string memory text2) external pure returns (bytes32) {
return keccak256(abi.encodePacked(text1, text2));
}
}
/*
通过智能合约来验证签名,验证签名分4个步骤:
1. 将消息签名 2. 将消息进行哈希 3. 再把消息和私钥进行签名(链下完成) 4. 恢复签名
*/
contract VerfiySig {
/*
定义消息签名验证函数
参数1. 签名人地址
参数2. 消息原文
参数3. 签名的结果
*/
function verfiy(address _signer, string memory _message, bytes memory _sig)
external pure returns (bool)
{
bytes32 messageHash = getMessageHash(_message);
bytes32 ethSignedMessageHash = getEthSignedMessageHash(messageHash);
// 恢复签名地址
return recover(ethSignedMessageHash, _sig) == _signer;
}
// 将消息进行哈希运算
function getMessageHash(string memory _message) public pure returns (bytes32) {
return keccak256(abi.encodePacked(_message));
}
// 将哈希值再次进行哈希运算
function getEthSignedMessageHash(bytes32 _messageHash) public pure returns (bytes32) {
// 这里需要进行两次哈希运行,可以增加破解难度, 一次哈希运算有破解的可能性
return keccak256(abi.encodePacked(
"\x19Ethereum Signed Message:\n32",
_messageHash
));
}
// 定义恢复函数 参数1:上面函数运算结果 参数2:不定长签名结果
function recover(bytes32 _ethSignedMessageHash, bytes memory _sig)
public pure returns (address)
{
// 返回非对称加密三个值r,s,v
(bytes32 r, bytes32 s, uint8 v) = _split(_sig);
// 使用智能合约内部函数恢复签名
return ecrecover(_ethSignedMessageHash, v, r, s);
}
// 定义分割签名函数 输入长度要65位 32+32+1 uint8是1位
function _split(bytes memory _sig) internal pure returns (bytes32 r, bytes32 s, uint8 v) {
require(_sig.length == 65, "invalid signature length");
// 使用内联汇编将bytes参数分割,原理就是签名参数就是用r、s、v三个参数拼接出来的
assembly {
// 使用mload内存读取,读取签名变量,使用add跳过32位长度,获取到_sig32位之后的32位
r := mload(add(_sig, 32))
s := mload(add(_sig, 64))
v := byte(0, mload(add(_sig, 96)))
}
}
}