签名与验证
签名原理
签名过程 0. message
- 第一次hash(message)
- 再次生成eth hash
- sign(hash(message), private key) 浏览器中调小狐狸生成,本代码不包含sign过程
被签内容
就是这么定义的,经过两次hash过程。第一次把所有变量hash,第二次添加了以太坊前缀后再hash一次
_messageHash = keccak256(abi.encodePacked(_message, amount))
keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32",_messageHash))
签名 链下签名
链上验签
验证签名原理
示例代码
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.3;
/*
翻解过程
0. message
1. 第一次hash(message)
2. 再次生成eth hash
3. ecrecover(hash(message), signature) == signer 翻解
*/
contract VerifySig {
function verify(
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)// 一次普通keccak256 hash
public
pure
returns (bytes32)
{return keccak256(abi.encodePacked(_message));}
function getEthSignedMessageHash(bytes32 _messageHash) // 生成用于签名的hash
public
pure
returns (bytes32)
{
return
keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32",_messageHash));
}
// 拆解签名然后调用ecrecover方法从椭圆曲线中恢复签名者公钥
function recover(bytes32 _ethSignedMessageHash, bytes memory _sig)
public
pure
returns (address)
{
(bytes32 r, bytes32 s, uint8 v) = _split(_sig);
return ecrecover(_ethSignedMessageHash, v, r, s); // ecrecover的功能就是反推初公钥
}
// 拆解签名
function _split(bytes memory _sig)
internal
pure
returns (
bytes32 r,
bytes32 s,
uint8 v
)
{
require(_sig.length == 65, "invalid signature length");
// 是一种汇编,https://docs.soliditylang.org/zh-cn/v0.8.24/assembly.html#inline-assembly
assembly {
r := mload(add(_sig, 32))
s := mload(add(_sig, 64))
v := byte(0, mload(add(_sig, 96)))
}
}
}
使用OpenZeepLin提供的方法
import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol";
using ECDSA for bytes32;
using MessageHashUtils for bytes32;
function checkRegistrationSignature(bytes memory signature, address user) public view returns (bool) {
/// @notice Check signature user submits for registration.
/// @param signature 是用户通过了KYC之后,管理员签发的,相当于管理员 把messageHash 经过了管理员的私钥进行了签名,得到了signature
/// @param user is the address of user which is registering for sale
// 将用户地址和合约地址拼接起来,然后哈希
bytes32 hash = keccak256(abi.encodePacked(user, address(this)));
// 将hash转换为以太坊签名消息的hash,来自于openzeeplin的MessageHashUtils.sol中的方法
bytes32 messageHash = hash.toEthSignedMessageHash();
// 这里相当于根据签名结果(signature),被签名内容(messageHash),推导出了签名者的公钥即地址,来自与openzeeplin的ECDSA.sol中的方法
return admin.isAdmin(messageHash.recover(signature));
}
参考资料
https://chatgpt.com/s/t_689c00447bdc819195098a93657f161c https://chatgpt.com/s/t_689c0051c530819194efea9be5117d2e https://docs.soliditylang.org/zh-cn/v0.8.24/units-and-global-variables.html#mathematical-and-cryptographic-functions https://docs.soliditylang.org/zh-cn/v0.8.24/assembly.html#inline-assembly
ethereum.enable()
ethereum.request({method: "personal_sign", params:['0xb314a242aa628F1FF3A04590edD63C8F036Df51f', hash]}) // 这里的account是小狐狸的地址,要不然拉不出来小狐狸