Skip to content

Commit

Permalink
Merge pull request #4 from bob-collective/chore/v3.4.0-solc-0.8
Browse files Browse the repository at this point in the history
chore: update contracts to v3.4.0-solc-0.8
  • Loading branch information
gregdhill authored Oct 9, 2023
2 parents 721155b + 23c0b0a commit 507d01b
Show file tree
Hide file tree
Showing 4 changed files with 117 additions and 52 deletions.
2 changes: 1 addition & 1 deletion script/ValidateSPV.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
97 changes: 82 additions & 15 deletions src/BTCUtils.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
/* ************ */
Expand Down Expand Up @@ -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
Expand All @@ -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 */
Expand Down Expand Up @@ -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;

Expand All @@ -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
Expand Down
68 changes: 33 additions & 35 deletions src/ValidateSPV.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
2 changes: 1 addition & 1 deletion src/ValidateSPVDelegate.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}

0 comments on commit 507d01b

Please sign in to comment.