From 92684ce1a470b308445e7754768d1ffee5a264e2 Mon Sep 17 00:00:00 2001 From: Daniel Graczer Date: Fri, 26 Jul 2024 18:57:44 +0700 Subject: [PATCH 1/3] feat: allow extracting hash from output --- src/utils/BitcoinTx.sol | 122 ++++++++++++++++++++++++++++++++-------- 1 file changed, 97 insertions(+), 25 deletions(-) diff --git a/src/utils/BitcoinTx.sol b/src/utils/BitcoinTx.sol index 796374de..f4858458 100644 --- a/src/utils/BitcoinTx.sol +++ b/src/utils/BitcoinTx.sol @@ -202,6 +202,12 @@ library BitcoinTx { ); } + /// @notice Type of data after the OP_RETURN code + enum OutputType { + EVM_ADDRESS, + HASH + } + /// @notice Represents temporary information needed during the processing of /// the Bitcoin transaction outputs. This structure is an internal one /// and should not be exported outside of the transaction processing code. @@ -211,10 +217,12 @@ library BitcoinTx { uint256 outputStartingIndex; // The number of outputs in the transaction. uint256 outputsCount; + // The type of data after the OP_RETURN code. + OutputType outputType; } /// @notice Represents an outcome of the Bitcoin transaction - /// outputs processing. + /// outputs processing potentially containing evm address. struct TxOutputsInfo { // Sum of all outputs values spending to the output. uint64 value; @@ -222,7 +230,27 @@ library BitcoinTx { address evmAddress; } - /// @notice Processes the Bitcoin transaction output vector. + /// @notice Represents an outcome of the Bitcoin transaction + /// outputs processing potentially containing 32 byte hash. + struct TxOutputsInfoHash { + // Sum of all outputs values spending to the output. + uint64 value; + // Optional hash specified in OP_RETURN. + bytes32 hash; + } + + /// @notice Represents an outcome of the Bitcoin transaction + /// outputs processing including either evm address or 32 bytes hash. + struct ExtendedTxOutputsInfo { + // Sum of all outputs values spending to the output. + uint64 value; + // Optional EVM address specified in OP_RETURN. + address evmAddress; + // Optional hash specified in OP_RETURN. + bytes32 hash; + } + + /// @notice Processes the Bitcoin transaction output vector potentially containing EVM address. /// @param txOutputVector Bitcoin transaction output vector. /// This function assumes vector's structure is valid so it /// must be validated using e.g. `BTCUtils.validateVout` function @@ -234,9 +262,46 @@ library BitcoinTx { pure returns (TxOutputsInfo memory resultInfo) { + ExtendedTxOutputsInfo memory extendedResultInfo = + processTxOutputs(txOutputVector, scriptPubKeyHash, OutputType.EVM_ADDRESS); + return TxOutputsInfo(extendedResultInfo.value, extendedResultInfo.evmAddress); + } + + /// @notice Processes the Bitcoin transaction output vector potentially containing 32 byte hash. + /// @param txOutputVector Bitcoin transaction output vector. + /// This function assumes vector's structure is valid so it + /// must be validated using e.g. `BTCUtils.validateVout` function + /// before it is passed here. + /// @param scriptPubKeyHash Expected Bitcoin scriptPubKey keccak256 hash. + /// @return resultInfo Outcomes of the processing. + function processTxOutputsHash(bytes memory txOutputVector, bytes32 scriptPubKeyHash) + internal + pure + returns (TxOutputsInfoHash memory resultInfo) + { + ExtendedTxOutputsInfo memory extendedResultInfo = + processTxOutputs(txOutputVector, scriptPubKeyHash, OutputType.HASH); + return TxOutputsInfoHash(extendedResultInfo.value, extendedResultInfo.hash); + } + + /// @notice Processes all outputs from the transaction. + /// @param txOutputVector Bitcoin transaction output vector. This function + /// assumes vector's structure is valid so it must be validated using + /// e.g. `BTCUtils.validateVout` function before it is passed here. + /// @param scriptPubKeyHash Expected Bitcoin scriptPubKey keccak256 hash. + /// @param outputType The type of extra data in the UTXO for example evm address or hash + /// starting index and the number of outputs. + function processTxOutputs(bytes memory txOutputVector, bytes32 scriptPubKeyHash, OutputType outputType) + internal + pure + returns (ExtendedTxOutputsInfo memory resultInfo) + { + // needed to avoid stack too deep errors + TxOutputsProcessingInfo memory processInfo; + // Determining the total number of transaction outputs in the same way as // for number of inputs. See `BitcoinTx.outputVector` docs for more details. - (uint256 outputsCompactSizeUintLength, uint256 outputsCount) = txOutputVector.parseVarInt(); + (processInfo.outputStartingIndex, processInfo.outputsCount) = txOutputVector.parseVarInt(); // To determine the first output starting index, we must jump over // the compactSize uint which prepends the output vector. One byte @@ -253,25 +318,9 @@ library BitcoinTx { // // Please refer `BTCUtils` library and compactSize uint // docs in `BitcoinTx` library for more details. - uint256 outputStartingIndex = 1 + outputsCompactSizeUintLength; + processInfo.outputStartingIndex++; + processInfo.outputType = outputType; - return processTxOutputs( - txOutputVector, scriptPubKeyHash, TxOutputsProcessingInfo(outputStartingIndex, outputsCount) - ); - } - - /// @notice Processes all outputs from the transaction. - /// @param txOutputVector Bitcoin transaction output vector. This function - /// assumes vector's structure is valid so it must be validated using - /// e.g. `BTCUtils.validateVout` function before it is passed here. - /// @param scriptPubKeyHash Expected Bitcoin scriptPubKey keccak256 hash. - /// @param processInfo TxOutputsProcessingInfo identifying output - /// starting index and the number of outputs. - function processTxOutputs( - bytes memory txOutputVector, - bytes32 scriptPubKeyHash, - TxOutputsProcessingInfo memory processInfo - ) internal pure returns (TxOutputsInfo memory resultInfo) { // Helper flag indicating whether there was at least one // output present bool outputPresent = false; @@ -306,10 +355,19 @@ library BitcoinTx { // payments to the same output resultInfo.value += outputValue; } else { - address outputEvmAddress = extractEvmAddressFromOutput(txOutputVector, processInfo.outputStartingIndex); - if (outputEvmAddress != address(0)) { - // NOTE: this will overwrite if there are multiple OP_RETURN outputs - resultInfo.evmAddress = outputEvmAddress; + if (processInfo.outputType == OutputType.EVM_ADDRESS) { + address outputEvmAddress = + extractEvmAddressFromOutput(txOutputVector, processInfo.outputStartingIndex); + if (outputEvmAddress != address(0)) { + // NOTE: this will overwrite if there are multiple OP_RETURN outputs + resultInfo.evmAddress = outputEvmAddress; + } + } else if (processInfo.outputType == OutputType.HASH) { + bytes32 outputHash = extractHashFromOutput(txOutputVector, processInfo.outputStartingIndex); + if (outputHash != 0) { + // NOTE: this will overwrite if there are multiple OP_RETURN outputs + resultInfo.hash = outputHash; + } } } @@ -339,6 +397,20 @@ library BitcoinTx { } } + function extractHashFromOutput(bytes memory _output, uint256 _at) internal pure returns (bytes32 outputHash) { + // OP_RETURN + if (_output[_at + 9] != hex"6a") { + return 0; + } + bytes1 _dataLen = _output[_at + 10]; + if (uint256(uint8(_dataLen)) == 32) { + uint256 opReturnStart = _at + 11; + assembly { + outputHash := mload(add(_output, add(opReturnStart, 32))) + } + } + } + function reverseEndianness(bytes32 b) internal pure returns (bytes32 txHash) { bytes memory newValue = new bytes(b.length); for (uint256 i = 0; i < b.length; i++) { From 899e151c663c44c51d767763878ae45fdb7541ea Mon Sep 17 00:00:00 2001 From: Daniel Graczer Date: Mon, 29 Jul 2024 16:11:13 +0700 Subject: [PATCH 2/3] feat: minor fixes after review --- src/utils/BitcoinTx.sol | 92 ++++++----------------------------------- test/BitcoinTx.t.sol | 11 ++++- 2 files changed, 23 insertions(+), 80 deletions(-) diff --git a/src/utils/BitcoinTx.sol b/src/utils/BitcoinTx.sol index f4858458..f6cd5e7b 100644 --- a/src/utils/BitcoinTx.sol +++ b/src/utils/BitcoinTx.sol @@ -202,12 +202,6 @@ library BitcoinTx { ); } - /// @notice Type of data after the OP_RETURN code - enum OutputType { - EVM_ADDRESS, - HASH - } - /// @notice Represents temporary information needed during the processing of /// the Bitcoin transaction outputs. This structure is an internal one /// and should not be exported outside of the transaction processing code. @@ -217,84 +211,28 @@ library BitcoinTx { uint256 outputStartingIndex; // The number of outputs in the transaction. uint256 outputsCount; - // The type of data after the OP_RETURN code. - OutputType outputType; } /// @notice Represents an outcome of the Bitcoin transaction - /// outputs processing potentially containing evm address. + /// outputs processing. struct TxOutputsInfo { // Sum of all outputs values spending to the output. uint64 value; // Optional EVM address specified in OP_RETURN. address evmAddress; - } - - /// @notice Represents an outcome of the Bitcoin transaction - /// outputs processing potentially containing 32 byte hash. - struct TxOutputsInfoHash { - // Sum of all outputs values spending to the output. - uint64 value; // Optional hash specified in OP_RETURN. bytes32 hash; } - /// @notice Represents an outcome of the Bitcoin transaction - /// outputs processing including either evm address or 32 bytes hash. - struct ExtendedTxOutputsInfo { - // Sum of all outputs values spending to the output. - uint64 value; - // Optional EVM address specified in OP_RETURN. - address evmAddress; - // Optional hash specified in OP_RETURN. - bytes32 hash; - } - - /// @notice Processes the Bitcoin transaction output vector potentially containing EVM address. - /// @param txOutputVector Bitcoin transaction output vector. - /// This function assumes vector's structure is valid so it - /// must be validated using e.g. `BTCUtils.validateVout` function - /// before it is passed here. - /// @param scriptPubKeyHash Expected Bitcoin scriptPubKey keccak256 hash. - /// @return resultInfo Outcomes of the processing. - function processTxOutputs(bytes memory txOutputVector, bytes32 scriptPubKeyHash) - internal - pure - returns (TxOutputsInfo memory resultInfo) - { - ExtendedTxOutputsInfo memory extendedResultInfo = - processTxOutputs(txOutputVector, scriptPubKeyHash, OutputType.EVM_ADDRESS); - return TxOutputsInfo(extendedResultInfo.value, extendedResultInfo.evmAddress); - } - - /// @notice Processes the Bitcoin transaction output vector potentially containing 32 byte hash. - /// @param txOutputVector Bitcoin transaction output vector. - /// This function assumes vector's structure is valid so it - /// must be validated using e.g. `BTCUtils.validateVout` function - /// before it is passed here. - /// @param scriptPubKeyHash Expected Bitcoin scriptPubKey keccak256 hash. - /// @return resultInfo Outcomes of the processing. - function processTxOutputsHash(bytes memory txOutputVector, bytes32 scriptPubKeyHash) - internal - pure - returns (TxOutputsInfoHash memory resultInfo) - { - ExtendedTxOutputsInfo memory extendedResultInfo = - processTxOutputs(txOutputVector, scriptPubKeyHash, OutputType.HASH); - return TxOutputsInfoHash(extendedResultInfo.value, extendedResultInfo.hash); - } - /// @notice Processes all outputs from the transaction. /// @param txOutputVector Bitcoin transaction output vector. This function /// assumes vector's structure is valid so it must be validated using /// e.g. `BTCUtils.validateVout` function before it is passed here. /// @param scriptPubKeyHash Expected Bitcoin scriptPubKey keccak256 hash. - /// @param outputType The type of extra data in the UTXO for example evm address or hash - /// starting index and the number of outputs. - function processTxOutputs(bytes memory txOutputVector, bytes32 scriptPubKeyHash, OutputType outputType) + function processTxOutputs(bytes memory txOutputVector, bytes32 scriptPubKeyHash) internal pure - returns (ExtendedTxOutputsInfo memory resultInfo) + returns (TxOutputsInfo memory resultInfo) { // needed to avoid stack too deep errors TxOutputsProcessingInfo memory processInfo; @@ -319,7 +257,6 @@ library BitcoinTx { // Please refer `BTCUtils` library and compactSize uint // docs in `BitcoinTx` library for more details. processInfo.outputStartingIndex++; - processInfo.outputType = outputType; // Helper flag indicating whether there was at least one // output present @@ -355,19 +292,16 @@ library BitcoinTx { // payments to the same output resultInfo.value += outputValue; } else { - if (processInfo.outputType == OutputType.EVM_ADDRESS) { - address outputEvmAddress = - extractEvmAddressFromOutput(txOutputVector, processInfo.outputStartingIndex); - if (outputEvmAddress != address(0)) { - // NOTE: this will overwrite if there are multiple OP_RETURN outputs - resultInfo.evmAddress = outputEvmAddress; - } - } else if (processInfo.outputType == OutputType.HASH) { - bytes32 outputHash = extractHashFromOutput(txOutputVector, processInfo.outputStartingIndex); - if (outputHash != 0) { - // NOTE: this will overwrite if there are multiple OP_RETURN outputs - resultInfo.hash = outputHash; - } + address outputEvmAddress = extractEvmAddressFromOutput(txOutputVector, processInfo.outputStartingIndex); + if (outputEvmAddress != address(0)) { + // NOTE: this will overwrite if there are multiple OP_RETURN outputs + resultInfo.evmAddress = outputEvmAddress; + } + + bytes32 outputHash = extractHashFromOutput(txOutputVector, processInfo.outputStartingIndex); + if (outputHash != 0) { + // NOTE: this will overwrite if there are multiple OP_RETURN outputs + resultInfo.hash = outputHash; } } diff --git a/test/BitcoinTx.t.sol b/test/BitcoinTx.t.sol index f39be877..1321b4f5 100644 --- a/test/BitcoinTx.t.sol +++ b/test/BitcoinTx.t.sol @@ -156,7 +156,7 @@ contract BitcoinTxTest is Test { assertFalse(success); } - function test_ProcessTxOutputsWithOpReturn() public { + function test_ProcessTxOutputsWithOpReturnAddress() public { BitcoinTx.TxOutputsInfo memory resultInfo = BitcoinTx.processTxOutputs( hex"02983a000000000000146142b39c0073672dc382b89a42b29e06368bcabd0000000000000000166a14675ca18a04027fd50c88ccd03939e0e5c97b795f", keccak256(hex"146142b39c0073672dc382b89a42b29e06368bcabd") @@ -164,4 +164,13 @@ contract BitcoinTxTest is Test { assertEq(resultInfo.value, 15000); assertEq(resultInfo.evmAddress, 0x675Ca18A04027fd50C88CcD03939E0e5C97b795f); } + + function test_ProcessTxOutputsWithOpReturnBytes32() public { + BitcoinTx.TxOutputsInfo memory resultInfo = BitcoinTx.processTxOutputs( + hex"02983a000000000000146142b39c0073672dc382b89a42b29e06368bcabd0000000000000000166a2000112233445566778899001122334455667788990011", + keccak256(hex"146142b39c0073672dc382b89a42b29e06368bcabd") + ); + assertEq(resultInfo.value, 15000); + assertEq(resultInfo.hash, hex"00112233445566778899001122334455667788990011"); + } } From fc56c7639b204bea541d2082e92c285b5cd852f2 Mon Sep 17 00:00:00 2001 From: ferencdg Date: Mon, 29 Jul 2024 09:12:22 +0000 Subject: [PATCH 3/3] chore: Generate Foundry docs --- .../utils/BitcoinTx.sol/library.BitcoinTx.md | 31 ++++--------------- 1 file changed, 6 insertions(+), 25 deletions(-) diff --git a/docs/docs/contracts/src/src/utils/BitcoinTx.sol/library.BitcoinTx.md b/docs/docs/contracts/src/src/utils/BitcoinTx.sol/library.BitcoinTx.md index d0bf5149..ffb574ab 100644 --- a/docs/docs/contracts/src/src/utils/BitcoinTx.sol/library.BitcoinTx.md +++ b/docs/docs/contracts/src/src/utils/BitcoinTx.sol/library.BitcoinTx.md @@ -58,7 +58,7 @@ function evaluateProofDifficulty(IRelay relay, uint256 txProofDifficultyFactor, ### processTxOutputs -Processes the Bitcoin transaction output vector. +Processes all outputs from the transaction. ```solidity @@ -74,39 +74,19 @@ function processTxOutputs(bytes memory txOutputVector, bytes32 scriptPubKeyHash) |`txOutputVector`|`bytes`|Bitcoin transaction output vector. This function assumes vector's structure is valid so it must be validated using e.g. `BTCUtils.validateVout` function before it is passed here.| |`scriptPubKeyHash`|`bytes32`|Expected Bitcoin scriptPubKey keccak256 hash.| -**Returns** - -|Name|Type|Description| -|----|----|-----------| -|`resultInfo`|`TxOutputsInfo`|Outcomes of the processing.| - - -### processTxOutputs -Processes all outputs from the transaction. +### extractEvmAddressFromOutput ```solidity -function processTxOutputs( - bytes memory txOutputVector, - bytes32 scriptPubKeyHash, - TxOutputsProcessingInfo memory processInfo -) internal pure returns (TxOutputsInfo memory resultInfo); +function extractEvmAddressFromOutput(bytes memory _output, uint256 _at) internal pure returns (address evmAddress); ``` -**Parameters** - -|Name|Type|Description| -|----|----|-----------| -|`txOutputVector`|`bytes`|Bitcoin transaction output vector. This function assumes vector's structure is valid so it must be validated using e.g. `BTCUtils.validateVout` function before it is passed here.| -|`scriptPubKeyHash`|`bytes32`|Expected Bitcoin scriptPubKey keccak256 hash.| -|`processInfo`|`TxOutputsProcessingInfo`|TxOutputsProcessingInfo identifying output starting index and the number of outputs.| - -### extractEvmAddressFromOutput +### extractHashFromOutput ```solidity -function extractEvmAddressFromOutput(bytes memory _output, uint256 _at) internal pure returns (address evmAddress); +function extractHashFromOutput(bytes memory _output, uint256 _at) internal pure returns (bytes32 outputHash); ``` ### reverseEndianness @@ -185,6 +165,7 @@ outputs processing. struct TxOutputsInfo { uint64 value; address evmAddress; + bytes32 hash; } ```