From 23c0b0a05df066446b6312703428a4d18ca01cd9 Mon Sep 17 00:00:00 2001 From: Gregory Hill Date: Mon, 9 Oct 2023 14:17:05 +0100 Subject: [PATCH] chore: update contracts to v3.4.0-solc-0.8 Signed-off-by: Gregory Hill --- script/ValidateSPV.s.sol | 2 +- src/BTCUtils.sol | 97 +++++++++++++++++++++++++++++++------ src/ValidateSPV.sol | 68 +++++++++++++------------- src/ValidateSPVDelegate.sol | 2 +- 4 files changed, 117 insertions(+), 52 deletions(-) diff --git a/script/ValidateSPV.s.sol b/script/ValidateSPV.s.sol index cd4a614..fc86d19 100644 --- a/script/ValidateSPV.s.sol +++ b/script/ValidateSPV.s.sol @@ -80,6 +80,6 @@ contract ValidateSPVScript { /// @param _prevHeaderDigest The previous header's digest /// @return true if header chain is valid, false otherwise function validateHeaderPrevHash(bytes memory _header, bytes32 _prevHeaderDigest) public returns (bool) { - return ValidateSPV.validateHeaderPrevHash(_header, _prevHeaderDigest); + return ValidateSPV.validateHeaderPrevHash(_header, 0, _prevHeaderDigest); } } diff --git a/src/BTCUtils.sol b/src/BTCUtils.sol index c4e74e1..bb900af 100644 --- a/src/BTCUtils.sol +++ b/src/BTCUtils.sol @@ -248,6 +248,25 @@ library BTCUtils { } } + /// @notice Implements bitcoin's hash256 (double sha2) + /// @dev sha2 is precompiled smart contract located at address(2) + /// @param _b The array containing the pre-image + /// @param at The start of the pre-image + /// @param len The length of the pre-image + /// @return res The digest + function hash256Slice( + bytes memory _b, + uint256 at, + uint256 len + ) internal view returns (bytes32 res) { + // solium-disable-next-line security/no-inline-assembly + assembly { + pop(staticcall(gas(), 2, add(_b, add(32, at)), len, 0x00, 32)) + pop(staticcall(gas(), 2, 0x00, 32, 0x00, 32)) + res := mload(0x00) + } + } + /* ************ */ /* Legacy Input */ /* ************ */ @@ -517,6 +536,17 @@ library BTCUtils { return _beValue; } + /// @notice Extracts the value from the output in a tx + /// @dev Value is an 8-byte little-endian number + /// @param _output The byte array containing the output + /// @param _at The starting index of the output in the array + /// @return The output value + function extractValueAt(bytes memory _output, uint256 _at) internal pure returns (uint64) { + uint64 _leValue = uint64(_output.slice8(_at)); + uint64 _beValue = reverseUint64(_leValue); + return _beValue; + } + /// @notice Extracts the data from an op return output /// @dev Returns hex"" if no data or not an op return /// @param _output The output @@ -534,46 +564,62 @@ library BTCUtils { /// @param _output The output /// @return The hash committed to by the pk_script, or null for errors function extractHash(bytes memory _output) internal pure returns (bytes memory) { - uint8 _scriptLen = uint8(_output[8]); + return extractHashAt(_output, 8, _output.length - 8); + } + + /// @notice Extracts the hash from the output script + /// @dev Determines type by the length prefix and validates format + /// @param _output The byte array containing the output + /// @param _at The starting index of the output script in the array + /// (output start + 8) + /// @param _len The length of the output script + /// (output length - 8) + /// @return The hash committed to by the pk_script, or null for errors + function extractHashAt( + bytes memory _output, + uint256 _at, + uint256 _len + ) internal pure returns (bytes memory) { + uint8 _scriptLen = uint8(_output[_at]); // don't have to worry about overflow here. - // if _scriptLen + 9 overflows, then output.length would have to be < 9 - // for this check to pass. if it's < 9, then we errored when assigning + // if _scriptLen + 1 overflows, then output length would have to be < 1 + // for this check to pass. if it's < 1, then we errored when assigning // _scriptLen - if (_scriptLen + 9 != _output.length) { + if (_scriptLen + 1 != _len) { return hex""; } - if (uint8(_output[9]) == 0) { + if (uint8(_output[_at + 1]) == 0) { if (_scriptLen < 2) { return hex""; } - uint256 _payloadLen = uint8(_output[10]); + uint256 _payloadLen = uint8(_output[_at + 2]); // Check for maliciously formatted witness outputs. // No need to worry about underflow as long b/c of the `< 2` check if (_payloadLen != _scriptLen - 2 || (_payloadLen != 0x20 && _payloadLen != 0x14)) { return hex""; } - return _output.slice(11, _payloadLen); + return _output.slice(_at + 3, _payloadLen); } else { - bytes3 _tag = _output.slice3(8); + bytes3 _tag = _output.slice3(_at); // p2pkh if (_tag == hex"1976a9") { // Check for maliciously formatted p2pkh // No need to worry about underflow, b/c of _scriptLen check - if (uint8(_output[11]) != 0x14 || - _output.slice2(_output.length - 2) != hex"88ac") { + if (uint8(_output[_at + 3]) != 0x14 || + _output.slice2(_at + _len - 2) != hex"88ac") { return hex""; } - return _output.slice(12, 20); + return _output.slice(_at + 4, 20); //p2sh } else if (_tag == hex"17a914") { // Check for maliciously formatted p2sh // No need to worry about underflow, b/c of _scriptLen check - if (uint8(_output[_output.length - 1]) != 0x87) { + if (uint8(_output[_at + _len - 1]) != 0x87) { return hex""; } - return _output.slice(11, 20); + return _output.slice(_at + 3, 20); } } return hex""; /* NB: will trigger on OPRETURN and any non-standard that doesn't overrun */ @@ -677,8 +723,17 @@ library BTCUtils { /// @param _header The header /// @return The target threshold function extractTarget(bytes memory _header) internal pure returns (uint256) { - uint24 _m = uint24(_header.slice3(72)); - uint8 _e = uint8(_header[75]); + return extractTargetAt(_header, 0); + } + + /// @notice Extracts the target from a block header + /// @dev Target is a 256-bit number encoded as a 3-byte mantissa and 1-byte exponent + /// @param _header The array containing the header + /// @param at The start of the header + /// @return The target threshold + function extractTargetAt(bytes memory _header, uint256 at) internal pure returns (uint256) { + uint24 _m = uint24(_header.slice3(72 + at)); + uint8 _e = uint8(_header[75 + at]); uint256 _mantissa = uint256(reverseUint24(_m)); uint _exponent = _e - 3; @@ -703,6 +758,18 @@ library BTCUtils { return _header.slice32(4); } + /// @notice Extracts the previous block's hash from a block header + /// @dev Block headers do NOT include block number :( + /// @param _header The array containing the header + /// @param at The start of the header + /// @return The previous block's hash (little-endian) + function extractPrevBlockLEAt( + bytes memory _header, + uint256 at + ) internal pure returns (bytes32) { + return _header.slice32(4 + at); + } + /// @notice Extracts the timestamp from a block header /// @dev Time is not 100% reliable /// @param _header The header diff --git a/src/ValidateSPV.sol b/src/ValidateSPV.sol index c2b7648..09f2893 100644 --- a/src/ValidateSPV.sol +++ b/src/ValidateSPV.sol @@ -80,72 +80,70 @@ library ValidateSPV { /// @notice Checks validity of header chain /// @notice Compares the hash of each header to the prevHash in the next header - /// @param _headers Raw byte array of header chain - /// @return _totalDifficulty The total accumulated difficulty of the header chain, or an error code - function validateHeaderChain(bytes memory _headers) internal view returns (uint256 _totalDifficulty) { + /// @param headers Raw byte array of header chain + /// @return totalDifficulty The total accumulated difficulty of the header chain, or an error code + function validateHeaderChain( + bytes memory headers + ) internal view returns (uint256 totalDifficulty) { // Check header chain length - if (_headers.length % 80 != 0) {return ERR_BAD_LENGTH;} + if (headers.length % 80 != 0) {return ERR_BAD_LENGTH;} // Initialize header start index - bytes32 _digest; + bytes32 digest; - _totalDifficulty = 0; + totalDifficulty = 0; - bytes memory _header; - - // Allocate _header with extra space after it to fit 3 full words - assembly { - _header := mload(0x40) - mstore(0x40, add(_header, add(32, 96))) - mstore(_header, 80) - } - - for (uint256 _start = 0; _start < _headers.length; _start += 80) { - - // ith header start index and ith header - _headers.sliceInPlace(_header, _start); + for (uint256 start = 0; start < headers.length; start += 80) { // After the first header, check that headers are in a chain - if (_start != 0) { - if (!validateHeaderPrevHash(_header, _digest)) {return ERR_INVALID_CHAIN;} + if (start != 0) { + if (!validateHeaderPrevHash(headers, start, digest)) {return ERR_INVALID_CHAIN;} } // ith header target - uint256 _target = _header.extractTarget(); + uint256 target = headers.extractTargetAt(start); // Require that the header has sufficient work - _digest = _header.hash256View(); - if(uint256(_digest).reverseUint256() > _target) { + digest = headers.hash256Slice(start, 80); + if(uint256(digest).reverseUint256() > target) { return ERR_LOW_WORK; } // Add ith header difficulty to difficulty sum - _totalDifficulty = _totalDifficulty.add(_target.calculateDifficulty()); + totalDifficulty = totalDifficulty + target.calculateDifficulty(); } } /// @notice Checks validity of header work - /// @param _digest Header digest - /// @param _target The target threshold + /// @param digest Header digest + /// @param target The target threshold /// @return true if header work is valid, false otherwise - function validateHeaderWork(bytes32 _digest, uint256 _target) internal pure returns (bool) { - if (_digest == bytes32(0)) {return false;} - return (uint256(_digest).reverseUint256() < _target); + function validateHeaderWork( + bytes32 digest, + uint256 target + ) internal pure returns (bool) { + if (digest == bytes32(0)) {return false;} + return (uint256(digest).reverseUint256() < target); } /// @notice Checks validity of header chain /// @dev Compares current header prevHash to previous header's digest - /// @param _header The raw bytes header - /// @param _prevHeaderDigest The previous header's digest + /// @param headers The raw bytes array containing the header + /// @param at The position of the header + /// @param prevHeaderDigest The previous header's digest /// @return true if the connect is valid, false otherwise - function validateHeaderPrevHash(bytes memory _header, bytes32 _prevHeaderDigest) internal pure returns (bool) { + function validateHeaderPrevHash( + bytes memory headers, + uint256 at, + bytes32 prevHeaderDigest + ) internal pure returns (bool) { // Extract prevHash of current header - bytes32 _prevHash = _header.extractPrevBlockLE(); + bytes32 prevHash = headers.extractPrevBlockLEAt(at); // Compare prevHash of current header to previous header's digest - if (_prevHash != _prevHeaderDigest) {return false;} + if (prevHash != prevHeaderDigest) {return false;} return true; } diff --git a/src/ValidateSPVDelegate.sol b/src/ValidateSPVDelegate.sol index 4a1e136..c47a180 100644 --- a/src/ValidateSPVDelegate.sol +++ b/src/ValidateSPVDelegate.sol @@ -73,6 +73,6 @@ library ValidateSPVDelegate { /// @param _prevHeaderDigest The previous header's digest /// @return true if header chain is valid, false otherwise function validateHeaderPrevHash(bytes memory _header, bytes32 _prevHeaderDigest) public pure returns (bool) { - return ValidateSPV.validateHeaderPrevHash(_header, _prevHeaderDigest); + return ValidateSPV.validateHeaderPrevHash(_header, 0, _prevHeaderDigest); } }