Michael.W基于Foundry精读Openzeppelin第30期——ECDSA.sol

news2025/1/14 0:46:52

Michael.W基于Foundry精读Openzeppelin第30期——ECDSA.sol

      • 0. 版本
        • 0.1 ECDSA.sol
      • 1. 目标合约
      • 2. 代码精读
        • 2.1 toEthSignedMessageHash(bytes32 hash) && toEthSignedMessageHash(bytes memory s)
        • 2.2 toTypedDataHash(bytes32 domainSeparator, bytes32 structHash)
        • 2.3 tryRecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) && recover(bytes32 hash, uint8 v, bytes32 r, bytes32 s)
        • 2.4 tryRecover(bytes32 hash, bytes memory signature) && recover(bytes32 hash, bytes memory signature)
        • 2.5 tryRecover(bytes32 hash, bytes32 r, bytes32 vs) && recover(bytes32 hash, bytes32 r, bytes32 vs)

0. 版本

[openzeppelin]:v4.8.3,[forge-std]:v1.5.6

0.1 ECDSA.sol

Github: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.8.3/contracts/utils/cryptography/ECDSA.sol

ECDSA(Elliptic Curve Digital Signature Algorithm)是椭圆曲线数字签名算法的简称。ECDSA库十分重要且使用广泛,其作用是在链上验证某message是否由给定的地址的私钥持有者进行签名的。简而言之,ECDSA库是一个验证地址真实身份的工具库。

1. 目标合约

封装ECDSA library成为一个可调用合约:

Github: https://github.com/RevelationOfTuring/foundry-openzeppelin-contracts/blob/master/src/utils/cryptography/MockECDSA.sol

// 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测试合约:

Github: https://github.com/RevelationOfTuring/foundry-openzeppelin-contracts/blob/master/test/utils/cryptography/ECDSA.t.sol

测试数据:

Github: https://github.com/RevelationOfTuring/foundry-openzeppelin-contracts/blob/master/test/utils/cryptography/data/ECDSA_test.json

测试数据的生成脚本:

Github: https://github.com/RevelationOfTuring/foundry-openzeppelin-contracts/blob/master/test/utils/cryptography/ECDSA_test.ts

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))

2. 代码精读

2.1 toEthSignedMessageHash(bytes32 hash) && toEthSignedMessageHash(bytes memory s)

  • 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);
    }
}

2.2 toTypedDataHash(bytes32 domainSeparator, bytes32 structHash)

通过传入的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();
    }
}

2.3 tryRecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) && recover(bytes32 hash, uint8 v, bytes32 r, bytes32 s)

  • 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

同时,以太坊黄皮书(https://ethereum.github.io/yellowpaper/paper.pdf)的附录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。

    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);
    }
}

2.4 tryRecover(bytes32 hash, bytes memory signature) && recover(bytes32 hash, bytes memory signature)

  • tryRecover(bytes32 hash, bytes memory signature):使用传入的摘要hash和签名(r、v和s值合并在一起)还原出签名者的地址以及错误类型。注:该过程不会触发revert;
  • recover(bytes32 hash, bytes memory signature) :使用传入的摘要hash和签名(r、v和s值合并在一起)还原出签名者的地址。

ps:生成签名的工具库如下

  • 使用web3.js:https://web3js.readthedocs.io/en/v1.3.4/web3-eth-accounts.html#sign
  • 使用ethers.js:https://docs.ethers.io/v5/api/signer/#Signer-signMessage
    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);
    }
}

2.5 tryRecover(bytes32 hash, bytes32 r, bytes32 vs) && recover(bytes32 hash, bytes32 r, bytes32 vs)

  • 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神。
以下是我个人的公众号,如果有技术问题可以关注我的公众号来跟我交流。
同时我也会在这个公众号上每周更新我的原创文章,喜欢的小伙伴或者老伙计可以支持一下!
如果需要转发,麻烦注明作者。十分感谢!

在这里插入图片描述

