@rsksmart/btc-transaction-solidity-helper 0.2.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,179 @@
1
+ [![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/rsksmart/btc-transaction-solidity-helper/badge)](https://scorecard.dev/viewer/?uri=github.com/rsksmart/btc-transaction-solidity-helper)
2
+ <img src="img/rootstock-docs.png" alt="RSK Logo" style="width:100%; height: auto;" />
3
+
4
+ # BTC Transaction Solidity Helper
5
+
6
+ Bitcoin, a decentralized digital currency, serves as both a store of value and a means of transferring wealth. Its security is rooted in the blockchain, a distributed ledger maintained by a network of miners. These miners expend significant computational power and energy to create new blocks, which are added to the blockchain every 10 minutes. The more hashing power contributed by miners, the more secure the network becomes. [Learn more about Bitcoin](https://developer.bitcoin.org/index.html).
7
+
8
+ Rootstock, the pioneering open-source smart contract platform built on Bitcoin, aims to enhance the Bitcoin ecosystem by introducing smart contract functionality, near-instant payments, and improved scalability. Its comprehensive technology stack, encompassing Rootstock smart contracts and the Rootstock Infrastructure Framework, is designed to foster a more equitable and inclusive financial system. Read more about the [Rootstock Stack](/concepts/fundamentals/stack/).
9
+
10
+ The [Bitcoin Solidity helper library](https://github.com/rsksmart/btc-transaction-solidity-helper) facilitates seamless interaction between Bitcoin transactions and Solidity smart contracts on the Rootstock platform. In this guide, we will learn how to handle Bitcoin transactions in a Solidity Smart contract, we will also learn how to parse transactions, hash transactions and validate scripts for bitcoin transactions. You can find the public repository for the [bitcoin transaction solidity helper library](https://github.com/rsksmart/btc-transaction-solidity-helper).
11
+
12
+ ## Features of the Library
13
+
14
+ The features of the Bitcoin Solidity Helper library include:
15
+ 1. Bitcoin transaction output parsing: This accurately extracts and organizes transaction outputs from raw Bitcoin transactions. It is able to receive a raw tx and return an array of structures with the tx outputs.
16
+ 2. Bitcoin transaction hashing: This calculates the cryptographic hash of a Bitcoin transaction, ensuring its authenticity and integrity. It receives a raw tx and returns its hash.
17
+ 3. Bitcoin transaction output script validation: This verifies the validity and type of output scripts within a Bitcoin transaction, allowing for specific data extraction. It receives a raw output script, validates that it is from a specific type and returns a result. E.g. receive a raw null-data script and return the embedded data in it.
18
+ 4. Bitcoin address generation: is able to generate Bitcoin the address from a specific script and also to validate if a given address was generated from a script or not.
19
+ 5. Bitcoin address validation: This checks if a Bitcoin address conforms to a particular type or format. It validates if a Bitcoin address is of a given type or not.
20
+
21
+ ## Versioning
22
+ Current version is ![npm version](https://img.shields.io/npm/v/@rsksmart/btc-transaction-solidity-helper.svg)
23
+
24
+ To check the NPM package, please check [Bitcoin Solidity Helper NPM Package.](https://www.npmjs.com/.package/@rsksmart/btc-transaction-solidity-helper)
25
+
26
+ ## Prerequisites
27
+ * Knowledge of Solidity and how to write smart contracts.
28
+ * [Bitcoin Solidity Helper Package.](https://github.com/rsksmart/btc-transaction-solidity-helper/pkgs/npm/btc-transaction-solidity-helper)
29
+
30
+ ## Setup
31
+ To setup the Solidity helper library in your project, run the following npm command:
32
+
33
+ ```bash
34
+ npm install @rsksmart/btc-transaction-solidity-helper
35
+ ```
36
+
37
+ ## Usage
38
+
39
+ ### Import the library:
40
+
41
+ ```bash
42
+ import "@rsksmart/btc-transaction-solidity-helper/contracts/BtcUtils.sol";
43
+ ```
44
+
45
+ ### Using the library:
46
+
47
+ ```bash
48
+ BtcUtils.TxRawOutput[] memory outputs = BtcUtils.getOutputs(btcTx);
49
+ bytes memory scriptData = BtcUtils.parseNullDataScript(outputs[0].pkScript);
50
+ ```
51
+
52
+ _This fragment parses a raw Bitcoin transaction to extract its outputs and then parses the first output to get the data of the null data script._
53
+
54
+ ## Parsing a Bitcoin Transaction Output
55
+ All the bitcoin transactions have a specific format when they are serialized. By having knowledge of this format, we can process a raw transaction in order to extract the information about its outputs.
56
+
57
+ A raw transaction has the following top-level format:
58
+
59
+ | Bytes | Name | Data Type | Description |
60
+ | --- | --- | --- | --- |
61
+ | 4 | Version | `int32_t` | [Transaction version number](https://developer.bitcoin.org/terms.html#term-transaction-version-number) (note, this is signed); currently version 1 or 2. Programs creating transactions using newer consensus rules may use higher version numbers. Version 2 means that [BIP68](https://github.com/bitcoin/bips/blob/master/bip-0068.mediawiki#specification) applies. |
62
+ | Varies | tx_in count | compactSize uint | Number of inputs in this transaction. |
63
+ | Varies | tx_in | txIn | Transaction inputs. See description of txIn below. |
64
+ | Varies | tx_out count | compactSize uint | Number of outputs in this transaction. |
65
+ | Varies | tx_out | txOut | Transaction outputs. See description of txOut below. |
66
+ | 4 | lock_time | `uint32_t` | A time ([Unix epoch time](https://en.wikipedia.org/wiki/Unix_time)) or block number. See the [locktime parsing rules](https://developer.bitcoin.org/devguide/transactions.html#locktime_parsing_rules). |
67
+
68
+
69
+ > See the [Reference Implementation](https://developer.bitcoin.org/reference/transactions.html#raw-transaction-format)
70
+
71
+ The approach that the library takes is to calculate, based on the length of each section, where does the output part start. After this, it starts parsing each output separately and adding its script and value into a solidity data structure.
72
+
73
+ | Bytes | Name | Data Type | Description |
74
+ | --- | --- | --- | --- |
75
+ | 8 | Value | `int64_t` | Number of satoshis to spend. May be zero; the sum of all outputs may not exceed the sum of satoshis previously spent to the outpoints provided in the input section. (Exception: coinbase transactions spend the block subsidy and collect transaction fees.) |
76
+ | 1+ | pk_script bytes | compactSize uint | Number of bytes in the pubkey script. Maximum is 10,000 bytes. |
77
+ | 1+ | pk_script | `char[]` | Defines the conditions which must be satisfied to spend this output. |
78
+
79
+ > See the [Reference Implementation](https://developer.bitcoin.org/reference/transactions.html#txout-a-transaction-output)
80
+
81
+ ```solidity
82
+ struct TxRawOutput {
83
+ uint64 value;
84
+ bytes pkScript;
85
+ uint256 scriptSize;
86
+ uint256 totalSize;
87
+ }
88
+ ```
89
+
90
+ After finishing the processing of each output, the library returns an ordered output array, so the user can take advantage of this information in its solidity contract.
91
+
92
+ In order to show the benefits of this library, we’ll use the example of the [Flyover Protocol](/developers/integrate/flyover/). In this protocol, there is a smart contract that one party uses to claim a refund, in order to claim this refund, they need to prove that there was a payment with a specific amount done to a specific address in the Bitcoin Network, in order to do this, the smart contract receives the Bitcoin raw transaction. Since making this validation is not a trivial process, as it requires to parse the whole transaction, here is where we can see the utility of the library.
93
+
94
+ The usage of the output parsing functionality is the following:
95
+ ```solidity
96
+ BtcUtils.TxRawOutput[] memory outputs = BtcUtils.getOutputs(btcTx);
97
+ ```
98
+
99
+ Then the user is able to perform any validation:
100
+ ```solidity
101
+ require(expectedValue <= outputs[0].value, "incorrect amount");
102
+ ```
103
+
104
+ :::info[Info]
105
+ The value field of the output structure is in satoshis.
106
+ :::
107
+
108
+
109
+ ## Hashing Transactions
110
+ The hash algorithm used in the Bitcoin Network is just the `SHA256(SHA256())` of the serialized transaction. The library exposes one function that will apply this hash algorithm to any byte array passed to it, making it easy to calculate the transaction id of any raw transaction present in the contract.
111
+
112
+ This function is specifically useful to interact with the [rootstock native bridge](/concepts/powpeg/), as many of its functions have a transaction id as parameter. For example, by using the transaction hash function, it is easy to know how many confirmations a Bitcoin block has inside a smart contract function.
113
+
114
+ ### Example code with explanation
115
+ Based on the example stated in the previous section, after validating that a specific transaction has an output paying a certain amount to an address. We need to know if that transaction has enough confirmations:
116
+
117
+ Here's an example:
118
+
119
+ ```solidity
120
+ BtcUtils.TxRawOutput[] memory outputs = BtcUtils.getOutputs(btcTx);
121
+ require(expectedValue <= outputs[0].value, "incorrect amount");
122
+ bytes32 txId = BtcUtils.hashBtcTx(btcTx)
123
+ // assuming btcBlockHeaderHash,partialMerkleTree, merkleBranchHashes
124
+ // were provided in the function parameters
125
+ uint confirmations = bridge.getBtcTransactionConfirmations(
126
+ txId,
127
+ btcBlockHeaderHash,
128
+ partialMerkleTree,
129
+ merkleBranchHashes
130
+ )
131
+ require(confirmations > expectedConfirmations, "not enough confirmations");
132
+ ```
133
+
134
+ Read more about the [bridge functionality.](https://github.com/rsksmart/rskj/blob/master/rskj-core/src/main/java/co/rsk/peg/BridgeSupport.java)
135
+
136
+ ## Script Validation for Bitcoin Transaction Output
137
+ In the Bitcoin network, when a user wants to send funds to another, the user creates a transaction and adds an output with the value that it wants to send. The other user doesn’t “receive” this amount directly, instead, we call receiving to the ability of providing the proper input to the output script so it returns `true`:
138
+
139
+ <Quote caption="Bitcoin Script Documentation">
140
+
141
+ A transaction is valid if nothing in the combined script triggers failure and the top stack item is True (non-zero) when the script exits. Read more info in [Bitcoin Script](https://en.bitcoin.it/wiki/Script)
142
+ </Quote>
143
+
144
+ > By having knowledge of the structure of the outputs that each type of address has, we can process and validate any arbitrary output extracted with the functions explained in the previous sections. In the same way, we can parse those outputs to obtain the specific value that later is encoded (in base58check, bech32 or bech32m) and presented as the “destination address”.
145
+
146
+ The output that the library supports and is able to parse to an address are:
147
+ * P2PKH (Pay to public key hash)
148
+ * P2SH (Pay to script hash)
149
+ * P2WPKH (Pay to witness public key hash)
150
+ * P2WSH (Pay to witness script hash)
151
+ * P2TR (Pay to taproot)
152
+
153
+ **Some use cases for script validation:**
154
+
155
+ As seen in the previous example, we validated inside our smart contract that a Bitcoin transaction has the correct amount and enough confirmations, now we need to validate that it was performed on the correct address. To do this, the library has the capability of parsing an arbitrary output and converting it into an address.
156
+
157
+ Here's an example:
158
+
159
+ ```solidity
160
+ bytes memory btcTxDestination = BtcUtils.outputScriptToAddress(
161
+ outputs[0].pkScript,
162
+ mainnetFlag
163
+ );
164
+ require(keccak256(expectedAddress) == keccak256(btcTxDestination), "incorrect address");
165
+ ```
166
+
167
+ ## Conclusion
168
+ Congratulations, we have successfully learnt how to use the Solidity Helper library to parse, hash, and validate scripts within Bitcoin transactions. By using this library, developers can gain valuable insights into Bitcoin transaction data and build more sophisticated smart contract dApps on Rootstock.
169
+
170
+ ### Future features
171
+ **Some future enhancements to the library includes:**
172
+ * Transaction Input Parsing: The ability to extract and analyze transaction input data to receive a raw tx and return an array of structs with the tx inputs.
173
+ * Transaction Creation: Utilities to facilitate the creation of raw Bitcoin transactions within smart contracts.
174
+
175
+ ## Contribution Guidelines
176
+ * Please refer to the Rootstock Contribution Guidelines for more information on how to contribute to this project.
177
+
178
+ ## License:
179
+ MIT License - Copyright (c) 2023 Rootstock.
@@ -59,7 +59,7 @@ library BtcUtils {
59
59
  if (rawTx[4] == 0x00 && rawTx[5] == 0x01) { // if its segwit, skip marker and flag
60
60
  currentPosition = 6;
61
61
  }
62
-
62
+
63
63
  (uint64 inputCount, uint16 inputCountSize) = parseCompactSizeInt(currentPosition, rawTx);
64
64
  currentPosition += inputCountSize;
65
65
 
@@ -167,7 +167,7 @@ library BtcUtils {
167
167
  }
168
168
 
169
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
170
+ /// @notice Reference for implementation: https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki
171
171
  /// @param pkScript the fragment of the raw transaction containing the raw output script
172
172
  /// @return Whether the script has a pay-to-taproot output structure or not
173
173
  function isP2TROutput(bytes memory pkScript) public pure returns (bool) {
@@ -180,7 +180,7 @@ library BtcUtils {
180
180
  /// the resulting byte array doesn't include the checksum bytes of the base58check encoding at
181
181
  /// the end
182
182
  /// @param outputScript the fragment of the raw transaction containing the raw output script
183
- /// @param mainnet if the address to generate is from mainnet or testnet
183
+ /// @param mainnet if the address to generate is from mainnet or testnet
184
184
  /// @return The address generated using the receiver's public key hash
185
185
  function parsePayToPubKeyHash(bytes calldata outputScript, bool mainnet) public pure returns (bytes memory) {
186
186
  require(isP2PKHOutput(outputScript), "Script hasn't the required structure");
@@ -217,13 +217,12 @@ library BtcUtils {
217
217
  /// @param outputScript the fragment of the raw transaction containing the raw output script
218
218
  /// @return The address bech32 words generated using the pubkey hash
219
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);
220
+ require(isP2WPKHOutput(outputScript), "Script hasn't the required structure");
221
+ uint length = 1 + HASH160_SIZE;
222
222
  bytes memory result = new bytes(length);
223
223
  result[0] = WITNESS_VERSION_0;
224
- bytes memory words = to5BitWords(outputScript[2:]);
225
224
  for (uint i = 1; i < length; i++) {
226
- result[i] = words[i - 1];
225
+ result[i] = outputScript[i + 1];
227
226
  }
228
227
  return result;
229
228
  }
@@ -234,12 +233,11 @@ library BtcUtils {
234
233
  /// @return The address bech32 words generated using the script hash
235
234
  function parsePayToWitnessScriptHash(bytes calldata outputScript) public pure returns (bytes memory) {
236
235
  require(isP2WSHOutput(outputScript), "Script hasn't the required structure");
237
- uint length = 1 + total5BitWords(SHA256_SIZE);
236
+ uint length = 1 + SHA256_SIZE;
238
237
  bytes memory result = new bytes(length);
239
238
  result[0] = WITNESS_VERSION_0;
240
- bytes memory words = to5BitWords(outputScript[2:]);
241
239
  for (uint i = 1; i < length; i++) {
242
- result[i] = words[i - 1];
240
+ result[i] = outputScript[i + 1];
243
241
  }
244
242
  return result;
245
243
  }
@@ -250,18 +248,17 @@ library BtcUtils {
250
248
  /// @return The address bech32m words generated using the taproot pubkey hash
251
249
  function parsePayToTaproot(bytes calldata outputScript) public pure returns (bytes memory) {
252
250
  require(isP2TROutput(outputScript), "Script hasn't the required structure");
253
- uint length = 1 + total5BitWords(TAPROOT_PUBKEY_SIZE);
251
+ uint length = 1 + TAPROOT_PUBKEY_SIZE;
254
252
  bytes memory result = new bytes(length);
255
253
  result[0] = WITNESS_VERSION_1;
256
- bytes memory words = to5BitWords(outputScript[2:]);
257
254
  for (uint i = 1; i < length; i++) {
258
- result[i] = words[i - 1];
255
+ result[i] = outputScript[i + 1];
259
256
  }
260
257
  return result;
261
258
  }
262
259
 
263
260
  /// @notice Parse a raw null-data output script to get its content
264
- /// @param outputScript the fragment of the raw transaction containing the raw output script
261
+ /// @param outputScript the fragment of the raw transaction containing the raw output script
265
262
  /// @return The content embedded inside the script
266
263
  function parseNullDataScript(bytes calldata outputScript) public pure returns (bytes memory) {
267
264
  require(outputScript.length > 1,"Invalid size");
@@ -271,7 +268,7 @@ library BtcUtils {
271
268
 
272
269
  /// @notice Hash a bitcoin raw transaction to get its id (reversed double sha256)
273
270
  /// @param btcTx the transaction to hash
274
- /// @return The transaction id
271
+ /// @return The transaction id
275
272
  function hashBtcTx(bytes calldata btcTx) public pure returns (bytes32) {
276
273
  bytes memory doubleSha256 = abi.encodePacked(sha256(abi.encodePacked(sha256(btcTx))));
277
274
  bytes1 aux;
@@ -340,7 +337,7 @@ library BtcUtils {
340
337
  } else if (maxSize <= MAX_COMPACT_SIZE_LENGTH) {
341
338
  return (maxSize, 1);
342
339
  }
343
-
340
+
344
341
  uint compactSizeBytes = 2 ** (maxSize - MAX_COMPACT_SIZE_LENGTH);
345
342
  require(compactSizeBytes <= MAX_BYTES_USED_FOR_COMPACT_SIZE, "unsupported compact size length");
346
343
 
@@ -357,7 +354,7 @@ library BtcUtils {
357
354
  bytes memory array
358
355
  ) private pure returns (uint) {
359
356
  require(
360
- fragmentStart < array.length && fragmentEnd < array.length,
357
+ fragmentStart < array.length && fragmentEnd < array.length,
361
358
  "Range can't be bigger than array"
362
359
  );
363
360
  uint result = 0;
@@ -366,36 +363,4 @@ library BtcUtils {
366
363
  }
367
364
  return result;
368
365
  }
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
- }
401
- }
366
+ }
@@ -3,6 +3,7 @@ pragma solidity ^0.8.18;
3
3
 
4
4
 
5
5
  library OpCodes {
6
+ bytes1 public constant OP_DROP = 0x75;
6
7
  bytes1 public constant OP_DUP = 0x76;
7
8
  bytes1 public constant OP_HASH160 = 0xa9;
8
9
  bytes1 public constant OP_EQUALVERIFY = 0x88;
@@ -10,6 +11,8 @@ library OpCodes {
10
11
  bytes1 public constant OP_RETURN = 0x6a;
11
12
  bytes1 public constant OP_EQUAL = 0x87;
12
13
 
14
+ bytes1 public constant OP_PUSHBYTES_32 = 0x20;
15
+
13
16
  bytes1 public constant OP_0 = 0x00;
14
17
  bytes1 public constant OP_1 = 0x51;
15
18
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rsksmart/btc-transaction-solidity-helper",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
4
4
  "description": "Solidity library with functions to work with Bitcoin transactions inside smart contracts",
5
5
  "main": "contracts",
6
6
  "files": [
package/Readme.md DELETED
@@ -1,33 +0,0 @@
1
- # Bitcoin Transaction Solidity Helper
2
- [![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/rsksmart/btc-transaction-solidity-helper/badge)](https://scorecard.dev/viewer/?uri=github.com/rsksmart/btc-transaction-solidity-helper)
3
-
4
- The intention of this library is to make easier to work with Bitcoin transactions in Solidity smart contracts. Since Rootstock extends Bitcoin's capabilities by enabling smart contracts it is important to be able to work with Bitcoin transactions in them.
5
-
6
- ## Features
7
-
8
- The features of this library include:
9
- * Bitcoin transaction output parsing: is able to receive a raw tx and return an array of structures with the tx outputs
10
- * Bitcoin transaction hashing: is able to receive a raw tx and return its hash
11
- * Bitcoin transaction output script validation: is able to receive a raw output script, validate that is from a specific type and return a result. E.g. receive a raw null-data script and return the embedded data in it
12
- * Bitcoin address generation: is able to generate Bitcoin the address from a specific script and also to validate if a given address was generated from a script or not.
13
- * Bitcoin address validation: is able to validate if a Bitcoin address is of a given type or not.
14
-
15
- ### Future features
16
- These are some features that can increase the library capabilities in the future:
17
- * Bitcoin transaction input parsing: should be able to receive a raw tx and return an array of structs with the tx inputs
18
- * Bitcoin transaction creation: utilities for building a raw transaction inside a contract
19
-
20
- ## Usage
21
- 1. Run this command to install the contracts
22
- ```console
23
- npm install @rsksmart/btc-transaction-solidity-helper
24
- ```
25
- 2. Import the library in your contract
26
- ```solidity
27
- import "@rsksmart/btc-transaction-solidity-helper/contracts/BtcUtils.sol";
28
- ```
29
- 3. Use the library. E.g.:
30
- ```solidity
31
- BtcUtils.TxRawOutput[] memory outputs = BtcUtils.getOutputs(btcTx);
32
- bytes memory btcTxDestination = BtcUtils.parseNullDataScript(outputs[0].pkScript, false);
33
- ```