常用库
ECDSA.sol
基本信息
椭圆曲线数字签名算法(ECDSA)操作。包含了一些列的用于验证签名的方法。这些函数可用于验证消息是否由给定地址的私钥持有者签名。
官方文档:
https://docs.openzeppelin.com/contracts/5.x/api/utils/cryptography#ecdsa
导入
import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
相关源码
相对于最简洁版本,添加了一下安全判断
function recover(bytes32 hash, bytes memory signature) internal pure returns (address) {
(address recovered, RecoverError error, bytes32 errorArg) = tryRecover(hash, signature);
_throwError(error, errorArg);
return recovered;
}
function tryRecover(
bytes32 hash,
bytes memory signature
) internal pure returns (address recovered, RecoverError err, bytes32 errArg) {
if (signature.length == 65) {
bytes32 r;
bytes32 s;
uint8 v;
// ecrecover takes the signature parameters, and the only way to get them
// currently is to use assembly.
assembly ("memory-safe") {
r := mload(add(signature, 0x20))
s := mload(add(signature, 0x40))
v := byte(0, mload(add(signature, 0x60)))
}
return tryRecover(hash, v, r, s);
} else {
return (address(0), RecoverError.InvalidSignatureLength, bytes32(signature.length));
}
}
function tryRecover(
bytes32 hash,
uint8 v,
bytes32 r,
bytes32 s
) internal pure returns (address recovered, RecoverError err, bytes32 errArg) {
// EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature
// unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines
// the valid range for s in (301): 0 < s < secp256k1n ÷ 2 + 1, and for v in (302): v ∈ {27, 28}. Most
// signatures from current libraries generate a unique signature with an s-value in the lower half order.
//
// If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value
// with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or
// vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept
// these malleable signatures as well.
if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) {
return (address(0), RecoverError.InvalidSignatureS, s);
}
// If the signature is valid (and not malleable), return the signer address
address signer = ecrecover(hash, v, r, s);
if (signer == address(0)) {
return (address(0), RecoverError.InvalidSignature, bytes32(0));
}
return (signer, RecoverError.NoError, bytes32(0));
}
MessageHashUtils
基本信息
这两个本身就是不同的协议。
toEthSignedMessageHash(bytes32 messageHash) → bytes32 digest // 我们sale合约里的线上验签,用的是这个
toEthSignedMessageHash(bytes message) → bytes32
官方文档
https://docs.openzeppelin.com/contracts/5.x/api/utils/cryptography#messagehashutils
导入
import "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol";
toEthSignedMessageHash(messageHash)
function toEthSignedMessageHash(bytes32 messageHash) internal pure returns (bytes32 digest) {
assembly ("memory-safe") {
mstore(0x00, "\x19Ethereum Signed Message:\n32") // 32 is the bytes-length of messageHash
mstore(0x1c, messageHash) // 0x1c (28) is the length of the prefix
digest := keccak256(0x00, 0x3c) // 0x3c is the length of the prefix (0x1c) + messageHash (0x20)
}
}
// 转为为solidity版本:这正是我们之前学的,一模一样,就是加了前缀,然后再哈希
function toEthSignedMessageHash(bytes32 messageHash) internal pure returns (bytes32) {
// 把前缀和 messageHash 拼在一起再 keccak256
return keccak256(
abi.encodePacked(
"\x19Ethereum Signed Message:\n32",
messageHash
)
);
}
使用
// 1. 先对原始 message 做 keccak256
bytes32 messageHash = keccak256(abi.encodePacked(message));
// 2. 转成 personal_sign 对应的 digest
bytes32 digest = MessageHashUtils.toEthSignedMessageHash(messageHash);
toEthSignedMessageHash(bytes message) → bytes32
直接把message进行以太坊签名,但是需要把message专场byte
bytes32 digest = MessageHashUtils.toEthSignedMessageHash(bytes(message));