公众号名称:后现代泼痞浪漫主义奠基人

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/915085.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Just KNIME it[S2C21] 图像识别

朋友们,Just KNIME it 还有在跟进吗? 本季已经到 21 期啦。 本期探讨的主题是图像识别问题,快随指北君一起看看吧。 挑战 21:帮助球童(第 1 部分) 级别:中 描述:球童汤姆是一位最受欢迎的高尔夫…

thinkphp6.0 配合shell 脚本 定时任务

1. 执行命令&#xff0c;生成自定义命令 php think make:command Custom<?php declare (strict_types 1);namespace app\command;use app\facade\AdmUser; use think\console\Command; use think\console\Input; use think\console\input\Argument; use think\console\i…

第 6 章 递归(3)(八皇后问题)

6.7递归-八皇后问题(回溯算法) 6.7.1八皇后问题介绍 八皇后问题&#xff0c;是一个古老而著名的问题&#xff0c;是回溯算法的典型案例。该问题是国际西洋棋棋手马克斯贝瑟尔于1848年提出&#xff1a;在88格的国际象棋上摆放八个皇后&#xff0c;使其不能互相攻击&#xff0c…

堆内存和栈内存的区别

什么是栈内存&#xff1f; 栈内存是为线程流出来的临时空间&#xff0c;每一个线程都有它的临时空间&#xff0c;而且每一个栈都只能被当前的线程访问。 所以它是线程安全的。栈内存的分配和回收是由系统完成的。 当函数调用的时候 系统就会为这个函数分配栈空间&#xff0c;当…

国产化kylin配置网络不通解决方案—1

kylin镜像自行官网下载 kylin系统安装外网不通配置 进入vi /etc/sysconfig/network-scripts/ifcfg-ens33 TYPEEthernet PROXY_METHODnone BROWSER_ONLYno BOOTPROTOstatic DEFROUTEyes IPV4_FAILURE_FATALno IPV6INITyes IPV6_AUTOCONFyes IPV6_DEFROUTEyes IPV6_FAILURE_FAT…

虚拟化技术——存储虚拟化

概念 通过虚拟化技术&#xff0c;把存储资源整合到一起后对外提供服务&#xff0c;同时实现数据安全性、容量提升、性能提升等效果。 存储资源整合对象 多套存储 多颗硬盘 多个盘符 数据安全性 容量提升 性能提升 对象及实现技术 磁盘虚拟化 保护数据安全&#xff0…

国内ChatGPT对比与最佳方案

很久没写内容了&#xff0c;主要还是工作占据了太多时间。简单分享下我这段时间的研究吧,由于时间仓促&#xff0c;有很多内容没有具体写&#xff0c;请自行到我分享的网站体验查看。 前言 ChatGPT 的出现确实在很大程度上改变了世界。许多人已经亲身体验到了ChatGPT作为一个…

Nacos简述

介绍 ​ Nacos主要在微服务生态中可以发现、配置和管理微服务&#xff0c;实现动态服务发现和注册、服务配置、DNS服务、服务元数据及流量管理。 ​ 目前Nacos支持多种生态&#xff0c;比如SpringCloud、Dubbo、云原生Cloud Native等。 Nacos关键特性 1、服务发现和服务健康…

Golang面向对象:封装 继承 多态

面向过程转换到面向对象&#xff0c;那么必然会涉及到几个特性&#xff1a;封装&#xff0c;继承&#xff0c;多态&#xff0c;那么Golang中的面向过程会有什么特性&#xff1f;那我们来仔细说一说&#xff1a; 封装&#xff1a; 首先要一个类的概念&#xff0c;类就像一下工厂…

BATPowerShell实现本地文件自动上传FTP服务器

运维工作中&#xff0c;经常需要一些脚本来实现自动化&#xff0c;今天分享本地文件自动上传FTP的两种解决办法&#xff1a; 一、使用BAT自动上传FTP 使用批处理&#xff08;BAT&#xff09;命令文件将本地文件夹内容上传到FTP服务器需要使用Windows自带的命令行工具&#xf…

