ECDSA(Elliptic Curve Digital Signature Algorithm)是椭圆曲线数字签名算法的简称。ECDSA库十分重要且使用广泛,其作用是在链上验证某message是否由给定的地址的私钥持有者进行签名的。简而言之,ECDSA库是一个验证地址真实身份的工具库。
[openzeppelin]:v4.8.3,[forge-std]:v1.5.6
ECDSA(Elliptic Curve Digital Signature Algorithm)是椭圆曲线数字签名算法的简称。ECDSA库十分重要且使用广泛,其作用是在链上验证某message是否由给定的地址的私钥持有者进行签名的。简而言之,ECDSA库是一个验证地址真实身份的工具库。
封装ECDSA library成为一个可调用合约:
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;
import "openzeppelin-contracts/contracts/utils/cryptography/ECDSA.sol";
contract MockECDSA {
using ECDSA for bytes32;
function tryRecover(bytes32 hash, bytes memory signature) external pure returns (address, ECDSA.RecoverError) {
return hash.tryRecover(signature);
}
function recover(bytes32 hash, bytes memory signature) external pure returns (address) {
return hash.recover(signature);
}
function tryRecover(
bytes32 hash,
bytes32 r,
bytes32 vs
) external pure returns (address, ECDSA.RecoverError) {
return hash.tryRecover(r, vs);
}
function recover(
bytes32 hash,
bytes32 r,
bytes32 vs
) external pure returns (address){
return hash.recover(r, vs);
}
function tryRecover(
bytes32 hash,
uint8 v,
bytes32 r,
bytes32 s
) external pure returns (address, ECDSA.RecoverError) {
return hash.tryRecover(v, r, s);
}
function recover(
bytes32 hash,
uint8 v,
bytes32 r,
bytes32 s
) external pure returns (address){
return hash.recover(v, r, s);
}
function toEthSignedMessageHash(bytes32 hash) external pure returns (bytes32) {
return hash.toEthSignedMessageHash();
}
function toEthSignedMessageHash(bytes memory s) external pure returns (bytes32) {
return ECDSA.toEthSignedMessageHash(s);
}
function toTypedDataHash(bytes32 domainSeparator, bytes32 structHash) external pure returns (bytes32) {
return domainSeparator.toTypedDataHash(structHash);
}
}
全部foundry测试合约:
测试数据:
测试数据的生成脚本:
import {ethers} from 'ethers'
import {writeFileSync} from 'fs'
// for:
// function toEthSignedMessageHash(bytes32 hash)
const digestHash = ethers.keccak256(ethers.toUtf8Bytes('Michael.W'))
const ethSignedMessageHashFromHash = ethers.hashMessage(ethers.getBytes(digestHash))
// for:
// function toEthSignedMessageHash(bytes memory s)
const ethSignedMessageHashFromBytes = ethers.hashMessage('Michael.W')
// for:
// function toTypedDataHash(bytes32 domainSeparator, bytes32 structHash)
const domain = {
'name': 'test name',
'version': '1',
'chainId': 1024,
'verifyingContract': '0x7a41fc8b73D6F307830b88878caf48D077128F63',
}
const types = {
'Student': [
{'name': 'address', 'type': 'address'},
{'name': 'age', 'type': 'uint256'},
]
}
const value = {
'address': ethers.ZeroAddress,
'age': 18,
}
const structHash = ethers.TypedDataEncoder.from(types).hash(value)
const typedDataHash = ethers.TypedDataEncoder.hash(domain, types, value)
// generate signature
const wallet = new ethers.Wallet(ethers.toBeHex(1024, 32))
const signature = wallet.signMessageSync('Michael.W')
// generate compact signature following EIP-2098
const signatureCompact = wallet.signingKey.sign(ethers.hashMessage('Michael.W'))
const signatureCompactR = signatureCompact.r
const signatureCompactVS = signatureCompact.yParityAndS
const output = {
eth_signed_msg_hash_from_hash: ethSignedMessageHashFromHash,
eth_signed_msg_hash_from_bytes: ethSignedMessageHashFromBytes,
struct_hash: structHash,
typed_data_hash: typedDataHash,
valid_signature: signature,
compact_signature_r: signatureCompactR,
compact_signature_vs: signatureCompactVS
}
writeFileSync('test/utils/cryptography/data/ECDSA_test.json', JSON.stringify(output))
toEthSignedMessageHash(bytes32 hash)
:将传入的摘要hash值转换为Ethereum特定的签名msg;toEthSignedMessageHash(bytes memory s)
:将传入的字节数组s转换为Ethereum特定的签名msg。以上方法生成的Ethereum特定签名msg正是ethereum json-rpc中用于签名的eth_sign()方法内部进行签名的真实内容。细节见:https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_sign
注:Ethereum特定的签名msg结构为:
"\x19Ethereum Signed Message:\n" + 原始签名内容字节长度 + 原始签名内容
通过在msg中添加前缀使后续计算出的签名可以被识别为Ethereum特定的签名,这样做是防止滥用。如果solidity内置的验签功能默认是对原有msg进行验证(例如签名msg为Ethereum的transaction),那么一些恶意的dapp就可以直接从区块中获取到历史transaction的签名进而伪装成发送者。细节内容见:EIP-191
// 使用枚举定义本库内部不同的错误类型
enum RecoverError {
// 无错误
NoError,
InvalidSignature,
InvalidSignatureLength,
InvalidSignatureS,
// 注:InvalidSignatureV在Openzeppelin v4.8中已遭弃用
InvalidSignatureV
}
// 根据传入的error的类别的不同,触发不同的revert msg
function _throwError(RecoverError error) private pure {
if (error == RecoverError.NoError) {
// 如果error为RecoverError.NoError,表示无错误发生,直接返回
return;
} else if (error == RecoverError.InvalidSignature) {
// 如果使用solidity内置的ecrecover()方法还原出的签名者地址为零地址,以该msg revert
revert("ECDSA: invalid signature");
} else if (error == RecoverError.InvalidSignatureLength) {
// 如果用于验签的签名长度不是65字节,以该msg revert
revert("ECDSA: invalid signature length");
} else if (error == RecoverError.InvalidSignatureS) {
// 如果用于验签的签名的s值大于secp256k1n/2,以该msg revert
revert("ECDSA: invalid signature 's' value");
}
}
function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32) {
// 由于keccak256的hash值为固定的32字节,所以原始签名内容字节长度写死成32。首先为原始hash添加前缀"\x19Ethereum Signed Message:\n32",然后返回整个msg的hash值
return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", hash));
}
function toEthSignedMessageHash(bytes memory s) internal pure returns (bytes32) {
// Strings.toString(s.length):将字节数组s的长度转换为对应字符串
// 为原始msg(字节数组s)添加前缀"\x19Ethereum Signed Message:\n" + 原始msg的字节长度,然后返回整个msg的hash值
return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n", Strings.toString(s.length), s));
}
foundry代码验证
contract ECDSATest is Test {
using stdJson for string;
MockECDSA me = new MockECDSA();
string jsonTestData = vm.readFile("test/utils/cryptography/data/ECDSA_test.json");
function test_ToEthSignedMessageHash() external {
// case 1: hash digest
bytes32 digestHash = keccak256("Michael.W");
bytes32 ethSignedMessageHash = me.toEthSignedMessageHash(digestHash);
bytes32 expectedEthSignedMessageHash = jsonTestData.readBytes32(".eth_signed_msg_hash_from_hash");
assertEq(expectedEthSignedMessageHash, ethSignedMessageHash);
// case 2: bytes digest
bytes memory digestBytes = bytes("Michael.W");
ethSignedMessageHash = me.toEthSignedMessageHash(digestBytes);
expectedEthSignedMessageHash = jsonTestData.readBytes32(".eth_signed_msg_hash_from_bytes");
assertEq(expectedEthSignedMessageHash, ethSignedMessageHash);
}
}
通过传入的domainSeparator和structHash计算Ethereum Signed Typed Data的Ethereum特定签名msg。
该方法生成的Ethereum特定签名msg正是EIP-712中提出的ethereum json-rpc方法——eth_signTypedData
的内部进行签名的真实内容。细节见:https://eips.ethereum.org/EIPS/eip-712
注:Ethereum特定的签名msg结构为:
"\x19\x01" + domainSeparator + 结构数据的hash值
function toTypedDataHash(bytes32 domainSeparator, bytes32 structHash) internal pure returns (bytes32) {
// 连接"\x19\x01"、传入domainSeparator以及structHash,并取hash值返回
return keccak256(abi.encodePacked("\x19\x01", domainSeparator, structHash));
}
foundry代码验证
contract ECDSATest is Test {
using stdJson for string;
MockECDSA me = new MockECDSA();
string jsonTestData = vm.readFile("test/utils/cryptography/data/ECDSA_test.json");
function test_ToTypedDataHash() external {
// set chain id
vm.chainId(1024);
// get fixed address of TargetEIP712 contract
vm.setNonce(address(1024), 1024);
vm.prank(address(1024));
TargetEIP712 te = new TargetEIP712();
// fixed contract address is 0x7a41fc8b73D6F307830b88878caf48D077128F63
assertEq(0x7a41fc8b73D6F307830b88878caf48D077128F63, address(te));
bytes32 structHash = jsonTestData.readBytes32(".struct_hash");
bytes32 typedDataHash = me.toTypedDataHash(te.getDomainSeparator(), structHash);
bytes32 expectedTypedDataHash = jsonTestData.readBytes32(".typed_data_hash");
assertEq(expectedTypedDataHash, typedDataHash);
}
}
contract TargetEIP712 is EIP712("test name", "1") {
function getDomainSeparator() external view returns (bytes32){
return _domainSeparatorV4();
}
}
tryRecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s)
:使用传入的摘要hash和各自独立的签名v、r、s值,还原出签名者的地址以及错误类型。注:该过程不会触发revert;recover(bytes32 hash, uint8 v, bytes32 r, bytes32 s)
:使用传入的摘要hash和各自独立的签名v、r、s值,还原出签名者的地址。注:该过程是对tryRecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s)的封装,如果产生错误会触发revert。ps:
EIP-2中提到:签名的s值大于secp256k1n/2
的交易都将被认作是无效的。但是solidity内置方法ecrecover()
却无需加入此限制。这样做的目的是为了使以太坊合约依旧可以recover老版Bitcoin的签名。细节见:https://eips.ethereum.org/EIPS/eip-2
同时,以太坊黄皮书的附录F定义了有效的s值范围为(0,secp256k1n÷2+1)
,有效的v值为27或28
。当前常用的椭圆曲线签名库大多都会生成一个唯一签名,其s值处于(0,secp256k1n÷2+1)
。如果你使用的库生成了具有延展性的签名,其s值处于[secp256k1n÷2+1,secp256k1n)
,可以利用如下方式计算出新的s值:
s_new = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s,并将v值从27变成28(或将28变成27)。
如果你使用的库生成的v值是0或1,请将其加上27变为27或28。
以太坊黄皮书:https://ethereum.github.io/yellowpaper/paper.pdf
function tryRecover(
bytes32 hash,
uint8 v,
bytes32 r,
bytes32 s
) internal pure returns (address, RecoverError) {
if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) {
// 如果签名的s值大于secp256k1n/2,说明此签名无效(EIP-2规定),直接返回零地址和错误类型InvalidSignatureS
return (address(0), RecoverError.InvalidSignatureS);
}
// 利用solidity内置的ecrecover方法,通过摘要hash和签名还原出签名者的地址
address signer = ecrecover(hash, v, r, s);
if (signer == address(0)) {
// 如果还原出的签名者地址为零地址说明此签名无效。返回零地址错误类型InvalidSignature
return (address(0), RecoverError.InvalidSignature);
}
// 返回签名者地址以及无错误标识NoError
return (signer, RecoverError.NoError);
}
function recover(
bytes32 hash,
uint8 v,
bytes32 r,
bytes32 s
) internal pure returns (address) {
// 使用传入的摘要hash和各自独立的签名v、r、s值,还原出签名者的地址以及错误类型
(address recovered, RecoverError error) = tryRecover(hash, v, r, s);
// 根据上面返回的错误类型触发相应revert msg
_throwError(error);
// 如果tryRecover()的过程中无错误产生,返回还原出签名者的地址
return recovered;
}
foundry代码验证
contract ECDSATest is Test {
using stdJson for string;
MockECDSA me = new MockECDSA();
uint signerPrivateKey = 1024;
address signerAddress = vm.addr(signerPrivateKey);
string jsonTestData = vm.readFile("test/utils/cryptography/data/ECDSA_test.json");
function test_TryRecover_WithVRS() external {
// case 1: pass tryRecover() with no RecoverError
bytes32 ethSignedMessageHash = me.toEthSignedMessageHash(bytes("Michael.W"));
(uint8 v, bytes32 r, bytes32 s) = vm.sign(signerPrivateKey, ethSignedMessageHash);
(address signerRecovered, ECDSA.RecoverError error) = me.tryRecover(ethSignedMessageHash, v, r, s);
assertEq(signerAddress, signerRecovered);
assertTrue(error == ECDSA.RecoverError.NoError);
// case 2: return InvalidSignatureS error with an s value > secp256k1n/2
bytes32 sInvalid = bytes32(type(uint).max);
(signerRecovered, error) = me.tryRecover(ethSignedMessageHash, v, r, sInvalid);
assertEq(address(0), signerRecovered);
assertTrue(error == ECDSA.RecoverError.InvalidSignatureS);
// case 3: return InvalidSignature error with zero v/r/s
(signerRecovered, error) = me.tryRecover(ethSignedMessageHash, 0, 0, 0);
assertEq(address(0), signerRecovered);
assertTrue(error == ECDSA.RecoverError.InvalidSignature);
// case 4: return an arbitrary signer and no RecoverError for another hash digest
(signerRecovered, error) = me.tryRecover(me.toEthSignedMessageHash(bytes("Michael.W/Michael.W")), v, r, s);
assertNotEq(signerAddress, signerRecovered);
assertTrue(error == ECDSA.RecoverError.NoError);
}
function test_Recover_WithVRS() external {
// case 1: pass recover() with no RecoverError
bytes32 ethSignedMessageHash = me.toEthSignedMessageHash(bytes("Michael.W"));
(uint8 v, bytes32 r, bytes32 s) = vm.sign(signerPrivateKey, ethSignedMessageHash);
address signerRecovered = me.recover(ethSignedMessageHash, v, r, s);
assertEq(signerAddress, signerRecovered);
// case 2: revert with an s value > secp256k1n/2
bytes32 sInvalid = bytes32(type(uint).max);
vm.expectRevert("ECDSA: invalid signature 's' value");
me.recover(ethSignedMessageHash, v, r, sInvalid);
// case 3: revert with zero v/r/s
vm.expectRevert("ECDSA: invalid signature");
me.recover(ethSignedMessageHash, 0, 0, 0);
// case 4: return an arbitrary signer for another hash digest
signerRecovered = me.recover(me.toEthSignedMessageHash(bytes("Michael.W/Michael.W")), v, r, s);
assertNotEq(signerAddress, signerRecovered);
}
}
tryRecover(bytes32 hash, bytes memory signature)
:使用传入的摘要hash和签名(r、v和s值合并在一起)还原出签名者的地址以及错误类型。注:该过程不会触发revert;recover(bytes32 hash, bytes memory signature)
:使用传入的摘要hash和签名(r、v和s值合并在一起)还原出签名者的地址。ps:生成签名使用的工具库如下:
function tryRecover(bytes32 hash, bytes memory signature) internal pure returns (address, RecoverError) {
// 该库可验签的签名长度必须为65字节
if (signature.length == 65) {
// 由于椭圆曲线验签的内置函数ecrecover()无法直接传入整个签名。所以按照标准,需要将65字节签名分割成32字节的r和s以及1字节的v
// 定义r,s,v,它们的长度比——32:32:1
bytes32 r;
bytes32 s;
uint8 v;
/// @solidity memory-safe-assembly
// 在内联汇编中进行bytes数组的切割
assembly {
// 在memory中,signature开始的前32字节存放的是signature的字节长度
// mload(add(signature, 0x20)):取出signature后第2个字(32字节)的内容给r
r := mload(add(signature, 0x20))
// mload(add(signature, 0x40)):取出signature后第3个字(32字节)的内容给s
s := mload(add(signature, 0x40))
// mload(add(signature, 0x60)):取出signature后第4个字(32字节)的内容;
// byte(0, mload(add(signature, 0x60))):取上述内容(32字节)的第1个字节内容给v
v := byte(0, mload(add(signature, 0x60)))
}
// 将分割后的r,s,v及hash传入tryRecover()并return执行结果
return tryRecover(hash, v, r, s);
} else {
// 如果传入签名不是65字节,直接返回零地址和InvalidSignatureLength错误
return (address(0), RecoverError.InvalidSignatureLength);
}
}
function recover(bytes32 hash, bytes memory signature) internal pure returns (address) {
// 使用传入的摘要hash和签名(r、v和s值合并在一起)还原出签名者的地址以及错误类型
(address recovered, RecoverError error) = tryRecover(hash, signature);
// 根据上面返回的错误类型触发相应revert msg
_throwError(error);
// 如果tryRecover()的过程中无错误产生,返回还原出签名者的地址
return recovered;
}
foundry代码验证
contract ECDSATest is Test {
using stdJson for string;
MockECDSA me = new MockECDSA();
uint signerPrivateKey = 1024;
address signerAddress = vm.addr(signerPrivateKey);
string jsonTestData = vm.readFile("test/utils/cryptography/data/ECDSA_test.json");
function test_TryRecover_WithSignature() external {
// case 1: pass tryRecover() with no RecoverError
bytes memory validSig = jsonTestData.readBytes(".valid_signature");
bytes32 digestHash = me.toEthSignedMessageHash(bytes("Michael.W"));
(address signerRecovered, ECDSA.RecoverError error) = me.tryRecover(digestHash, validSig);
assertEq(signerAddress, signerRecovered);
assertTrue(error == ECDSA.RecoverError.NoError);
// case 2: return InvalidSignatureLength if signature's length != 65
(signerRecovered, error) = me.tryRecover(digestHash, '0x');
assertEq(address(0), signerRecovered);
assertTrue(error == ECDSA.RecoverError.InvalidSignatureLength);
// case 3: return InvalidSignatureS if s value (second bytes32) in signature > secp256k1n/2
bytes memory invalidSig = abi.encodePacked(bytes32(0), type(uint).max, uint8(0));
(signerRecovered, error) = me.tryRecover(digestHash, invalidSig);
assertEq(address(0), signerRecovered);
assertTrue(error == ECDSA.RecoverError.InvalidSignatureS);
// case 4: return InvalidSignature error with signature of zero v/r/s
invalidSig = abi.encodePacked(bytes32(0), bytes32(0), uint8(0));
(signerRecovered, error) = me.tryRecover(digestHash, invalidSig);
assertEq(address(0), signerRecovered);
assertTrue(error == ECDSA.RecoverError.InvalidSignature);
// case 5: return an arbitrary signer and no RecoverError for another hash digest
(signerRecovered, error) = me.tryRecover(me.toEthSignedMessageHash(bytes("Michael.W/Michael.W")), validSig);
assertNotEq(signerAddress, signerRecovered);
assertTrue(error == ECDSA.RecoverError.NoError);
}
function test_Recover_WithSignature() external {
// case 1: pass recover() with no RecoverError
bytes memory validSig = jsonTestData.readBytes(".valid_signature");
bytes32 digestHash = me.toEthSignedMessageHash(bytes("Michael.W"));
address signerRecovered = me.recover(digestHash, validSig);
assertEq(signerAddress, signerRecovered);
// case 2: revert if signature's length != 65
vm.expectRevert("ECDSA: invalid signature length");
me.recover(digestHash, '0x');
// case 3: revert if s value (second bytes32) in signature > secp256k1n/2
bytes memory invalidSig = abi.encodePacked(bytes32(0), type(uint).max, uint8(0));
vm.expectRevert("ECDSA: invalid signature 's' value");
me.recover(digestHash, invalidSig);
// case 4: revert with signature of zero v/r/s
invalidSig = abi.encodePacked(bytes32(0), bytes32(0), uint8(0));
vm.expectRevert("ECDSA: invalid signature");
me.recover(digestHash, invalidSig);
// case 5: return an arbitrary signer and no RecoverError for another hash digest
signerRecovered = me.recover(me.toEthSignedMessageHash(bytes("Michael.W/Michael.W")), validSig);
assertNotEq(signerAddress, signerRecovered);
}
}
tryRecover(bytes32 hash, bytes32 r, bytes32 vs)
:使用传入的摘要hash、签名r值以及合并的vs值,还原出签名者的地址以及错误类型。注:该过程不会触发revert;recover(bytes32 hash, bytes32 r, bytes32 vs)
:使用传入的摘要hash、签名r值以及合并的vs值,还原出签名者的地址。注:该过程是对tryRecover(bytes32 hash, bytes32 r, bytes32 vs)的封装,如果产生错误会revert。ps:关于为何要将v和s合并到一个bytes32中以及如何合并vs值,参见:https://eips.ethereum.org/EIPS/eip-2098
function tryRecover(
bytes32 hash,
bytes32 r,
bytes32 vs
) internal pure returns (address, RecoverError) {
// 从合并的vs中解析出s值:过滤出vs中的低255位作为s
bytes32 s = vs & bytes32(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff);
// 从合并的vs中解析出v值:过滤出vs中的最高位+27作为v值
uint8 v = uint8((uint256(vs) >> 255) + 27);
// 使用传入的摘要hash和各自独立的签名v、r、s值,还原出签名者的地址以及错误类型
return tryRecover(hash, v, r, s);
}
function recover(
bytes32 hash,
bytes32 r,
bytes32 vs
) internal pure returns (address) {
// 使用传入的摘要hash、签名r值以及合并的vs值,还原出签名者的地址以及错误类型
(address recovered, RecoverError error) = tryRecover(hash, r, vs);
// 根据上面返回的错误类型触发相应revert msg
_throwError(error);
// 如果tryRecover()的过程中无错误产生,返回还原出签名者的地址
return recovered;
}
foundry代码验证
contract ECDSATest is Test {
using stdJson for string;
MockECDSA me = new MockECDSA();
uint signerPrivateKey = 1024;
address signerAddress = vm.addr(signerPrivateKey);
string jsonTestData = vm.readFile("test/utils/cryptography/data/ECDSA_test.json");
function test_TryRecover_WithRAndVS() external {
// case 1: pass tryRecover() with no RecoverError
bytes32 ethSignedMessageHash = me.toEthSignedMessageHash(bytes("Michael.W"));
bytes32 r = jsonTestData.readBytes32(".compact_signature_r");
bytes32 vs = jsonTestData.readBytes32(".compact_signature_vs");
(address signerRecovered, ECDSA.RecoverError error) = me.tryRecover(ethSignedMessageHash, r, vs);
assertEq(signerAddress, signerRecovered);
assertTrue(error == ECDSA.RecoverError.NoError);
// case 2: return InvalidSignatureS error with an s value > secp256k1n/2
bytes32 vsInvalid = bytes32(type(uint).max);
(signerRecovered, error) = me.tryRecover(ethSignedMessageHash, r, vsInvalid);
assertEq(address(0), signerRecovered);
assertTrue(error == ECDSA.RecoverError.InvalidSignatureS);
// case 3: return InvalidSignature error with zero r/vs
(signerRecovered, error) = me.tryRecover(ethSignedMessageHash, 0, 0);
assertEq(address(0), signerRecovered);
assertTrue(error == ECDSA.RecoverError.InvalidSignature);
// case 4: return an arbitrary signer and no RecoverError for another hash digest
(signerRecovered, error) = me.tryRecover(me.toEthSignedMessageHash(bytes("Michael.W/Michael.W")), r, vs);
assertNotEq(signerAddress, signerRecovered);
assertTrue(error == ECDSA.RecoverError.NoError);
}
function test_Recover_WithRAndVS() external {
// case 1: pass recover() with no RecoverError
bytes32 ethSignedMessageHash = me.toEthSignedMessageHash(bytes("Michael.W"));
bytes32 r = jsonTestData.readBytes32(".compact_signature_r");
bytes32 vs = jsonTestData.readBytes32(".compact_signature_vs");
address signerRecovered = me.recover(ethSignedMessageHash, r, vs);
assertEq(signerAddress, signerRecovered);
// case 2: revert with an s value > secp256k1n/2
bytes32 vsInvalid = bytes32(type(uint).max);
vm.expectRevert("ECDSA: invalid signature 's' value");
me.recover(ethSignedMessageHash, r, vsInvalid);
// case 3: revert with zero r/vs
vm.expectRevert("ECDSA: invalid signature");
me.recover(ethSignedMessageHash, 0, 0);
// case 4: return an arbitrary signer for another hash digest
signerRecovered = me.recover(me.toEthSignedMessageHash(bytes("Michael.W/Michael.W")), r, vs);
assertNotEq(signerAddress, signerRecovered);
}
}
ps:\ 本人热爱图灵,热爱中本聪,热爱V神。 以下是我个人的公众号,如果有技术问题可以关注我的公众号来跟我交流。 同时我也会在这个公众号上每周更新我的原创文章,喜欢的小伙伴或者老伙计可以支持一下! 如果需要转发,麻烦注明作者。十分感谢!
公众号名称:后现代泼痞浪漫主义奠基人
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!