@@ -13,19 +13,26 @@ library BtcUtils {
1313 uint8 private constant MAX_COMPACT_SIZE_LENGTH = 252 ;
1414 uint8 private constant MAX_BYTES_USED_FOR_COMPACT_SIZE = 8 ;
1515
16+ uint private constant HASH160_SIZE = 20 ;
17+ uint private constant SHA256_SIZE = 32 ;
18+ uint private constant TAPROOT_PUBKEY_SIZE = 32 ;
19+
1620 uint8 private constant OUTPOINT_SIZE = 36 ;
1721 uint8 private constant OUTPUT_VALUE_SIZE = 8 ;
1822
19- uint8 private constant PUBKEY_HASH_SIZE = 20 ;
2023 uint8 private constant PUBKEY_HASH_START = 3 ;
2124 bytes1 private constant PUBKEY_HASH_MAINNET_BYTE = 0x00 ;
2225 bytes1 private constant PUBKEY_HASH_TESTNET_BYTE = 0x6f ;
2326
24- uint8 private constant SCRIPT_HASH_SIZE = 20 ;
2527 uint8 private constant SCRIPT_HASH_START = 2 ;
2628 bytes1 private constant SCRIPT_HASH_MAINNET_BYTE = 0x05 ;
2729 bytes1 private constant SCRIPT_HASH_TESTNET_BYTE = 0xc4 ;
2830
31+ uint private constant BECH32_WORD_SIZE = 5 ;
32+ uint private constant BYTE_SIZE = 8 ;
33+
34+ bytes1 private constant WITNESS_VERSION_0 = 0x00 ;
35+ bytes1 private constant WITNESS_VERSION_1 = 0x01 ;
2936
3037
3138 /**
@@ -39,6 +46,10 @@ library BtcUtils {
3946 uint256 totalSize;
4047 }
4148
49+ function version () external pure returns (string memory ) {
50+ return "0.2.1 " ;
51+ }
52+
4253 /// @notice Parse a raw transaction to get an array of its outputs in a structured representation
4354 /// @param rawTx the raw transaction
4455 /// @return An array of `TxRawOutput` with the outputs of the transaction
@@ -103,18 +114,26 @@ library BtcUtils {
103114 if (isP2SHOutput (outputScript)) {
104115 return parsePayToScriptHash (outputScript, mainnet);
105116 }
106- // TODO add here P2WPKH, P2WSH and P2TR
117+ if (isP2WPKHOutput (outputScript)) {
118+ return parsePayToWitnessPubKeyHash (outputScript);
119+ }
120+ if (isP2WSHOutput (outputScript)) {
121+ return parsePayToWitnessScriptHash (outputScript);
122+ }
123+ if (isP2TROutput (outputScript)) {
124+ return parsePayToTaproot (outputScript);
125+ }
107126 revert ("Unsupported script type " );
108127 }
109128
110129 /// @notice Check if a raw output script is a pay-to-public-key-hash output
111130 /// @param pkScript the fragment of the raw transaction containing the raw output script
112131 /// @return Whether the script has a pay-to-public-key-hash output structure or not
113132 function isP2PKHOutput (bytes memory pkScript ) public pure returns (bool ) {
114- return pkScript.length == 25 &&
133+ return pkScript.length == 5 + HASH160_SIZE &&
115134 pkScript[0 ] == OpCodes.OP_DUP &&
116135 pkScript[1 ] == OpCodes.OP_HASH160 &&
117- uint8 (pkScript[2 ]) == PUBKEY_HASH_SIZE &&
136+ uint8 (pkScript[2 ]) == HASH160_SIZE &&
118137 pkScript[23 ] == OpCodes.OP_EQUALVERIFY &&
119138 pkScript[24 ] == OpCodes.OP_CHECKSIG;
120139 }
@@ -123,12 +142,40 @@ library BtcUtils {
123142 /// @param pkScript the fragment of the raw transaction containing the raw output script
124143 /// @return Whether the script has a pay-to-script-hash output structure or not
125144 function isP2SHOutput (bytes memory pkScript ) public pure returns (bool ) {
126- return pkScript.length == 23 &&
145+ return pkScript.length == 3 + HASH160_SIZE &&
127146 pkScript[0 ] == OpCodes.OP_HASH160 &&
128- uint8 (pkScript[1 ]) == SCRIPT_HASH_SIZE &&
147+ uint8 (pkScript[1 ]) == HASH160_SIZE &&
129148 pkScript[22 ] == OpCodes.OP_EQUAL;
130149 }
131150
151+ /// @notice Check if a raw output script is a pay-to-witness-pubkey-hash output
152+ /// @param pkScript the fragment of the raw transaction containing the raw output script
153+ /// @return Whether the script has a pay-to-witness-pubkey-hash output structure or not
154+ function isP2WPKHOutput (bytes memory pkScript ) public pure returns (bool ) {
155+ return pkScript.length == 2 + HASH160_SIZE &&
156+ pkScript[0 ] == OpCodes.OP_0 &&
157+ uint8 (pkScript[1 ]) == HASH160_SIZE;
158+ }
159+
160+ /// @notice Check if a raw output script is a pay-to-witness-script-hash output
161+ /// @param pkScript the fragment of the raw transaction containing the raw output script
162+ /// @return Whether the script has a pay-to-witness-script-hash output structure or not
163+ function isP2WSHOutput (bytes memory pkScript ) public pure returns (bool ) {
164+ return pkScript.length == 2 + SHA256_SIZE &&
165+ pkScript[0 ] == OpCodes.OP_0 &&
166+ uint8 (pkScript[1 ]) == SHA256_SIZE;
167+ }
168+
169+ /// @notice Check if a raw output script is a pay-to-taproot output
170+ /// @notice Reference for implementation: https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki
171+ /// @param pkScript the fragment of the raw transaction containing the raw output script
172+ /// @return Whether the script has a pay-to-taproot output structure or not
173+ function isP2TROutput (bytes memory pkScript ) public pure returns (bool ) {
174+ return pkScript.length == 2 + TAPROOT_PUBKEY_SIZE &&
175+ pkScript[0 ] == OpCodes.OP_1 &&
176+ uint8 (pkScript[1 ]) == TAPROOT_PUBKEY_SIZE;
177+ }
178+
132179 /// @notice Parse a raw pay-to-public-key-hash output script to get the corresponding address,
133180 /// the resulting byte array doesn't include the checksum bytes of the base58check encoding at
134181 /// the end
@@ -138,8 +185,8 @@ library BtcUtils {
138185 function parsePayToPubKeyHash (bytes calldata outputScript , bool mainnet ) public pure returns (bytes memory ) {
139186 require (isP2PKHOutput (outputScript), "Script hasn't the required structure " );
140187
141- bytes memory destinationAddress = new bytes (PUBKEY_HASH_SIZE + 1 );
142- for (uint8 i = PUBKEY_HASH_START; i < PUBKEY_HASH_SIZE + PUBKEY_HASH_START; i++ ) {
188+ bytes memory destinationAddress = new bytes (HASH160_SIZE + 1 );
189+ for (uint8 i = PUBKEY_HASH_START; i < HASH160_SIZE + PUBKEY_HASH_START; i++ ) {
143190 destinationAddress[i - PUBKEY_HASH_START + 1 ] = outputScript[i];
144191 }
145192
@@ -156,15 +203,63 @@ library BtcUtils {
156203 function parsePayToScriptHash (bytes calldata outputScript , bool mainnet ) public pure returns (bytes memory ) {
157204 require (isP2SHOutput (outputScript), "Script hasn't the required structure " );
158205
159- bytes memory destinationAddress = new bytes (SCRIPT_HASH_SIZE + 1 );
160- for (uint8 i = SCRIPT_HASH_START; i < SCRIPT_HASH_SIZE + SCRIPT_HASH_START; i++ ) {
206+ bytes memory destinationAddress = new bytes (HASH160_SIZE + 1 );
207+ for (uint8 i = SCRIPT_HASH_START; i < HASH160_SIZE + SCRIPT_HASH_START; i++ ) {
161208 destinationAddress[i - SCRIPT_HASH_START + 1 ] = outputScript[i];
162209 }
163210
164211 destinationAddress[0 ] = mainnet? SCRIPT_HASH_MAINNET_BYTE : SCRIPT_HASH_TESTNET_BYTE;
165212 return destinationAddress;
166213 }
167214
215+ /// @notice Parse a raw pay-to-witness-pubkey-hash output script to get the corresponding address words,
216+ /// the resulting words are only the data part of the bech32 encoding and doesn't include the HRP
217+ /// @param outputScript the fragment of the raw transaction containing the raw output script
218+ /// @return The address bech32 words generated using the pubkey hash
219+ function parsePayToWitnessPubKeyHash (bytes calldata outputScript ) public pure returns (bytes memory ) {
220+ require (isP2WPKHOutput (outputScript), "Script hasn't the required structure " );
221+ uint length = 1 + total5BitWords (HASH160_SIZE);
222+ bytes memory result = new bytes (length);
223+ result[0 ] = WITNESS_VERSION_0;
224+ bytes memory words = to5BitWords (outputScript[2 :]);
225+ for (uint i = 1 ; i < length; i++ ) {
226+ result[i] = words[i - 1 ];
227+ }
228+ return result;
229+ }
230+
231+ /// @notice Parse a raw pay-to-witness-script-hash output script to get the corresponding address words,
232+ /// the resulting words are only the data part of the bech32 encoding and doesn't include the HRP
233+ /// @param outputScript the fragment of the raw transaction containing the raw output script
234+ /// @return The address bech32 words generated using the script hash
235+ function parsePayToWitnessScriptHash (bytes calldata outputScript ) public pure returns (bytes memory ) {
236+ require (isP2WSHOutput (outputScript), "Script hasn't the required structure " );
237+ uint length = 1 + total5BitWords (SHA256_SIZE);
238+ bytes memory result = new bytes (length);
239+ result[0 ] = WITNESS_VERSION_0;
240+ bytes memory words = to5BitWords (outputScript[2 :]);
241+ for (uint i = 1 ; i < length; i++ ) {
242+ result[i] = words[i - 1 ];
243+ }
244+ return result;
245+ }
246+
247+ /// @notice Parse a raw pay-to-taproot output script to get the corresponding address words,
248+ /// the resulting words are only the data part of the bech32m encoding and doesn't include the HRP
249+ /// @param outputScript the fragment of the raw transaction containing the raw output script
250+ /// @return The address bech32m words generated using the taproot pubkey hash
251+ function parsePayToTaproot (bytes calldata outputScript ) public pure returns (bytes memory ) {
252+ require (isP2TROutput (outputScript), "Script hasn't the required structure " );
253+ uint length = 1 + total5BitWords (TAPROOT_PUBKEY_SIZE);
254+ bytes memory result = new bytes (length);
255+ result[0 ] = WITNESS_VERSION_1;
256+ bytes memory words = to5BitWords (outputScript[2 :]);
257+ for (uint i = 1 ; i < length; i++ ) {
258+ result[i] = words[i - 1 ];
259+ }
260+ return result;
261+ }
262+
168263 /// @notice Parse a raw null-data output script to get its content
169264 /// @param outputScript the fragment of the raw transaction containing the raw output script
170265 /// @return The content embedded inside the script
@@ -271,4 +366,36 @@ library BtcUtils {
271366 }
272367 return result;
273368 }
369+
370+ /// @notice Referece for implementation: https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki
371+ function to5BitWords (bytes memory byteArray ) private pure returns (bytes memory ) {
372+ uint8 MAX_VALUE = 31 ;
373+
374+ uint currentValue = 0 ;
375+ uint bitCount = 0 ;
376+ uint8 resultIndex = 0 ;
377+ bytes memory result = new bytes (total5BitWords (byteArray.length ));
378+
379+ for (uint i = 0 ; i < byteArray.length ; ++ i) {
380+ currentValue = (currentValue << BYTE_SIZE) | uint8 (byteArray[i]);
381+ bitCount += BYTE_SIZE;
382+ while (bitCount >= BECH32_WORD_SIZE) {
383+ bitCount -= BECH32_WORD_SIZE;
384+ // this mask ensures that the result will always have 5 bits
385+ result[resultIndex] = bytes1 (uint8 ((currentValue >> bitCount) & MAX_VALUE));
386+ resultIndex++ ;
387+ }
388+ }
389+
390+ if (bitCount > 0 ) {
391+ result[resultIndex] = bytes1 (uint8 ((currentValue << (BECH32_WORD_SIZE - bitCount)) & MAX_VALUE));
392+ }
393+ return result;
394+ }
395+
396+ function total5BitWords (uint numberOfBytes ) private pure returns (uint ) {
397+ uint total = (numberOfBytes * BYTE_SIZE) / BECH32_WORD_SIZE;
398+ bool extraWord = (numberOfBytes * BYTE_SIZE) % BECH32_WORD_SIZE == 0 ;
399+ return total + (extraWord? 0 : 1 );
400+ }
274401}
0 commit comments