使用mysql:5.6和 owncloud 镜像,构建一个个人网盘

一.拉取镜像 docker pull mysql:5.7 docker pull owncloud 二.创建容器 1.MySQL容器 docker run -d --name db1 -p 3306:3306 -e MYSQL_ROOT_PASSWORD123456. -e MYSQL_DATABASEowncloud -e MYSQL_USERowncloud -e MYSQL_PASSWORDowncloud mysql:5.7 docker run: 创建和运行…

2023年03月 C/C++(三级)真题解析#中国电子学会#全国青少年软件编程等级考试

第1题&#xff1a;和数 给定一个正整数序列&#xff0c;判断其中有多少个数&#xff0c;等于数列中其他两个数的和。 比如&#xff0c;对于数列1 2 3 4, 这个问题的答案就是2, 因为3 2 1, 4 1 3。 时间限制&#xff1a;10000 内存限制&#xff1a;65536 输入 共两行&#x…

飞天使-k8s基础组件分析-控制器

文章目录 控制器含义解释pod的标签与注释ReplicaControllerReplicaSetDeploymentsDaemonSetJobCronjob参考文档 控制器含义解释 空调遥控器知道吧ReplicationController: ReplicationController确保在任何时候都运行指定数量的pod副本。换句话说&#xff0c;一个ReplicationCo…

DRF ImageFiled字段时会加上域名和乱码的问题

问题描述&#xff1a; 一张表的image类型为imageFiled # ############################### 商品 ############################### class Category(models.Model):"""类别名称比如&#xff1a;螃蟹、大米、油等"""categoryname models.CharFie…

Unity 之`Physics.Raycast()`方法,射线检测

文章目录 总述参数解释形参前两个变量可以用Ray 来代替 返回值 总述 当你在Unity中使用Physics.Raycast()方法时&#xff0c;你实际上是在进行一种射线检测&#xff0c;以查看一条射线是否与场景中的碰撞体相交。这可以用来实现很多不同的功能&#xff0c;如点击选择物体、射击…

Windows如何安装Git

一、简介 Git 是一个开源的分布式版本控制系统&#xff0c;是目前世界上最先进、最流行的版本控制系统。可以快速高效地处理从很小到非常大的项目版本管理。特点&#xff1a;项目越大越复杂&#xff0c;协同开发者越多&#xff0c;越能体现出 Git 的高性能和高可用性。 二、…

31、springboot 配置HTTP服务端口及如何通过WebServer实例动态获取项目中的HTTP端口

配置HTTP服务端口及如何通过WebServer实例动态获取项目中的HTTP端口 ★ 设置HTTP服务端口&#xff1a; - server.port或者SERVER_PORT环境变量——总结来说&#xff0c;其实就是要配置server.port外部配置属性。▲ 同样遵守如下优先级&#xff1a; 这些都是外部配置源&#x…

探索Vue生命周期钩子函数:从创生到销毁

Vue这个引领前端开发潮流的框架&#xff0c;其优雅的响应式数据绑定和组件式开发方式&#xff0c;使得它备受瞩目。然而&#xff0c;Vue的魅力绝不仅限于此&#xff0c;它还赋予开发者一组神奇的生命周期钩子函数&#xff0c;能够在组件的各个成长阶段插入自定义代码。本文将带…

css 实现四角边框样式

效果如图 此图只实现 左下与右下边角样式 右上与左上同理 /* 容器 */ .card-mini {position: relative; } /* 左下*/ .card-mini::before {content: ;position: absolute;left: 0;bottom: 0;width: 20px;height: 20px;border-bottom: 2px solid #253d64;border-left: 2px so…

赴日程序员学日语需要学多久?

现在很多国内程序员想转型做赴日IT&#xff0c;但是因为完全没有日语经验&#xff0c;又长期从事解决问题debug的工作&#xff0c;所以非常担心自己学不会日语。其实你的心底里可能比较担心是&#xff0c;投入了很多时间发现学不会文科类型的语言&#xff0c;自己没有学母语以外…