Skip to content

Commit a33113b

Browse files
authored
Merge pull request #46 from rsksmart/Stable-Test
Stable test -> master
2 parents 6cde088 + 6585183 commit a33113b

File tree

8 files changed

+532
-128
lines changed

8 files changed

+532
-128
lines changed

contracts/BtcUtils.sol

Lines changed: 138 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -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
}

contracts/OpCodes.sol

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,7 @@ library OpCodes {
99
bytes1 public constant OP_CHECKSIG = 0xac;
1010
bytes1 public constant OP_RETURN = 0x6a;
1111
bytes1 public constant OP_EQUAL = 0x87;
12+
13+
bytes1 public constant OP_0 = 0x00;
14+
bytes1 public constant OP_1 = 0x51;
1215
}

0 commit comments

Comments
 (0)