├── .eslintrc.json
├── .gitignore
├── .travis.yml
├── LICENSE
├── README.md
├── data
├── networks.js
└── test-cases
│ ├── .gitignore
│ ├── LICENSE
│ ├── README.md
│ └── json
│ ├── create_and_sign_transaction_response.json
│ ├── create_and_sign_transaction_response_P2WSH-over-P2SH_1of2_251inputs.json
│ ├── create_and_sign_transaction_response_P2WSH-over-P2SH_1of2_252inputs.json
│ ├── create_and_sign_transaction_response_P2WSH-over-P2SH_1of2_253inputs.json
│ ├── create_and_sign_transaction_response_P2WSH-over-P2SH_1of2_762inputs.json
│ ├── create_and_sign_transaction_response_dtrust_P2SH_3of5_195inputs.json
│ ├── create_and_sign_transaction_response_dtrust_P2SH_4of5_195inputs.json
│ ├── create_and_sign_transaction_response_dtrust_P2WSH-over-P2SH_3of5_251inputs.json
│ ├── create_and_sign_transaction_response_dtrust_P2WSH-over-P2SH_3of5_252inputs.json
│ ├── create_and_sign_transaction_response_dtrust_P2WSH-over-P2SH_3of5_253inputs.json
│ ├── create_and_sign_transaction_response_dtrust_P2WSH-over-P2SH_4of5_251inputs.json
│ ├── create_and_sign_transaction_response_dtrust_P2WSH-over-P2SH_4of5_252inputs.json
│ ├── create_and_sign_transaction_response_dtrust_P2WSH-over-P2SH_4of5_253inputs.json
│ ├── create_and_sign_transaction_response_dtrust_WITNESS_V0_3of5_251inputs.json
│ ├── create_and_sign_transaction_response_dtrust_WITNESS_V0_3of5_252inputs.json
│ ├── create_and_sign_transaction_response_dtrust_WITNESS_V0_3of5_253inputs.json
│ ├── create_and_sign_transaction_response_dtrust_WITNESS_V0_4of5_251inputs.json
│ ├── create_and_sign_transaction_response_dtrust_WITNESS_V0_4of5_252inputs.json
│ ├── create_and_sign_transaction_response_dtrust_WITNESS_V0_4of5_253inputs.json
│ ├── create_and_sign_transaction_response_dtrust_p2sh_3_of_5_keys.json
│ ├── create_and_sign_transaction_response_dtrust_p2sh_4_of_5_keys.json
│ ├── create_and_sign_transaction_response_dtrust_p2wsh_over_p2sh_3_of_5_keys.json
│ ├── create_and_sign_transaction_response_dtrust_p2wsh_over_p2sh_4_of_5_keys.json
│ ├── create_and_sign_transaction_response_dtrust_witness_v0_3_of_5_keys.json
│ ├── create_and_sign_transaction_response_dtrust_witness_v0_3of5_251outputs.json
│ ├── create_and_sign_transaction_response_dtrust_witness_v0_3of5_252outputs.json
│ ├── create_and_sign_transaction_response_dtrust_witness_v0_3of5_253outputs.json
│ ├── create_and_sign_transaction_response_dtrust_witness_v0_4_of_5_keys.json
│ ├── create_and_sign_transaction_response_dtrust_witness_v0_4of5_251outputs.json
│ ├── create_and_sign_transaction_response_dtrust_witness_v0_4of5_252outputs.json
│ ├── create_and_sign_transaction_response_dtrust_witness_v0_4of5_253outputs.json
│ ├── create_and_sign_transaction_response_sweep_p2pkh.json
│ ├── create_and_sign_transaction_response_sweep_p2wpkh.json
│ ├── create_and_sign_transaction_response_sweep_p2wpkh_over_p2sh.json
│ ├── create_and_sign_transaction_response_with_blockio_fee_and_expected_unsigned_txid.json
│ ├── create_and_sign_transaction_response_witness_v1_output.json
│ ├── get_balance_response.json
│ ├── prepare_dtrust_transaction_response_P2SH_3of5_195inputs.json
│ ├── prepare_dtrust_transaction_response_P2SH_4of5_195inputs.json
│ ├── prepare_dtrust_transaction_response_P2WSH-over-P2SH_3of5_251inputs.json
│ ├── prepare_dtrust_transaction_response_P2WSH-over-P2SH_3of5_252inputs.json
│ ├── prepare_dtrust_transaction_response_P2WSH-over-P2SH_3of5_253inputs.json
│ ├── prepare_dtrust_transaction_response_P2WSH-over-P2SH_4of5_251inputs.json
│ ├── prepare_dtrust_transaction_response_P2WSH-over-P2SH_4of5_252inputs.json
│ ├── prepare_dtrust_transaction_response_P2WSH-over-P2SH_4of5_253inputs.json
│ ├── prepare_dtrust_transaction_response_WITNESS_V0_3of5_251inputs.json
│ ├── prepare_dtrust_transaction_response_WITNESS_V0_3of5_252inputs.json
│ ├── prepare_dtrust_transaction_response_WITNESS_V0_3of5_253inputs.json
│ ├── prepare_dtrust_transaction_response_WITNESS_V0_4of5_251inputs.json
│ ├── prepare_dtrust_transaction_response_WITNESS_V0_4of5_252inputs.json
│ ├── prepare_dtrust_transaction_response_WITNESS_V0_4of5_253inputs.json
│ ├── prepare_dtrust_transaction_response_p2sh.json
│ ├── prepare_dtrust_transaction_response_p2wsh_over_p2sh.json
│ ├── prepare_dtrust_transaction_response_witness_v0.json
│ ├── prepare_dtrust_transaction_response_witness_v0_3of5_251outputs.json
│ ├── prepare_dtrust_transaction_response_witness_v0_3of5_252outputs.json
│ ├── prepare_dtrust_transaction_response_witness_v0_3of5_253outputs.json
│ ├── prepare_dtrust_transaction_response_witness_v0_4of5_251outputs.json
│ ├── prepare_dtrust_transaction_response_witness_v0_4of5_252outputs.json
│ ├── prepare_dtrust_transaction_response_witness_v0_4of5_253outputs.json
│ ├── prepare_sweep_transaction_response_p2pkh.json
│ ├── prepare_sweep_transaction_response_p2wpkh.json
│ ├── prepare_sweep_transaction_response_p2wpkh_over_p2sh.json
│ ├── prepare_transaction_response.json
│ ├── prepare_transaction_response_P2WSH-over-P2SH_1of2_251inputs.json
│ ├── prepare_transaction_response_P2WSH-over-P2SH_1of2_252inputs.json
│ ├── prepare_transaction_response_P2WSH-over-P2SH_1of2_253inputs.json
│ ├── prepare_transaction_response_P2WSH-over-P2SH_1of2_762inputs.json
│ ├── prepare_transaction_response_with_blockio_fee_and_expected_unsigned_txid.json
│ ├── prepare_transaction_response_witness_v1_output.json
│ └── summarize_prepared_transaction_response_with_blockio_fee_and_expected_unsigned_txid.json
├── examples
├── basic.js
├── dtrust.js
└── sweep.js
├── lib
├── block_io.js
├── helper.js
├── httpsclient.js
├── key.js
└── networks.js
├── package.json
└── test
├── helpers
├── cache.js
├── clienttest.js
└── generic.js
├── integration
├── api.js
└── dtrust.js
└── unit
├── bitcoinjs-lib.js
├── cryptohelper.js
├── httpsclient.js
├── key.js
└── prepare_transaction.js
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "browser": false,
4 | "commonjs": true,
5 | "es2020": true,
6 | "node": true
7 | },
8 | "extends": "eslint:recommended",
9 | "parserOptions": {
10 | "ecmaVersion": 11
11 | },
12 | "rules": {
13 | "no-var": 1
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | blockio.min.js
3 | package-lock.json
4 | tmp
5 | *~
6 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | dist: focal
2 | language: node_js
3 | script:
4 | - npm --version
5 | - npm test
6 | node_js:
7 | - 14
8 | - 16
9 | - 17
10 | - 18
11 | - 19
12 | - 20
13 | - 21
14 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014-2021 Block.io, Inc.
4 |
5 | Permission is hereby granted, free of charge, to any person
6 | obtaining a copy of this software and associated documentation
7 | files (the "Software"), to deal in the Software without
8 | restriction, including without limitation the rights to use,
9 | copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the
11 | Software is furnished to do so, subject to the following
12 | conditions:
13 |
14 | The above copyright notice and this permission notice shall be
15 | included in all copies or substantial portions of the Software.
16 |
17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
19 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
21 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
22 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
24 | OTHER DEALINGS IN THE SOFTWARE.
25 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # BlockIo
2 |
3 | This NodeJS module is the official reference SDK for the Block.io payments
4 | API. To use this, you will need the Bitcoin, Litecoin or Dogecoin API key(s)
5 | from block.io. Go ahead, sign
6 | up :)
7 |
8 | ## Installation
9 |
10 | Install the package using npm:
11 |
12 | ```bash
13 | npm install block_io
14 | ```
15 |
16 | ### Supported NodeJS versions
17 |
18 | We aim to support only the current (non-EOL) NodeJS LTS versions.
19 |
20 | ## Usage
21 |
22 | It's super easy to get started:
23 |
24 | ```javascript
25 |
26 | // load this library
27 | const BlockIo = require('block_io');
28 |
29 | // instantiate a client
30 | const block_io = new BlockIo('API_KEY');
31 |
32 | async function example() {
33 | try {
34 | // print the account balance
35 | let balance = await block_io.get_balance();
36 | console.log(JSON.stringify(balance,null,2));
37 |
38 | // print first page of unarchived addresses on this account
39 | let addresses = await block_io.get_my_addresses();
40 | console.log(JSON.stringify(addresses,null,2));
41 |
42 | // withdrawal:
43 | // prepare_transaction ->
44 | // summarize_prepared_transaction ->
45 | // create_and_sign_transaction ->
46 | // submit_transaction
47 | let prepared_transaction = await block_io.prepare_transaction({
48 | from_labels: 'label1,label2',
49 | to_label: 'label3',
50 | amount: '50.0'
51 | });
52 |
53 | // inspect the prepared data for yourself. here's a
54 | // summary of the transaction you will create and sign
55 | let summarized_transaction = await block_io.summarize_prepared_transaction({data: prepared_transaction});
56 | console.log(JSON.stringify(summarized_transaction,null,2));
57 |
58 | // create and sign this transaction:
59 | // we specify the PIN here to decrypt
60 | // the private key to sign the transaction
61 | let signed_transaction = await block_io.create_and_sign_transaction({data: prepared_transaction, pin: 'SECRET_PIN'});
62 |
63 | // inspect the signed transaction yourself
64 | // once satisfied, submit it to Block.io
65 | let result = await block_io.submit_transaction({transaction_data: signed_transaction});
66 | console.log(JSON.stringify(result,null,2)); // contains the transaction ID of the final transaction
67 |
68 | } catch (error) {
69 | console.log("Error:", error.message);
70 | }
71 | }
72 |
73 | example();
74 |
75 | ```
76 |
77 | ### Promises
78 |
79 | Since v3.0.0, all methods return promises, like so:
80 |
81 | ```javascript
82 |
83 | block_io.get_balance()
84 | .then(data => console.log(JSON.stringify(data,null,2)))
85 | .catch(error => console.log("Error:", error.message));
86 |
87 | ```
88 |
89 | ### Callbacks
90 |
91 | For backward compatibility, callback-style method calls are supported too.
92 | Just add a callback function/lambda as the last argument.
93 |
94 | ```javascript
95 |
96 | block_io.get_balance((error, data) => {
97 | if (error) return console.log("Error:", error.message);
98 | console.log(JSON.stringify(data,null,2));
99 | });
100 |
101 | ```
102 |
103 | For more information, see [NodeJS API Docs](https://block.io/api/nodejs).
104 | This client provides a mapping for all methods listed on the Block.io API
105 | site.
106 |
107 | ### Configuration
108 |
109 | To change behavior of the `block_io` client, attributes can be passed to the
110 | class at instantiation time, in the form of an object.
111 |
112 | The following attributes are supported:
113 |
114 | ```javascript
115 | const config = {
116 | api_key: "YOUR_API_KEY",
117 | version: 2, // REST API version to use. Default: 2
118 | options: {
119 | allowNoPin: false, // Allow ommission of PIN for withdrawal.
120 | // This may be useful when interfacing with
121 | // hardware wallets and HSMs. Default: false.
122 |
123 | lowR: true, // Sign with a low R value to save a byte and
124 | // make signature size more predictable, at the
125 | // cost of more CPU time needed to sign transactions.
126 | // Default: true
127 |
128 | }
129 | }
130 |
131 | const block_io = new BlockIo(config);
132 | ```
133 |
134 | ## Contributing
135 |
136 | 1. Fork it ( https://github.com/BlockIo/block_io-nodejs/fork )
137 | 2. Create your feature branch (`git checkout -b my-new-feature`)
138 | 3. Commit your changes (`git commit -am 'Add some feature'`)
139 | 4. Push to the branch (`git push origin my-new-feature`)
140 | 5. Create a new Pull Request
141 |
142 | ## Testing
143 |
144 | We use [tape](https://www.npmjs.com/package/tape) for unit and integration
145 | tests. To run the unit tests simply run `npm test`.
146 |
147 | To run the integration tests you need to specify ```BLOCK_IO_API_KEY``` and
148 | ```BLOCK_IO_PIN``` environment variables.
149 |
150 | **DO NOT USE PRODUCTION CREDENTIALS FOR INTEGRATION TESTING!**
151 |
152 | Integration test syntax:
153 | ```bash
154 | BLOCK_IO_API_KEY="API_KEY" BLOCK_IO_PIN="SECRET_PIN" node test/integration/api.js
155 | ```
156 |
--------------------------------------------------------------------------------
/data/networks.js:
--------------------------------------------------------------------------------
1 | const Bitcoin = require('bitcoinjs-lib');
2 |
3 | Bitcoin.networks.litecoin = {
4 | messagePrefix: '\x18Dogecoin Signed Message:\n',
5 | bech32: 'ltc',
6 | bip32: {
7 | public: 0x019da462,
8 | private: 0x019d9cfe
9 | },
10 | pubKeyHash: 0x30,
11 | scriptHash: 0x32,
12 | wif: 0xb0,
13 | };
14 |
15 | Bitcoin.networks.dogecoin = {
16 | messagePrefix: '\x19Dogecoin Signed Message:\n',
17 | bech32: 'doge',
18 | bip32: {
19 | public: 0x02facafd,
20 | private: 0x02fac398
21 | },
22 | pubKeyHash: 0x1e,
23 | scriptHash: 0x16,
24 | wif: 0x9e,
25 | }
26 |
27 | // extend known network params with dogecoin and litecoin testnets
28 | Bitcoin.networks.dogecoin_testnet = {
29 | messagePrefix: '\x18Dogecoin Signed Message:\n',
30 | bech32: 'tdge',
31 | bip32: {
32 | public: 0x0432a9a8,
33 | private: 0x0432a243
34 | },
35 | pubKeyHash: 0x71,
36 | scriptHash: 0xc4,
37 | wif: 0xf1,
38 | };
39 |
40 | Bitcoin.networks.litecoin_testnet = {
41 | messagePrefix: '\x18Litecoin Signed Message:\n',
42 | bech32: 'tltc',
43 | bip32: {
44 | public: 0x0436ef7d,
45 | private: 0x0436f6e1
46 | },
47 | pubKeyHash: 0x6f,
48 | scriptHash: 0x3a,
49 | wif: 0xef,
50 | };
51 |
--------------------------------------------------------------------------------
/data/test-cases/.gitignore:
--------------------------------------------------------------------------------
1 | *~
2 | .DS_Store
3 |
--------------------------------------------------------------------------------
/data/test-cases/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Block.io, Inc.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/data/test-cases/README.md:
--------------------------------------------------------------------------------
1 | # test-cases
2 | Test cases for libraries
3 |
--------------------------------------------------------------------------------
/data/test-cases/json/create_and_sign_transaction_response.json:
--------------------------------------------------------------------------------
1 | {
2 | "tx_type": "basic",
3 | "tx_hex": "010000000b735465c299f05c5c7103e2b88e86ac7d91041055c2110d8b330bcb8da636b2170000000000ffffffff34965dc0e3e4aef1c244d426837c83291d9da1bfac830d41cc5a4e75504a570a0000000000ffffffff8f11671a7dbd5ad8f12c265266c6bbe259b5b960dfc8a394bab77b33d17409020000000000fffffffff3959c0d0281f0c265ea27e28c4f7369b0c4599338c3edde8f999d85b9a685cd0100000000ffffffff465503fc60f66fa094fcd506d27e099cd462af2209350d26e41cd2ee4b6fbe4c0000000000ffffffffe85f49ce97b5561c1fe96253c72daf37220855e710db0da9792c47c4035e7a020100000000ffffffff144cb1d56e06f514fba86afdfe39b79346a21490753a4af4125caedf79cdffe10000000000ffffffff343a521829667d856f38dbb8dd7f54f630305781ce1e8dd664863158c2f87ee30000000000ffffffff343a521829667d856f38dbb8dd7f54f630305781ce1e8dd664863158c2f87ee30100000000ffffffffaf3901d009e01e2e5946b6cab911b1b0e3e852cf0344488a50d5d0b0ec8a0df30000000000fffffffff84a933e79a29d870bec3059849cf44eb8eb9b1a000beb1ef486b2edd38de12a0000000000ffffffff044e61bc00000000002200203397d06887d687628c5581c79fad2e6591279a0635630dd876ad336c86bc86d487d612000000000017a91449e0ff8beaca290b0461e3160b0df0921d4468988740e201000000000017a914dac6654620e9a5126ba9fc129486d7177bf9f12187cf6f4c000000000022002028572b37c3e4907068225a8dc6157960a499efebcc0a44760634a399919bbc2c00000000",
4 | "signatures": [
5 | {
6 | "input_index": 0,
7 | "public_key": "03f2e51b2f298bab1e4485d7d31a559de73fc29dced12f568e30264dbae4ec326e",
8 | "signature": "3044022017fb6ea34dfbe60437cb84ec67ec73454b9d9f58b75811a147b59bd5ca954bfc02203ee7b640466404c0676e8602ffbef48248a95aa1652ae6b228ec9b368b35138f"
9 | },
10 | {
11 | "input_index": 1,
12 | "public_key": "03f2e51b2f298bab1e4485d7d31a559de73fc29dced12f568e30264dbae4ec326e",
13 | "signature": "3044022026e3ceba44ce578a7098c3f365603fc21d2d282d026c369927721de1590dc980022059d24fa4abe1ccc9b6986bd2cddbec2a3f5f97ad8c5f815146803113912b7312"
14 | },
15 | {
16 | "input_index": 2,
17 | "public_key": "03f2e51b2f298bab1e4485d7d31a559de73fc29dced12f568e30264dbae4ec326e",
18 | "signature": "30440220517de4d3e1f51c9e5f7fb1376b22b404d8bc03c8df5ac734fb3353bade44fbab0220381d612eaafe6c2b0f3c44e265d3f47ded8ca706780a0f5448dcefc70d529f68"
19 | },
20 | {
21 | "input_index": 3,
22 | "public_key": "03f2e51b2f298bab1e4485d7d31a559de73fc29dced12f568e30264dbae4ec326e",
23 | "signature": "30440220757c86210a336cc07367b2d12da8487244d1e84208547a61bd86f77e840aca3b02204fabd15eb44c582c66b3dd784512bf5c3ab80618f6761265168ec91d73094462"
24 | },
25 | {
26 | "input_index": 4,
27 | "public_key": "03f2e51b2f298bab1e4485d7d31a559de73fc29dced12f568e30264dbae4ec326e",
28 | "signature": "304402201f14e391bfe16c22a32c061b9193be1465476f87509c93e1ea118fb188ac287702206ffa69ad153249597ab306dd35ba3c89a61358df3228f81cc406821915ee7a4f"
29 | },
30 | {
31 | "input_index": 5,
32 | "public_key": "03f2e51b2f298bab1e4485d7d31a559de73fc29dced12f568e30264dbae4ec326e",
33 | "signature": "3044022030d55965464233ba251f0dbf9322717594d1718b045500abde2992cb8555b17502202f79d2952fd871102048a453f5cd52e1b09eac36012f6facddfe04a963d5c2d6"
34 | },
35 | {
36 | "input_index": 6,
37 | "public_key": "03f2e51b2f298bab1e4485d7d31a559de73fc29dced12f568e30264dbae4ec326e",
38 | "signature": "3044022048cb445613a187c71ffa6ef3cdc6e9af0d86bdeaa3373a11bd9f04c6ad10a9b802204a4f84a78245c60532f93f0bdacecc53942f722ffc8764616613ebb98fa0f60d"
39 | },
40 | {
41 | "input_index": 7,
42 | "public_key": "03f2e51b2f298bab1e4485d7d31a559de73fc29dced12f568e30264dbae4ec326e",
43 | "signature": "304402203147ac2addd51426c7e5fbf83daa82f5b7b59280111b236a3b42c98f98568d2702202869bbfb1aa9a0ce29c730d6eb27951e3d295f7315e617f5ad856c9e398ea2bb"
44 | },
45 | {
46 | "input_index": 8,
47 | "public_key": "03f2e51b2f298bab1e4485d7d31a559de73fc29dced12f568e30264dbae4ec326e",
48 | "signature": "304402206385e6e518f6c40ae8ed5c33109e93b335d131eb49229c529b9ba722f737529e0220232374f110f84014e6274bcc4dce606fa25a3d59d4f028ebc42e45d070275688"
49 | },
50 | {
51 | "input_index": 9,
52 | "public_key": "03f2e51b2f298bab1e4485d7d31a559de73fc29dced12f568e30264dbae4ec326e",
53 | "signature": "304402205019b1479d453820555969e0c101df35b8c505f558dbd0dc0d5ed5289976d94502201b392ca1fb42f0b616f5fe1558aba46736750998cedf619d760eb7787c47ac39"
54 | },
55 | {
56 | "input_index": 10,
57 | "public_key": "03f2e51b2f298bab1e4485d7d31a559de73fc29dced12f568e30264dbae4ec326e",
58 | "signature": "30440220337043e970615bf1a12c6bb952aaca2bb26d159c4d1a1e8a389b003150c88f4702201bfac594e6ebf526b507512c38afcdffe90301b705f3a6344dfb94ddb8a62f40"
59 | }
60 | ]
61 | }
62 |
--------------------------------------------------------------------------------
/data/test-cases/json/create_and_sign_transaction_response_dtrust_p2sh_3_of_5_keys.json:
--------------------------------------------------------------------------------
1 | {
2 | "tx_type": "dtrust",
3 | "tx_hex": "010000000134965dc0e3e4aef1c244d426837c83291d9da1bfac830d41cc5a4e75504a570a0100000000ffffffff02204e00000000000017a914b181639abb73131894e5e96e00b0f5db8a42d7a087c4ce0d000000000017a9148d5d2999eb915d22a3584775f542cd48176fc5e28700000000",
4 | "signatures": [
5 | {
6 | "input_index": 0,
7 | "public_key": "02510e29d51e9a4268e6a5253c1fbd8144857945b82acb0accfc235cc7ca36da11",
8 | "signature": "304402207bb0bf476f44b895e8daf52b8e810712e3ee1993ba25714eb9a26ee9d7c43571022070853c14e30aa98723d6277c7d44b618cebdf8ac0bc7b3cde7bc8951c2fbf775"
9 | },
10 | {
11 | "input_index": 0,
12 | "public_key": "0201a819bf549c253c4397bdd1535374de39e9bc278f637afdc27642d52cf79139",
13 | "signature": "304402207649104a18c341be32d604b64c60b3afc8c8aa6f1691e39190cfafa605882b0b02204632fcac63ea0a110063736b8c284ddb1c07aa897999d0cd239a5f5baef25dbc"
14 | },
15 | {
16 | "input_index": 0,
17 | "public_key": "039e3aa9ea182ccdaff2d8d150010b27cc4765c1d55ce674e52631af7376354d62",
18 | "signature": "304402203d91e8faa1bef7a5a0f0d8b3785a8ff00bbaff2b791cdc1a9b5fa9d3807d2e1002201e4c4019a3749d5181158a55915cf561c6500bce7f8dc48cf6691fb239423e57"
19 | }
20 | ]
21 | }
22 |
--------------------------------------------------------------------------------
/data/test-cases/json/create_and_sign_transaction_response_dtrust_p2sh_4_of_5_keys.json:
--------------------------------------------------------------------------------
1 | {
2 | "tx_type": "dtrust",
3 | "tx_hex": "010000000134965dc0e3e4aef1c244d426837c83291d9da1bfac830d41cc5a4e75504a570a01000000fdd0010047304402207bb0bf476f44b895e8daf52b8e810712e3ee1993ba25714eb9a26ee9d7c43571022070853c14e30aa98723d6277c7d44b618cebdf8ac0bc7b3cde7bc8951c2fbf7750147304402207649104a18c341be32d604b64c60b3afc8c8aa6f1691e39190cfafa605882b0b02204632fcac63ea0a110063736b8c284ddb1c07aa897999d0cd239a5f5baef25dbc0147304402203d91e8faa1bef7a5a0f0d8b3785a8ff00bbaff2b791cdc1a9b5fa9d3807d2e1002201e4c4019a3749d5181158a55915cf561c6500bce7f8dc48cf6691fb239423e570147304402204ad00b8fd0918e5a0e9ec353a32139265ab3e633748dc85494561f1cee748551022073b229aad08f7bf62020300a34df587336a30784b29439abb405435413c961f4014cad542102c59aa2350d024c456bb5047df28a5cc73fe2799999ed11035adff1dd24eb035a2102510e29d51e9a4268e6a5253c1fbd8144857945b82acb0accfc235cc7ca36da11210201a819bf549c253c4397bdd1535374de39e9bc278f637afdc27642d52cf7913921039e3aa9ea182ccdaff2d8d150010b27cc4765c1d55ce674e52631af7376354d622103ee980e6334142342fcd9e6facfecfa139981e2276584c91d6a9739d533ac99fc55aeffffffff02204e00000000000017a914b181639abb73131894e5e96e00b0f5db8a42d7a087c4ce0d000000000017a9148d5d2999eb915d22a3584775f542cd48176fc5e28700000000",
4 | "signatures": null
5 | }
6 |
--------------------------------------------------------------------------------
/data/test-cases/json/create_and_sign_transaction_response_dtrust_p2wsh_over_p2sh_3_of_5_keys.json:
--------------------------------------------------------------------------------
1 | {
2 | "tx_type": "dtrust",
3 | "tx_hex": "0100000001f3959c0d0281f0c265ea27e28c4f7369b0c4599338c3edde8f999d85b9a685cd0000000000ffffffff02204e00000000000017a914b181639abb73131894e5e96e00b0f5db8a42d7a087777312000000000017a914d4d8bb14b4edc0c080f5f8cd009da3949a92a3cd8700000000",
4 | "signatures": [
5 | {
6 | "input_index": 0,
7 | "public_key": "02510e29d51e9a4268e6a5253c1fbd8144857945b82acb0accfc235cc7ca36da11",
8 | "signature": "304402202993a66e25fafe1d1b3135dc5318c7eaada3b261f11ee487bdae663927aad944022071853079a911468c8f39daedf245196b0117bcff490e611836bbbf5a56cdf28f"
9 | },
10 | {
11 | "input_index": 0,
12 | "public_key": "0201a819bf549c253c4397bdd1535374de39e9bc278f637afdc27642d52cf79139",
13 | "signature": "3044022019c6f407d87a5d0a00d0627da79d64c9e947e8f0268108283807bb9a4b2ae86202203b0b3fe94998038e7d6f950bff05d94c9398343181b44653bd99fcb188357df2"
14 | },
15 | {
16 | "input_index": 0,
17 | "public_key": "039e3aa9ea182ccdaff2d8d150010b27cc4765c1d55ce674e52631af7376354d62",
18 | "signature": "304402201fabc50f84f8577ec60157276b1d00c3375bbeeb9005c07fa6a76a87d786238c022055e67b6ad7f63b6eb1c70eadeb9208768dc2acad04472dd837c99f3afa0e0c23"
19 | }
20 | ]
21 | }
22 |
--------------------------------------------------------------------------------
/data/test-cases/json/create_and_sign_transaction_response_dtrust_p2wsh_over_p2sh_4_of_5_keys.json:
--------------------------------------------------------------------------------
1 | {
2 | "tx_type": "dtrust",
3 | "tx_hex": "01000000000101f3959c0d0281f0c265ea27e28c4f7369b0c4599338c3edde8f999d85b9a685cd0000000023220020d6c7f0329b85272f1a42e92dda2f69bc4aebd4714df489717684bc22fae56eb0ffffffff02204e00000000000017a914b181639abb73131894e5e96e00b0f5db8a42d7a087777312000000000017a914d4d8bb14b4edc0c080f5f8cd009da3949a92a3cd87060047304402202993a66e25fafe1d1b3135dc5318c7eaada3b261f11ee487bdae663927aad944022071853079a911468c8f39daedf245196b0117bcff490e611836bbbf5a56cdf28f01473044022019c6f407d87a5d0a00d0627da79d64c9e947e8f0268108283807bb9a4b2ae86202203b0b3fe94998038e7d6f950bff05d94c9398343181b44653bd99fcb188357df20147304402201fabc50f84f8577ec60157276b1d00c3375bbeeb9005c07fa6a76a87d786238c022055e67b6ad7f63b6eb1c70eadeb9208768dc2acad04472dd837c99f3afa0e0c230147304402202a6645d538554dfd0e6d88737e1711ef575e6acd1041f2e10f982e58b49a997102205ab473fab9426408cb95604d61bb0c00c48bdf599a29543aceb6b84f5d547f8701ad542102bdecaf3813bfb1d3d9a3a20844fb979a2cdd9664fdf5b8a8eaddaff95df67a032102510e29d51e9a4268e6a5253c1fbd8144857945b82acb0accfc235cc7ca36da11210201a819bf549c253c4397bdd1535374de39e9bc278f637afdc27642d52cf7913921039e3aa9ea182ccdaff2d8d150010b27cc4765c1d55ce674e52631af7376354d622103ee980e6334142342fcd9e6facfecfa139981e2276584c91d6a9739d533ac99fc55ae00000000",
4 | "signatures": null
5 | }
6 |
--------------------------------------------------------------------------------
/data/test-cases/json/create_and_sign_transaction_response_dtrust_witness_v0_3_of_5_keys.json:
--------------------------------------------------------------------------------
1 | {
2 | "tx_type": "dtrust",
3 | "tx_hex": "0100000002e85f49ce97b5561c1fe96253c72daf37220855e710db0da9792c47c4035e7a020000000000ffffffff7105c9ea492ebac1fd204d066eeced78b5391b26ab7c87b6963e5e4e0a3a2ef40000000000ffffffff02204e00000000000017a914b181639abb73131894e5e96e00b0f5db8a42d7a087b82e0000000000002200205b218e070e9fe04b724129c8f95d27854d53ec431c58761d4a22d1a66c2360ba00000000",
4 | "signatures": [
5 | {
6 | "input_index": 0,
7 | "public_key": "02510e29d51e9a4268e6a5253c1fbd8144857945b82acb0accfc235cc7ca36da11",
8 | "signature": "304402206d07505e3db3bf8c740e2d5c4ec9fce64e0bfa8758c073ea9757b4b1fa943b74022052c49f2601842688e234f3aeca59f6e47a72928226b3c0745370fc05f2a725ba"
9 | },
10 | {
11 | "input_index": 0,
12 | "public_key": "0201a819bf549c253c4397bdd1535374de39e9bc278f637afdc27642d52cf79139",
13 | "signature": "304402201201d5df81209c101780ab520b13574513ef04a281045ba7dbf3bb8cf7d09315022025b62392b9b755d742b6a5f5d73e1b5f99b6fef4307a8cd7713564c6acd3fe38"
14 | },
15 | {
16 | "input_index": 0,
17 | "public_key": "039e3aa9ea182ccdaff2d8d150010b27cc4765c1d55ce674e52631af7376354d62",
18 | "signature": "30440220387157472111bed5e8e2c3b598144bf2794408d7ca9c576e41a6d199de5ed4520220735a6b5b836ee17dd7974cb60d8b11fae451b1640a3d1e2e71491cee38c20f9b"
19 | },
20 | {
21 | "input_index": 1,
22 | "public_key": "02510e29d51e9a4268e6a5253c1fbd8144857945b82acb0accfc235cc7ca36da11",
23 | "signature": "304402205824896bc40e3b213b3d21d4c4f00a01312f9fb4aa0d09311227a50a21a2d3880220033c688dd9302d56107cf4e4210b4dc03993e34c168022f5ec87498ab06e4c74"
24 | },
25 | {
26 | "input_index": 1,
27 | "public_key": "0201a819bf549c253c4397bdd1535374de39e9bc278f637afdc27642d52cf79139",
28 | "signature": "3044022045448fff60a911ec7a061e25fc09172bfe0eb66ea308eacf3014a419e939e8e3022008c3f6df9f949437fbd6514f6eb6cf71ea507bbbd9eec1ee907cab701ac3c665"
29 | },
30 | {
31 | "input_index": 1,
32 | "public_key": "039e3aa9ea182ccdaff2d8d150010b27cc4765c1d55ce674e52631af7376354d62",
33 | "signature": "304402207f702b80aa73343fea0bf8d0ff5bbcf6bd532f1f1dd0a12d8e44ea3ef4df8adf02202904cf3fa8c1c08c87be6d2465d8a0a0a5fad7cee0ec96d91e65bc58de46b6ef"
34 | }
35 | ]
36 | }
37 |
--------------------------------------------------------------------------------
/data/test-cases/json/create_and_sign_transaction_response_dtrust_witness_v0_4_of_5_keys.json:
--------------------------------------------------------------------------------
1 | {
2 | "tx_type": "dtrust",
3 | "tx_hex": "01000000000102e85f49ce97b5561c1fe96253c72daf37220855e710db0da9792c47c4035e7a020000000000ffffffff7105c9ea492ebac1fd204d066eeced78b5391b26ab7c87b6963e5e4e0a3a2ef40000000000ffffffff02204e00000000000017a914b181639abb73131894e5e96e00b0f5db8a42d7a087b82e0000000000002200205b218e070e9fe04b724129c8f95d27854d53ec431c58761d4a22d1a66c2360ba060047304402206d07505e3db3bf8c740e2d5c4ec9fce64e0bfa8758c073ea9757b4b1fa943b74022052c49f2601842688e234f3aeca59f6e47a72928226b3c0745370fc05f2a725ba0147304402201201d5df81209c101780ab520b13574513ef04a281045ba7dbf3bb8cf7d09315022025b62392b9b755d742b6a5f5d73e1b5f99b6fef4307a8cd7713564c6acd3fe38014730440220387157472111bed5e8e2c3b598144bf2794408d7ca9c576e41a6d199de5ed4520220735a6b5b836ee17dd7974cb60d8b11fae451b1640a3d1e2e71491cee38c20f9b0147304402200298d300b22bc1f02d63142ff0ffff2236cf2d47e3c79af28cae3da6488698a0022075647bf8137c9272eb3f5b8906842ade89efe57df2f08d299bbddb7d05c8ceed01ad542103a37fd7b94d49db3a3ab57d0b07b1408057e63d76d80a1286f841f04fa58e35402102510e29d51e9a4268e6a5253c1fbd8144857945b82acb0accfc235cc7ca36da11210201a819bf549c253c4397bdd1535374de39e9bc278f637afdc27642d52cf7913921039e3aa9ea182ccdaff2d8d150010b27cc4765c1d55ce674e52631af7376354d622103ee980e6334142342fcd9e6facfecfa139981e2276584c91d6a9739d533ac99fc55ae060047304402205824896bc40e3b213b3d21d4c4f00a01312f9fb4aa0d09311227a50a21a2d3880220033c688dd9302d56107cf4e4210b4dc03993e34c168022f5ec87498ab06e4c7401473044022045448fff60a911ec7a061e25fc09172bfe0eb66ea308eacf3014a419e939e8e3022008c3f6df9f949437fbd6514f6eb6cf71ea507bbbd9eec1ee907cab701ac3c6650147304402207f702b80aa73343fea0bf8d0ff5bbcf6bd532f1f1dd0a12d8e44ea3ef4df8adf02202904cf3fa8c1c08c87be6d2465d8a0a0a5fad7cee0ec96d91e65bc58de46b6ef01473044022002cbf5c78011eea75f89f38fa3733dcbdfb2853cc11aa76ceb7c9a3839bf056f02200250151d80d85810fcb5bfd0db0515027c92d272514699987faa0e27a88bfcc101ad542103a37fd7b94d49db3a3ab57d0b07b1408057e63d76d80a1286f841f04fa58e35402102510e29d51e9a4268e6a5253c1fbd8144857945b82acb0accfc235cc7ca36da11210201a819bf549c253c4397bdd1535374de39e9bc278f637afdc27642d52cf7913921039e3aa9ea182ccdaff2d8d150010b27cc4765c1d55ce674e52631af7376354d622103ee980e6334142342fcd9e6facfecfa139981e2276584c91d6a9739d533ac99fc55ae00000000",
4 | "signatures": null
5 | }
6 |
--------------------------------------------------------------------------------
/data/test-cases/json/create_and_sign_transaction_response_dtrust_witness_v0_4of5_253outputs.json:
--------------------------------------------------------------------------------
1 | {
2 | "tx_type": "dtrust",
3 | "tx_hex": "010000000001230ecb7ff26cb82cebab87c44b4d1031239456b338308ee753e6ecfc1ab2ac79010200000000ffffffff3659d0c80935a690ae7c1c840525727ef5850784c3e3cfe74989451f13bdc0e00200000000ffffffff51918a88a68b449bea1823bab4d008a31b6505b2111da8a3baec4be8eb7abb870200000000ffffffff93cf389b25a9260ef8dc38c85b2997fcd499ba20ac4455982969070fec8832120200000000ffffffff778008f3a1b2b444f9585ec12111c74fe38ea7f9e61775f774abbe989a7df61d0200000000ffffffffd754c449213fc9a221d2521ec5c0662d338a8ede824d674889303cdd2e39861d0200000000ffffffff0a734cc8387abf17c9613e057d9e34423e0d07e80df3c9cddd7458944ded117f0200000000ffffffff733ce47a6506673b674bbe02e9a5081da1e948bb01712de25c71534743117bbb0200000000ffffffff257103178d2faaa3c97b142c8bc524415c5e10d5c45a35f4643da7ead22defe60200000000ffffffff6221ddc287332d3e495f04e644eae4b69f2773f7f7d9628a2cafbc1ce8eea2770200000000ffffffffd7312488afc29bc6374ed2624ab11565596ab3a1202e791b20aaf63ceb754dc70200000000ffffffff2c449bda0cddb0cb86f87d08f81fda0dda99e6ca6d338803ee585566890ef7f80200000000ffffffff1bb26c75d87f21cc577823488f322476a1a9056d3eacc7c2461ce95f89f94bfd0200000000ffffffffb030c4407fc529bcb3b5a3717fb4653186ec08f79b3ca26b4eca0ef763cf565b0200000000fffffffffceb5395bf8571d5d13b30b0852e7d97f152a02936c24d84c5ca7e69f84c334e0200000000ffffffffbe9099bea7b97eb2c9e29741c3f8ab3d5b2a7868f4d8863a6c71d62b65780c530200000000ffffffff3b9ed91b0d084784289b35ec1369d068f4b53f5aad8ae8ee4d0ee06f049c652c0200000000ffffffffe42e08cdca8167f4ebe30e3d7bfdb5774d54c7978682637f5536923e44ce18660200000000ffffffffb5398061d44ae37c61e8e01376437c9e437fd2977d4eb8197a6db746417e98990200000000ffffffff03d866a8bb550b859fd62b093cba74250800cf8795e8cece91ca54af74c64fb50200000000ffffffffdbbd804213a9a9fbe70cf68645ec128e4b8b0b0860ee7fd2c4ddc04628dca6c40200000000ffffffffe88df8e2623971ad70e1be880097d91f85401d1eb530ec4d8f5eaa2e185adf7e0200000000ffffffff7209394752ab5f0d2feb8012cfe78e896c96b0f50b83de295e41af00fbbed9ef0200000000ffffffff436c77f9ef61d8e520c578e07b978bbe86762aa567f72427ce9d8aab3014b4eb0200000000fffffffffb9443f80ad675c0599d74d215c22fe0de0be9dd186c1dbf898816ba9033e5a10200000000ffffffffeaa335d2dfa86fb90429fe9095153838e392ee557d97869cea43da6bc44e99570200000000ffffffff2a48566fa4df226efe9e7d659cd1f938486e376c2bec5e497899e5ead8a058480200000000ffffffffd5cb1966c64a8414a879fd45876180e043109bd33225bb1b0362349241e25e610200000000ffffffffc3412113a3c60e6f43d55d134117a16d1e90df439f58138e94857059ecbb24b40200000000ffffffffd714d13f5acec80070a2ea7dce701fd6be2c0b4f86306d90a36d90874b34fdee0200000000ffffffff24777b9fa9fdf16bef805fa0e30f7863adb1834ee8569dab3b70ad21bec378100200000000ffffffff79dee647f8bc65e418f79cd1ecfda4c742e0815c8e768018b38bd03d5a98dd7a0200000000ffffffff0806590239a55f6af0e6465e47cd93eb92ac168f933b3ab4feaadfb5057cc55a0200000000ffffffff0149916c16e288757fc96b05a70d3a52878b2c743251b0b31e723448b1582a000200000000ffffffff5837cad3f5a8b419ba6bcca212aad16e95295cf42d89fe4b2854e52786c616bf0200000000fffffffffdfd00204e00000000000017a914c70ca37da55009f249a3fd80403b92ff14eae32a87204e00000000000017a914d68ca31649c642e0a2270b13f61bfa34b0ae632387204e00000000000017a91402a6d0c2e11682b8bf3ebcc883b6971210d4eda487204e00000000000017a914d48910dbdd8f23890f747f79dbc61e82144176a787204e00000000000017a9149779b023948be8441ae94c965d79ee74fe6e5cc387204e00000000000017a91458762cca9caeec958ea6fe8be2ddee67ba50c74687204e00000000000017a914177f2ee61cd8508a4752254beedcf66207b07caf87204e00000000000017a91424379b17a26e4c012fd5a2fc1026bbd1f85a15a287204e00000000000017a91420919c0b1ffd57e98610f44732f94b2d68d0224287204e00000000000017a91463a822339e45214fe8b92bd51d9ef73d349b0cda87204e00000000000017a914eb289b268893a9ef4a9e0e39068b13c9b10d4fd187204e00000000000017a914016f454ea8043f115cce2f4200cb2f56381b7fa387204e00000000000017a9149a274c5b1f1df994bb89a7f3723c63c402d1fa8887204e00000000000017a914dc2a484c3a24f8ad5953fc8febb444bf1e940cf087204e00000000000017a91421f29dc04b03e1bb2450f6052707496d7b1b474d87204e00000000000017a91424e2df3368ce99bd4d41319b04abd8469d6694e187204e00000000000017a914cf902171ca56ad7b01e267e344496e5e1cf9e61087204e00000000000017a914bb1e9ebcc99cbd469eb5d67ee9df5f60fceaf93d87204e00000000000017a91498f1798c691fd542e74a2b03ece8250e86a3dd3187204e00000000000017a914c8ef62b7d8e072a4a9df23e829dec0fb289dae5987204e00000000000017a91415c078637957df24b949af0828741755d0e926e687204e00000000000017a9142baccc41414c0a0e9235f5cb055c955851492c0787204e00000000000017a914dac2255699bd060cc9312fb477e8ff448a8c797787204e00000000000017a914e8f9ae0b8e7969202bf14c85fcf2ba761a6073bb87204e00000000000017a91417d545c2201dfc96bf9184ad8e86253142f1b19787204e00000000000017a914a09fe56dd20658e7e9adf71dc2d8afa5ddff79b987204e00000000000017a914a776fa0514799a9a7c9e1d332967711b9f63783887204e00000000000017a9143f119c6199b9e8e198384fce365f10f07acb5fea87204e00000000000017a9146c195cd1d98117f70755bc1d46db7b7f1b00c19587204e00000000000017a9140c432b3ed2d70b38b468ee14c2a743f6eab3283687204e00000000000017a914b20028d3beccf3e5954cf052a4357d6bab1341b587204e00000000000017a91430893081aa4767eecc25fbd1dd08675cfc528d6787204e00000000000017a914f33e15d2cfe86f99b667716a9d2de7e481b0dbe087204e00000000000017a914f3d08bca97dceac1189a52c77c0040e7b4913a6c87204e00000000000017a9144f04565d837ee8392fdc62e857520c45c631c03287204e00000000000017a9149f4039ed6a4922a56a57af7fdb1baaeacdb5c6ff87204e00000000000017a914cb738b1034e6222cb0d9b8ed8936407ac51b341e87204e00000000000017a9141a0a85ae00716c932c15986d141162f772057c7c87204e00000000000017a914c061e2e5a9172ccfade0a4842fbc92392005d27987204e00000000000017a914c3d5accb2865297e4a8ffe11db6234a83906490087204e00000000000017a91451d348b9866f0094b05a47e491547edb217eff3987204e00000000000017a9145e20461572c961727609a08e7f6ee15b895dbbf287204e00000000000017a91494f6a68ecda68f050c01d38a8661c18d9570e75b87204e00000000000017a9146f5d541a57cbcaee2a3517e5c19b3531647325bf87204e00000000000017a9140fd6453e3b81a539d40df2a247b842b67a3fb45b87204e00000000000017a914f1c95abd6913b9c03eefe88a5db9244f1bc8958e87204e00000000000017a9149d65640c7eed06eb19af23451a52677a37da917387204e00000000000017a914a52f37cfe2e91082fb14e5369a5efcf06dfe455a87204e00000000000017a9145489e5be6671eb367e5312beedc1a646e791e1fb87204e00000000000017a91497cc3e013180e75995f78ba39d764cd8d947545687204e00000000000017a91467ec996b691b5ec2fc11b24b9f3df4f73bf2c71f87204e00000000000017a914843b91dececa4a52275c411712a46293afa8f06187204e00000000000017a9142f614fd46c84e844abca6a720e85a73d79cbd70b87204e00000000000017a914597ce1f024490182c88e8c4f27d444a1265aed6087204e00000000000017a914525a7ef0b2305e94c3065b40a7bba476a89b89b387204e00000000000017a91455e11b35615ef0dc7d9b1747133e1bc6beef1d0b87204e00000000000017a914c72a861aff69ecc85da0502d925f3a9051693bb187204e00000000000017a9143492fb75132e38c3877d1edca0ec9f0bc08591ba87204e00000000000017a914ce09e6b9e12290d4650ce8cdf0c8c22d4fa4fe5387204e00000000000017a9140e5f1256df04df0fae6fbc33fd7e5683e87d41ad87204e00000000000017a914f1611925fa1fc3edcc957a81a4d081095255357f87204e00000000000017a91414d3f994edc35fb63de481520f0f5f25c7c82a5687204e00000000000017a9148f60e53ed4a93e48f87167a2360940cad243ae5e87204e00000000000017a914a45d33d7f26f2312f9e5ebce208d897a3f19d76487204e00000000000017a91482f113fad254efc0ddebd7a72df2ffaf4ae7234d87204e00000000000017a9148cf557233b27ddfaacc42f1600d7c8c2cc52e4ae87204e00000000000017a91463519e110b48129266416f862b76480d9989122587204e00000000000017a91406c7f078624495e7cc9d5b961737791146f06bdc87204e00000000000017a9148ac7698446f2a9ae075c3963e2f904ab0feaef7887204e00000000000017a9142d6a4f776cf8be0ab280e658c008a9e17fd2acc687204e00000000000017a9146f1b2d5ec71e5f58c1bbc52fa3f79b7f430e3e8e87204e00000000000017a9147ba5adc1aafbd7f984756d361a259be75c2fda2d87204e00000000000017a914b8e4b815a7867d253e7944a11549838593df5ff987204e00000000000017a914e912992da037597e2124d8336c252c40fd77d4d087204e00000000000017a9146f61e0febe02316cc7ab2985064ee5e0ade447bd87204e00000000000017a9142ff040b8ded1e2c0dd7bdc2a9eb1394ef08db83e87204e00000000000017a914c6eea09c4df875106da4c37297bc5682cebbfea487204e00000000000017a91429d17ba52abee77015aef19c51538052ca795b7987204e00000000000017a9148e859aea188324b9998fce7a157444d4f2c886b987204e00000000000017a914ab83839b96f40d70496b01412b36ca97e5107ce587204e00000000000017a9144ab2acb452ecb754a6f5dccae97b3987dbc872cc87204e00000000000017a9145f3514dbb154baef2bc2aac14af04c3c21b0ed5487204e00000000000017a914a9082ee9496eebd65d8a62cee3fa690884c7105287204e00000000000017a914e6643d9e166537e201f6694f9dc1b51a703baa9387204e00000000000017a914a2f0925c5537e915d25b0461dcefc6e745bb8f8e87204e00000000000017a91489da55406ffbf13ce537360fd8210db8647d236c87204e00000000000017a914aa7ed0cba255995e9e687e1379b9f4ff15cc7f6087204e00000000000017a91462986ee5ae20ae7808ff88028692dbb1ae8f395387204e00000000000017a91416a7bf1bf0b4516ff3d5785ee78fc0e34e93191e87204e00000000000017a914f656fe02405be5c68a895b27b303f1c7b55d525b87204e00000000000017a914f5cf2e87478869515f0c4d1a9d4b400cbcc599e987204e00000000000017a914339c0b5574808db02ba45e21d8d5fb21fff033a487204e00000000000017a9147a2964d470109a0f3a013f4de53b30db9efaec3687204e00000000000017a9141adf7f9e196236c7a0a32149334b6f1b56ed4a2287204e00000000000017a914e0d5a795294bd75c38ec4211be6252c7024a138c87204e00000000000017a91468b134d148dba5466ac0d5d19c88145e41e6785a87204e00000000000017a9147665c49682957bc2b0fa654424e2eaedce0ecf7887204e00000000000017a9147e3598eaaff836836e4d5547d96c42b8d57649e687204e00000000000017a914a0c5607b8362addf43825d9db771514b3b67e4ad87204e00000000000017a914d6acc66dda8ca1bf3b333b682fe642b75395517a87204e00000000000017a9147b286c930df4b06fcaca9705b2a435781897cab787204e00000000000017a914b23aa167f04162139bf72615c9717c7d54e3328887204e00000000000017a914223cd01fbf593bb9dc4719c75caae068339f5b1d87204e00000000000017a914939910bdbe6eb72e7ace6852635df3dc5a73ca1d87204e00000000000017a91471ed3ff151db1d7bb7c2fe3889f2522db960e14487204e00000000000017a9148e92b798ed4d754a2d98d43e366719c15e61365a87204e00000000000017a914fa22b883f76df1c0efc91c80855410d748d29c0087204e00000000000017a914e2ce057de83516f064ac5283e3e4508d8694871887204e00000000000017a914437c8976357dc50bd57da966ca392fbe46e73ebb87204e00000000000017a9144399b0ab710ea0860819aa131b77ee420684f68d87204e00000000000017a91423a9d032bb06b54a8c9e6bc400d2e86e636a648687204e00000000000017a91490520584d77580e139e419d89bb33b832996128787204e00000000000017a91427419341c9995ff899d0e40f676f77e3cdf5cbae87204e00000000000017a914eb43ab55cfdfe4e6ec9d4e21b6102e9cdf38115d87204e00000000000017a91459ce0419660258cf9d0370a1a6591b29a151ee9687204e00000000000017a9143921e2e09cc601bf21b50c7bfc09e351e3a5d55f87204e00000000000017a914d3cd1ddb45f17ddda58e5e4f77597a89a8c043c787204e00000000000017a914e67cd562e6b889d8b05ca035cd39281c1d09d38787204e00000000000017a91428f5315db4b45876e567710bfca17848652803fa87204e00000000000017a914f75db64bffe7efa47c68bbebd69c420eb44e2fae87204e00000000000017a91490388d5bec04599aa169f199834ebe5b3d1a246187204e00000000000017a9145648290d53523ea618363ffb1ef8d26223989cec87204e00000000000017a914f11e9488d38659428b8d871a47203bd2fb15530487204e00000000000017a914e4c1094a2dec208713d0c7c57c83dc3967f5f78087204e00000000000017a9141c66bc481e0cde94d739c345931c43a4a12ed57687204e00000000000017a914d1c0f4cdc61ca1044fec849ddc16f7979a0c436e87204e00000000000017a914b26aec1e468b42386c54834ecd5bb23148405ca187204e00000000000017a9144a3808be26cf7a18d276483f1746df14ef77ad0487204e00000000000017a91490527e48cee3317debc8f019f8faef27a0eaf6f787204e00000000000017a914b60091832547ea6a76a16e3b20206c79d054167b87204e00000000000017a9148985d09ddb596767996654a7c1a1124412c6b95787204e00000000000017a914c379fb79e2da83bd03086403a9da122254e2adf687204e00000000000017a914349e1dbac217d98f1a0d0ff77c1101c7bc1e7be187204e00000000000017a914f96e2e22cef965e1750aca76bc11335b5f6d9d4487204e00000000000017a9147f56f33e4b2b8ec4a74c8d9800dc90543a02b4f287204e00000000000017a91462828a6d8a9f2dac5e7c1d2028862d73b2a29a6687204e00000000000017a914577cd5ced4c20b8e8a1eae86b08a280ea690bc9787204e00000000000017a9147ee9b2417dba8a982026a69d16399a898fff07b587204e00000000000017a9146f6c7a2e8e0d57e06b22d037eece88e0f5f9efa887204e00000000000017a91479695ab284b031b3d7c41b757d90d89f37da863f87204e00000000000017a9146cac27f59f45db03597d62af931d5a022032e7c087204e00000000000017a914d681a71ec418fa6a6fe0c6c160959502dab88d1787204e00000000000017a9147e185697d46ab2a704ee7ef364ff7d075489eea487204e00000000000017a91484222114ce30e97b61538543bae9dc63e464393b87204e00000000000017a9140f5a97be94462d30491369a012beb5db65062bcb87204e00000000000017a91406843e78ee746914c1e158cbb7da3a9c9e46b3f387204e00000000000017a9144d8a82e0f76eea534464d06184c75f1ae3e931b287204e00000000000017a9148e1ad7465067b64753454ddf89e1156eee2fd62687204e00000000000017a9145be71e09c9c98bb47dd66410478b9ac51f9fe78087204e00000000000017a914e49f3a6301a57370849318d8409942b6adea015187204e00000000000017a914255145b8c05395327cccc90170379ea462c8d61f87204e00000000000017a914424d96ea4b3e2abe256f50b2197e6e1d79faac2487204e00000000000017a91402bea32eef3147444b147ea7a68591c7ab1dafcf87204e00000000000017a914456898fe1970b00b906a8c7faaf1298ec7e9654387204e00000000000017a914194dfddfe779bba09ba08bd4c94b1d05f82a980d87204e00000000000017a914f4accceb0e217883f3abb841d7bc8d7e1fa944d487204e00000000000017a914b5dd0b4705f6e0c40f7d289a4660600324a936bf87204e00000000000017a91412746676d0ed638048e2b81d95a2af01c9a508e087204e00000000000017a914c564b4d005da76c8d5e4262ca1f8d67ea6bd06e887204e00000000000017a914a7764b658cbda8693839ed36bfa65ef0de3632d687204e00000000000017a914d6d3b2dbf66fcddd84225ef39c703faa3a1d937887204e00000000000017a914d97ca7f99258cf54ff171f5ec934cf6bcfe0a13887204e00000000000017a9145294c70f2fd193894badacc41a33fb98e2680c2587204e00000000000017a9143bd5a16fb18512195c03573dcccd43cba46a09ea87204e00000000000017a914e50eaa2388ce643aab45435cf7b2412a0687956a87204e00000000000017a9148e86d92c3b8478a515e6b376a2d541732186f07187204e00000000000017a9146ec9314580777f5a7e1a2367a5b193953681b33987204e00000000000017a914a9d92d790fc981de95ce126816ba708037ce5d8c87204e00000000000017a914ad1dae39d8311425b79344a30d391b2b94b9816487204e00000000000017a91407cdf0ba650b58669d1cf51292fed74b0b57911587204e00000000000017a91493537c3660e1776583bff1183057e2cbe5240d8287204e00000000000017a91482a960ffc504d4aedc26d59c1e1731e58f7d48ff87204e00000000000017a9146100c594e7020b3881a2c062155f1d46dea15dbe87204e00000000000017a914b6d953ef8b88d81337a2882c7aff857094d8013c87204e00000000000017a914c1f3bd17b8ce806b6027444824d45ce84bbfac0687204e00000000000017a9145b5fba5e28732acebb243b9ea702499411b1a95b87204e00000000000017a91439444d3d3e25aafb350d0bce15adfefc7641fa9687204e00000000000017a91448bb343960332dffad5b5cb7ddee3ae5f327d82f87204e00000000000017a914bf060028a12de9a514a1a12aaa0652a06b63c3cd87204e00000000000017a9140b0ada0a85364d792b8c70141ff1675273bdd07187204e00000000000017a9144511438c5b92b9b6e1820c2a3697b8367dc939d687204e00000000000017a91499088387b92766b80aa22334e442944395fe5a6787204e00000000000017a914f927ef7161bb94a379330467ac9e86edadd4e4f387204e00000000000017a914c310616f9d738cb67e009205c5bffb4a99bb4e0187204e00000000000017a9148556648c99999a790c740b44c153a3256cfd2b3f87204e00000000000017a91416d1831ff2f5ecdc9806ca854cc6843ac677c59e87204e00000000000017a914a7f0fca07a49f42a8583f2e5023e9b4bde14417687204e00000000000017a9149150943a90c9666d871a5179302b23189f12de4b87204e00000000000017a914b097d64d8988f6b2f8442f6151f915c4a322841787204e00000000000017a9142918a1c8076dd8790451658a45447dd69d7324ad87204e00000000000017a9142b8a885b73c0c39f8fada9438991045cd8c5224787204e00000000000017a914d78a5d792fe7d5a1d87da234e4d2dba2c80ef6fc87204e00000000000017a91465d68e2dd8bcddebea29427267fe519311e9e99687204e00000000000017a9147acd71ad1533337f5493152cee84dadb2a39b84f87204e00000000000017a91418e58092fb7deea5b4eaef54f741a3df14a9a7bb87204e00000000000017a914481a9d6208477c745b7e17b87ab9d70bf5a2b20587204e00000000000017a91432c7ad5734d7904ff052a28daea3db9dc1dd4f6e87204e00000000000017a914db7cd471731e1dfecea8a94daa5ea13f1c95053387204e00000000000017a9140fe89d25449e56e63fefe8cfdab9f55dc7569e2487204e00000000000017a91461e74ff03c4e45cfe8ad40e44906def1a60d54a187204e00000000000017a914dc2729762f3f1613ac3939ffeb1a5ed2e0870c1387204e00000000000017a9143648bb2cc883b4e773e5e7d9d572f22918d1513487204e00000000000017a914857cde84f456e7ad5dbe56318b63d514894f537887204e00000000000017a91431fa56b5f0ab182ca9f1cbf47439b21b3e22758687204e00000000000017a914e43398ee6950f497dbf4ad16432137016f0dbbfc87204e00000000000017a9144c65ac70cd9e7fcfa866d19a0df6602668bc736987204e00000000000017a914651a2390bae098dc7e0f1241d996f7f71608d67287204e00000000000017a91451d8ab518f385fd14ab54392a166f61670d629dd87204e00000000000017a914f9b2011a14eb8716e1e0582aea2e000d5b97b6d487204e00000000000017a9148559699aa17028be681352667457a79ce24587df87204e00000000000017a9142687fd65b92d58f847318b7c464e9ec593a4f1ac87204e00000000000017a914cdff3777ab46b6b89dd0e0f7a585d1369ad4644787204e00000000000017a91416afdee43f0fceff42d0557e9d7f4fa6243856c787204e00000000000017a9147e682c04f7d1a091b98b4df7344923a7b349e09087204e00000000000017a914dbc3942c1dbd1d1cec822af127d0272a7db31a2187204e00000000000017a91456887317ba69cf3bc890fdc061d54e4ee40d4a3c87204e00000000000017a914b08bf64d0a8b83e5ad9f6262bf6cf732a647168387204e00000000000017a91412e31056fad122fc5b617d66732ca5e554fdc24f87204e00000000000017a9147c98891d20a34c92bb4b2d9dd7aecf76d1f3747387204e00000000000017a914c828a75a60435ea6a38d5bb028cb4bafd7941a7e87204e00000000000017a9140918ec0f15f82286d6e970fac5cf3a2156cc026f87204e00000000000017a91429e05da33b9fde9c28ffc911eb8ecb7798dfa5a087204e00000000000017a914f0f30daddd9e300ed5cf5dd3093e7eba2659598c87204e00000000000017a914370bf38b1d7bc158db49d7502280e9100235e4f187204e00000000000017a914e8d0a07b25f925136f2de976312e6c1f7e1976de87204e00000000000017a91418e46a538473c16fde66e802f762aa8c5616ff0387204e00000000000017a914ae6aec39d55d9202c498e617f620f76d1a53dc4087204e00000000000017a914a18c026cf4806bee7eb9a52164e0b9ce24d34c8887204e00000000000017a9140ebe036c016275059a9494e1347462139bb2faeb87204e00000000000017a914f5862dabd1f9a8a7074ced69f194d46ff73ac25987204e00000000000017a9146f9614fceb2babaa49ac4404f8f5999fb967031387204e00000000000017a9144be1efbfa39e87918f4ab1fd39e8d8f6c377749387204e00000000000017a914d561f8e47117f9a8127527334ffeef4d1755b05187204e00000000000017a9146bf1357ef0f580f3134cd767e1e53cb690a9b4ea87204e00000000000017a914b396652d1462a5fca8fc3e7b36dc34da0c96bd3087204e00000000000017a914fa6622fd7a6f68cd9c997620859dd885a556c0c987204e00000000000017a91418e7c88f8ea3493a4f5478ed2c73919995337d0887204e00000000000017a914c558c17c58902a6edc08cdfd376c4ec6434b33c687204e00000000000017a91496b6c3e5826ebfd657c08192a02f980251dc26b687204e00000000000017a91464add069e9b93588b62678966a8b7b5928a7461887204e00000000000017a9143409d5b034c11527cb16101cdeb5272974f7786087204e00000000000017a914c47d6251ef57b0491626054e8837ab72307b4c9287204e00000000000017a9148e673f8d2a0596416e8d5b2da9a41d6150885c8587204e00000000000017a91463bc94a8c1c39c39cb00e4ec1e138c8a8af355f787204e00000000000017a91442d34007d750fe5f52e93f3b364ac3ca5f76ddc287204e00000000000017a91469e833a168fff65cdd5e2c83fb6294facc46c0ac87204e00000000000017a914ab394039bdbeac8930c5953a1ae4bf1c306c08f587204e00000000000017a91485ba862815c75fa68229041415e6c5094819b9bd87204e00000000000017a914ffeb0666dedc4b132c6744041b857068ae232a6887204e00000000000017a914260dfdf15ac6fd37f69543b7ad8c192120dce91887204e00000000000017a9144de0551ff83005d268afc95649679c7260e9cc4887204e00000000000017a914daddf63ee568a056dbd0143c113f8be24bc8d59887204e00000000000017a9147622e657691bddeb4a620a0012685dd1eef1f546870600473044022065bd10ddabace3327b2363f148cf87a9f31492c2568f849ac75d3b1c75bba99302203a4706843be12397fa0bc74610aa3b72424884f03800c4effad21442fa7b0c380147304402202748fe19a59e74a0af23b57b7e912e1dc50462b4341be19e4db014965e3f5e5202200b787963c0b4116922dfdaece89aec8e63a03098ab94db28ab95a297b4a1699801473044022008b609d0fae84fa3232da0c18d76764003f9d47d016cbb7f498f41d40e692bc102207d2aa4a7fb95597466a2dcc459ff7cc3629d90227727abf45c326663a04a6785014730440220447f26ad0faa23e4a114665414b4ddf016c4b4befcdef308e21a1be747f2bcae02205b6079e544c36f4ceac13bcaed556ec4ecea44f6b4a4e11f3142529a2dcf77f101ad542103974f49bffe0d5276fb04c1972372f9af676d71cdfe5c3b4192edb13d7a22d77b2102510e29d51e9a4268e6a5253c1fbd8144857945b82acb0accfc235cc7ca36da11210201a819bf549c253c4397bdd1535374de39e9bc278f637afdc27642d52cf7913921039e3aa9ea182ccdaff2d8d150010b27cc4765c1d55ce674e52631af7376354d622103ee980e6334142342fcd9e6facfecfa139981e2276584c91d6a9739d533ac99fc55ae060047304402202cebf5c4f0661f79f1c0d391be460b3baf5044fd6157bbdab560ad633b9fda1202207911e5788a06d24085915ab0c61339c610a3d3b50eb2d4059793dafa5581456a01473044022073874ec2cd037cf39a9fff3b8aa6025d586d8f0e7c7085845e083800d6f5b4d4022002c36ceb9aac798b998d6cee355108c29174b43f9a866bcf1f5a4f153d1f3f7b01473044022044e9dc0537fa50522cc084e4b82a9be351c1089beb601982234216c60ba0834402206402699e82dc6119c52fd3d66a36c97db238555356fa8389c957bcff1256c8dc0147304402203edd67dc2533763dbe80ce8af76e2b0dc3ad1e79cd6430815b870d11d842d813022076aa5e4860eb09c3b967521f722b0a9027da8bfea95b58f9d6ddbc8651df769801ad542103974f49bffe0d5276fb04c1972372f9af676d71cdfe5c3b4192edb13d7a22d77b2102510e29d51e9a4268e6a5253c1fbd8144857945b82acb0accfc235cc7ca36da11210201a819bf549c253c4397bdd1535374de39e9bc278f637afdc27642d52cf7913921039e3aa9ea182ccdaff2d8d150010b27cc4765c1d55ce674e52631af7376354d622103ee980e6334142342fcd9e6facfecfa139981e2276584c91d6a9739d533ac99fc55ae060047304402203e8adbb03fe009d199be1fc11c7065a219bb73e7d8f773cfe4dfd2ae45a5ecc8022003f330ee98b5b3c6ab22271b6d50ffe609eea0c56b5706fd4433f5767f193fc60147304402201f00ee93b92e3949b419b1b9a5b863c2f5895559710bb36280d677c0fbfd528c02200acaa55f9b7104ab0659c809b860206ce71326c9634368db223570bc22c8a9ee0147304402200fa7a2531961ebb4b2055624c0e8f244b556931e61f2e63cfc981599444e2f6d022002d1c2030861bcbb764c4846c51efcacff7ba290ddd056ba07efd702f3a508540147304402207ec9a6cfc2d08360a8d6168f0d8ce9f3a58b529ebe968a21a499e20b2b4521640220377a277fe03d991d9e3e73f029b29768d519dadc0a5e59c1ec13a9c2b3aff72001ad542103974f49bffe0d5276fb04c1972372f9af676d71cdfe5c3b4192edb13d7a22d77b2102510e29d51e9a4268e6a5253c1fbd8144857945b82acb0accfc235cc7ca36da11210201a819bf549c253c4397bdd1535374de39e9bc278f637afdc27642d52cf7913921039e3aa9ea182ccdaff2d8d150010b27cc4765c1d55ce674e52631af7376354d622103ee980e6334142342fcd9e6facfecfa139981e2276584c91d6a9739d533ac99fc55ae0600473044022013e1ebdc2da7ac61ce583c50a646f0b39363a79c08a085ec2d881189544ecc5802204e9be87a8978c23bd40faefe770c6c68ad4af43dc3ccd25e7d1685c36bf62047014730440220189baa9b7156326764eee9cf0855400522fbf3898a8fea2326298c46dae5f26f0220372b701003941f047c3902f5e7552bac022a6712da7739e6e4976d5205d729a001473044022076b0a84773d9604b05f12c36d05b580583bf62aac8b0d64cb99a873c588aab0e02206b3a6d4e6d83abb7a8bcfe36030bb0ca3219ed2efe3af6e61dcea9aabfe70e5c0147304402201caf9833d03c99522a8a7dc31d2dc16ead5bc902e22f6e7f4dc32db4c8fe4a5902204f9a04673537c87451d595fb9210a4d3deafff15aa93beea68f525c494d0762b01ad542103974f49bffe0d5276fb04c1972372f9af676d71cdfe5c3b4192edb13d7a22d77b2102510e29d51e9a4268e6a5253c1fbd8144857945b82acb0accfc235cc7ca36da11210201a819bf549c253c4397bdd1535374de39e9bc278f637afdc27642d52cf7913921039e3aa9ea182ccdaff2d8d150010b27cc4765c1d55ce674e52631af7376354d622103ee980e6334142342fcd9e6facfecfa139981e2276584c91d6a9739d533ac99fc55ae0600473044022032ef270d05aaf28c5a1bc0bb64179ffbd952ddacdf86c1d6f99191266c9cc36f02206ec1cda5ff29ea4379ffcf603952def31f5f3123c54aac6155139f8644eaa88801473044022062500e88357a520a2b60e61ecd935c2b2dfa4312b8d333d4dbfe38bbdd2fe83f02204316a8cabc05549bb64cffaf78e5e3c1a04a97ff0337b94c54d99932908aa80801473044022036d2a14f234e0b9ee5944b3a80c1d54dac16a190517caf8185b76e798b0324c002207c33d2ea8c1ece8e624e6d47e2cd85c0086b07d502eb29615bc863886b9d58040147304402201db4efab99e60e288b861687c762bd6d82ebb411c77f8d4c02622d6951b7e60a02200d52c5667d1168114dc7ed1478037327b1d0bd6011ab60c40426d3407816949b01ad542103974f49bffe0d5276fb04c1972372f9af676d71cdfe5c3b4192edb13d7a22d77b2102510e29d51e9a4268e6a5253c1fbd8144857945b82acb0accfc235cc7ca36da11210201a819bf549c253c4397bdd1535374de39e9bc278f637afdc27642d52cf7913921039e3aa9ea182ccdaff2d8d150010b27cc4765c1d55ce674e52631af7376354d622103ee980e6334142342fcd9e6facfecfa139981e2276584c91d6a9739d533ac99fc55ae06004730440220341e1ca48244eb97f1aecded7b04e1bbdad5a12ee1c954ad07e3a6b0fd11e7700220123e49f3f26be7cab166260fea51c596b0098c3cfa2b5d97f2d6dff50d2c0c550147304402207c39f31848a84853dd5475f98f961fd0b16fa6b0e55090eb78756f1df77174d202204304fdb4de86a7ceffe904a02b36c03f206760d4a53b4f6ffbe7c9338a8ad3e20147304402200349d7265dd40737b59a8472a7168e72c39246ae082c5ed836fb7faaccf57d6e022078cbf66014dfbe9124a1c1622297cf0ab35ff35cab6432d321359b92a05cc35c014730440220162f2da5c12acda5ec3a41820ed1f3209c2b75934c153336907bad7785a6c2e402203101da60392741cb915021555c835c2d341a80578e1737db351dd514750774f301ad542103974f49bffe0d5276fb04c1972372f9af676d71cdfe5c3b4192edb13d7a22d77b2102510e29d51e9a4268e6a5253c1fbd8144857945b82acb0accfc235cc7ca36da11210201a819bf549c253c4397bdd1535374de39e9bc278f637afdc27642d52cf7913921039e3aa9ea182ccdaff2d8d150010b27cc4765c1d55ce674e52631af7376354d622103ee980e6334142342fcd9e6facfecfa139981e2276584c91d6a9739d533ac99fc55ae06004730440220522513cca0c3ff5225f64934a232ee3809696899c2a23f8d7c09318b0f7169340220418186f2826600d328ff5bfb79eadad7c454f641b425b63919e3aa4f9511e3300147304402200ddd83191734b03cf3edb79db4b59e76d123189a4b800fc02e8fba77ebecd9fc022014fb8bad3b87a6c52bf121744788cd7da746e3792b4b2ecee9e3b73b735a0b8f0147304402202bac17b3c72cb892e411cc6090cfe1e26c9dfa5fe4f20ea4dd6002ed3f6c42b002206c9eeed07deb7ca797403637ea57167cf82bad7db17e5647c81e1689baf0b8d00147304402200dbfd121c1b8545318202c1dc3b3675b76efd8a152fb02825e3b656e68955a2202203e060ca439ad0f2be8be44e4294622b1abd363629dc1e1c9df4bcbbceb28ca5c01ad542103974f49bffe0d5276fb04c1972372f9af676d71cdfe5c3b4192edb13d7a22d77b2102510e29d51e9a4268e6a5253c1fbd8144857945b82acb0accfc235cc7ca36da11210201a819bf549c253c4397bdd1535374de39e9bc278f637afdc27642d52cf7913921039e3aa9ea182ccdaff2d8d150010b27cc4765c1d55ce674e52631af7376354d622103ee980e6334142342fcd9e6facfecfa139981e2276584c91d6a9739d533ac99fc55ae060047304402207c7f563c238933cd1a7436cb947bc739ce2c40643df7da094b788d48ce579e7202207082759ded78c9c38ccd686a50b7fca2b12c33c25c9d38e6aa7752e5c7bd89b60147304402206c4767cf664de3cbcdcf9803c9b447288bac1d407b3455c3cfc627e31a732e1102206f13564f5a6aede0671602ab8e0af6307968701444dbaccf3904a4b9eafe43160147304402205904b78bb8d831761a9b072e6739f2c700b0fa1c3ccba34c31d21fdd2631b3d10220693fd3708d1b7c83023023c74ad2e2ade1ffc82b7d6b6f5dd8bb2665136584d50147304402204064aa8e81bbd2443c77ee5e7cd4feb04ba0f401cb0dcbb199d5b676cb077bdc02202fc52eef470a769379a4c54eb2f1e642c5d88730cbdee5da3d34adca1964d43501ad542103974f49bffe0d5276fb04c1972372f9af676d71cdfe5c3b4192edb13d7a22d77b2102510e29d51e9a4268e6a5253c1fbd8144857945b82acb0accfc235cc7ca36da11210201a819bf549c253c4397bdd1535374de39e9bc278f637afdc27642d52cf7913921039e3aa9ea182ccdaff2d8d150010b27cc4765c1d55ce674e52631af7376354d622103ee980e6334142342fcd9e6facfecfa139981e2276584c91d6a9739d533ac99fc55ae06004730440220729d7c3a2a2c92ed6603a0cd34d75e13b5352dd12f1c3adb066d7ff470f937bd02200de11e8f0c491e4a0fed859946acec6af92ce312b840597d6fc025da3de9428301473044022001dd191eceefa1d43f0f32d29a57bef63f4d90cb58c625989920df7a48203d930220447690c8fd15ceae9ef7c86ec5834b3507c0e03ed13a759fa80f992a914e2a3f0147304402201b25c1cd6608a488bf46f19beba7ee9e9bc1736cc4a5212a2c88832524005f5702206ffe9b5e72908044dbe94c2d791ebf1a66322a4689ec23bbf28c9443ffe6dafe014730440220210e60b36e439b85b2a1a65ad109d4c3c442ef479e43d82f0f340618e73e69df02207b5251b81cc7a9565c34d7525379ad6256d920d63bd0ee81d8f23d4a2adc57ac01ad542103974f49bffe0d5276fb04c1972372f9af676d71cdfe5c3b4192edb13d7a22d77b2102510e29d51e9a4268e6a5253c1fbd8144857945b82acb0accfc235cc7ca36da11210201a819bf549c253c4397bdd1535374de39e9bc278f637afdc27642d52cf7913921039e3aa9ea182ccdaff2d8d150010b27cc4765c1d55ce674e52631af7376354d622103ee980e6334142342fcd9e6facfecfa139981e2276584c91d6a9739d533ac99fc55ae060047304402200bf685bf2fd84fc44d3caec18e295de941f43391f03971c59987c920c052fb1902205142955c9ae36ac1d799f0d799ee1fa0c17890420e473276070641c93b7c62cf01473044022058f6c0c1552145b9342bac021b7cd258aad9a029a5f23b3e7365af3d9d7c805002200c4bb3ba5621ff7e5baf0bacc145a6e07e7562d8337a5b59eea2b9431318592c014730440220301206a59297e15db90418fcb8a0f6b073f634f0fbc493e7f25156448e82d11002206fce236e0f32a613f1f0891e4ba04f527708046d7bee91eae9e2a6104b9cbeae01473044022070ed6ed8676567dabbbe8f5f417a97649368cd6032149f8ce9ae1fd7afbad24a02204433207051a6d12c33636e094c03c10e30d7a11e54cc34a4ccbc48c83447e48901ad542103974f49bffe0d5276fb04c1972372f9af676d71cdfe5c3b4192edb13d7a22d77b2102510e29d51e9a4268e6a5253c1fbd8144857945b82acb0accfc235cc7ca36da11210201a819bf549c253c4397bdd1535374de39e9bc278f637afdc27642d52cf7913921039e3aa9ea182ccdaff2d8d150010b27cc4765c1d55ce674e52631af7376354d622103ee980e6334142342fcd9e6facfecfa139981e2276584c91d6a9739d533ac99fc55ae060047304402202dcb5d1c9554a3f800c621b3c171617f47fc64b93560c453f8d4c2000d85e3480220209c23936d8ff6d7612611088adb70be99abe744e5e8646309645378471cbf2401473044022005b5b38a0e400d46f6b8595dea6d706f9aea7840189130bf5f44ac08a147b7cd0220612a752eb8d3c7dd79234b384c12aeec211915f3b1322a866a40fa1c243ccc7a0147304402200b497b0e030e084c3a8ed4db0d5e98ef6be59b63e12b34086a0969b1073028ff0220220a891059ead777659c38b16f710f2d2a6b15ec430836bf76b1d1305e535d4b0147304402203c2c87f02ed021901f746b05cd0111279be481e4c2c3ef97ba709e7460e78fd20220540385625f4b7f5869048aeb33708ed04ed6f21af84a09cce7bf2dc76a54a1b701ad542103974f49bffe0d5276fb04c1972372f9af676d71cdfe5c3b4192edb13d7a22d77b2102510e29d51e9a4268e6a5253c1fbd8144857945b82acb0accfc235cc7ca36da11210201a819bf549c253c4397bdd1535374de39e9bc278f637afdc27642d52cf7913921039e3aa9ea182ccdaff2d8d150010b27cc4765c1d55ce674e52631af7376354d622103ee980e6334142342fcd9e6facfecfa139981e2276584c91d6a9739d533ac99fc55ae060047304402200ee03c52fc1a67a44724be3575f37f4addf1d183cf4c321b7a87c72d930ac5d3022032cee1024e38e4c7936b3d0d67eab31cd0dac660ecee32be96d6491e091cdab6014730440220081749ff93afdaf0deaed41a7d9e11f340ace71b557acc5f486eb30be4e08f3702200bb163ea81ac35ac2fd03a5ff333d412b655b8dc261a4099b55217864b7490ab0147304402207677095b8744c59c4269d78457151e49a6fa6dccde3a87224b7fcd5c80d1a8660220795447b84f6d57d3471ebc22d6ba69efa5c8a41191bcd6f9515402a0e66dc5420147304402204ba5407c257252abe7ec31ec77a49d8de3db8d58d443fd5e34e3f590ba448ab60220513f0bfc4803220aa39be8824365391725492bc99c46355474ba8f5c9c77e0b201ad542103974f49bffe0d5276fb04c1972372f9af676d71cdfe5c3b4192edb13d7a22d77b2102510e29d51e9a4268e6a5253c1fbd8144857945b82acb0accfc235cc7ca36da11210201a819bf549c253c4397bdd1535374de39e9bc278f637afdc27642d52cf7913921039e3aa9ea182ccdaff2d8d150010b27cc4765c1d55ce674e52631af7376354d622103ee980e6334142342fcd9e6facfecfa139981e2276584c91d6a9739d533ac99fc55ae0600473044022062f01265ff043bbe25a749dc757c99aaefe9c47cf8a71dad80811f8dc8b4766a02201e54448243824090b793fb2176b613f2c14e92fdb464d82aec19e28f02a7d0ac0147304402204d9f72d2b464ca90d56c5a08f50d16bcddde8b9670775f68f4653bdb6ce804200220508c50f627625dc01044581d75f49920d6da60d16485494a8d3800d47718b1a30147304402206e5b8b791d3e37b1b32fb084729cb51696e11777ef17110b5567935d020ac745022023bde8093875e1f37638ef78d861b4bcb60a0876547502a9df2894bae310d19a01473044022019138101433c6805771f0401ab14c357d70c5d0347bf34283aba56fe2042c72702206dc21b4019af58f017f4d91ce2a5948ef9412de8688b673ec5b0160cc4e3df2d01ad542103974f49bffe0d5276fb04c1972372f9af676d71cdfe5c3b4192edb13d7a22d77b2102510e29d51e9a4268e6a5253c1fbd8144857945b82acb0accfc235cc7ca36da11210201a819bf549c253c4397bdd1535374de39e9bc278f637afdc27642d52cf7913921039e3aa9ea182ccdaff2d8d150010b27cc4765c1d55ce674e52631af7376354d622103ee980e6334142342fcd9e6facfecfa139981e2276584c91d6a9739d533ac99fc55ae06004730440220310804ffe3e3c6f646949f959734deed4874eccefc6436306f6d626b1abfad6402201e7d132cd0c056950126ec0e9cd441a43258b03eff3386e75b244963fc9385f201473044022016c893fc293e23ce48ed5e2cbbe5e20df92d026b08c04ceded4a073a2ff9d6e602203eb7a9e7aef14a9e3517546a03e169743978065259fac90cd361ad40ca891099014730440220790ea2e86cbf757a0b521a4ade42f63c261a1d60326317a3efdb8c9d21171fea0220769baf1647b74dd2cbbb35c8ef460153cbb0a913337d246987a710b55efe08e6014730440220795a1580bf6660410dac078b5de7bf842aeb7096c70a2e29f96168b069d64d9b0220634f770e012f301be600629de0229acc228b1d11efcc900d053a7e143ba7947501ad542103974f49bffe0d5276fb04c1972372f9af676d71cdfe5c3b4192edb13d7a22d77b2102510e29d51e9a4268e6a5253c1fbd8144857945b82acb0accfc235cc7ca36da11210201a819bf549c253c4397bdd1535374de39e9bc278f637afdc27642d52cf7913921039e3aa9ea182ccdaff2d8d150010b27cc4765c1d55ce674e52631af7376354d622103ee980e6334142342fcd9e6facfecfa139981e2276584c91d6a9739d533ac99fc55ae06004730440220320efd1ea327311cc83ed498d2407f598872f8da127f0447c1531ed864405775022001884e7ffc829f7d81254bf468636590c75ae65c04539e2cd63f0c650d8ed028014730440220782d157941635b8c63fcf83a76d371d02657ae73b54d40363b5f8e77a74a456d02200a259156aa7cd82930ca2759e2aa1d0913e9df8caca45d466447737863c79e9b0147304402202ea2ac6ddf0ae0f6d4b07b7d00fa2b19fceab912985309560e77ddd3475abd2102207b76096670b001ab8f7036c2962f88589ea6121ec4e14d08359c69d0664f3b5a014730440220658505b2b5e978a8f55c9ef0df8596c0d0cffc523f249d6715cc12baf58c9cd2022066e5b46ec446e05aecb8db7682d7b29b7918bfbd87ab0d0c5bc38f42cff59d4201ad542103974f49bffe0d5276fb04c1972372f9af676d71cdfe5c3b4192edb13d7a22d77b2102510e29d51e9a4268e6a5253c1fbd8144857945b82acb0accfc235cc7ca36da11210201a819bf549c253c4397bdd1535374de39e9bc278f637afdc27642d52cf7913921039e3aa9ea182ccdaff2d8d150010b27cc4765c1d55ce674e52631af7376354d622103ee980e6334142342fcd9e6facfecfa139981e2276584c91d6a9739d533ac99fc55ae06004730440220401035e995908c72811354a55464a076dc3062b7ffc5548b5507afbdf12c523802207bfde36a01bc96c5f90487909b6b63b9426531528c9a90dff68f4a55de8bcf6c0147304402206b70ab68ba9b1634f041a981bbcaef6d3c57884ce08d65303adeaab2c2274e6302203d5ad58d6bf365a39dad04dab1059a02919b70bddf1b6b3f7c4902f7daa89ca4014730440220640d72b2868ee9a2883bd84cadc4243c2f8a5a30b5ee303943abb84e7e572dda02206d285a1c6e34d4b87b4aefbaefc05ed0612b4523f70354652bd232fbc0ad0ef40147304402205dc84b14baff101bd9fdb9a4e7e58c6e202b7668f8fd40274adf528cec92eec402205dd7e12b2a867d619f0174e0cb6c6fe752e69f881106d86cce4c2dc744b7a8eb01ad542103974f49bffe0d5276fb04c1972372f9af676d71cdfe5c3b4192edb13d7a22d77b2102510e29d51e9a4268e6a5253c1fbd8144857945b82acb0accfc235cc7ca36da11210201a819bf549c253c4397bdd1535374de39e9bc278f637afdc27642d52cf7913921039e3aa9ea182ccdaff2d8d150010b27cc4765c1d55ce674e52631af7376354d622103ee980e6334142342fcd9e6facfecfa139981e2276584c91d6a9739d533ac99fc55ae060047304402205c8b7d148f329e38408c723b4c3b649ce076dae16fb4d168c9c31e47626af38002201014be05b5483e7984edec4cff0d8c873aa264185c55ce0a04cbb618ba32883901473044022051bb66657b70e86628c309a3aaaab2ba83349d8a40dcb440eb2fde0c9d5ec29f022047cc6675f9b6f50633ee998543b303d3c6e6d25970846340d8e5b6ae171c489f0147304402203a6bfb188347d1558e2efc5ba7671a2768ad1ade8563fe4deb329114567169a802207dbab5210551c2187d34976881f6cb2aae013c5e82411bdcb330e4440d463c140147304402207c44ce1b3e10919de0f61f2cc6cc45fdbcbdf00c5d2e747ecdc852a99ed3c74a02204aa374b609c81b214de573f20212fc571606044357425f0a09cc651abcdeaace01ad542103974f49bffe0d5276fb04c1972372f9af676d71cdfe5c3b4192edb13d7a22d77b2102510e29d51e9a4268e6a5253c1fbd8144857945b82acb0accfc235cc7ca36da11210201a819bf549c253c4397bdd1535374de39e9bc278f637afdc27642d52cf7913921039e3aa9ea182ccdaff2d8d150010b27cc4765c1d55ce674e52631af7376354d622103ee980e6334142342fcd9e6facfecfa139981e2276584c91d6a9739d533ac99fc55ae060047304402203577ab6c0f7fef55790a21fe7cb85ac89fba8452a2c59dfb8ff8cb6e9afaf3e702202eef3772f0743d50d45bb78ce0f52ccaf3233b6faf976ffd953ce16259739ef101473044022074c8f1cd22d78499695608b503c8f5f7edac48a7326432065931e9235116667b0220730e6f76c2d7cc94a8968349451db208b243050cc11d66d3fddfea0c302de0510147304402206a7ccedd071f321a6baca582f8f20a4fb92e66935387b8484507ac1e3918d5af02204bcf5113a8cc0697ace3147db35f78d239f5d3e98c7d1bcdc18be72576d5b0eb01473044022027143e76892feb5d1b5905e31ce04b97b75d80f121a3c25ebff0a29227bc68d3022028865f36d43c032595b1000632303d3baf1b54f26c07cf768c2320bfe1ff710501ad542103974f49bffe0d5276fb04c1972372f9af676d71cdfe5c3b4192edb13d7a22d77b2102510e29d51e9a4268e6a5253c1fbd8144857945b82acb0accfc235cc7ca36da11210201a819bf549c253c4397bdd1535374de39e9bc278f637afdc27642d52cf7913921039e3aa9ea182ccdaff2d8d150010b27cc4765c1d55ce674e52631af7376354d622103ee980e6334142342fcd9e6facfecfa139981e2276584c91d6a9739d533ac99fc55ae060047304402203f7b869031b07fff33ec782d616c1cf22be70f2006bb1b406dba625610f7601602202e21c8347d465c7895d2394f7de8fd63a54f0f35f1d5c2f757be9a792db99c590147304402207dcf8d088d7d07367089b0b5e8269d553f65ed224b549eec644ad07b5b267f150220534f0de2ff6aaac17bae62e44e45ccf2d561c6d57d1817525913a173938bf72b0147304402200a50ac0836d019461b6a2cf4e532a73856fd0577888e707f5afb5fefb646fcef022008a98ce580a60cee412a8126d113eb468c9042e4ba4945988cd20d1d1f44fedf014730440220195725b7c269772dd19e06846e5d6ba5026bcf8e8fb59027e8fa8c222d0717c102204bf52047181024551b5993a9425c84f7b1b99bf2483d061e373f7bf7fda1049101ad542103974f49bffe0d5276fb04c1972372f9af676d71cdfe5c3b4192edb13d7a22d77b2102510e29d51e9a4268e6a5253c1fbd8144857945b82acb0accfc235cc7ca36da11210201a819bf549c253c4397bdd1535374de39e9bc278f637afdc27642d52cf7913921039e3aa9ea182ccdaff2d8d150010b27cc4765c1d55ce674e52631af7376354d622103ee980e6334142342fcd9e6facfecfa139981e2276584c91d6a9739d533ac99fc55ae06004730440220445d3d89d1fc3b7236e467bf938838bca84a263a7d62a6a78552337c5cc62038022071822284ec45d064de439c268406396fe395614f0d3f02851254fcdbad679f100147304402202ce48fa9bc6b4b54d7b8dc0d2aa46c46a51665ba52ffd27936801f4b2276cda302205a4711c8c01fc4bc01790bdc90f9901c7fd03819af9276151d7c0c2c981c8f41014730440220423588c14b95a3f19aab2d9230b3527e5954b87b29ec15ee61b1a2ab4d63419e02204c9bd7823afb2890a80c01c4ed12e9e8642af7b943d4355eb9a173c531b16c1f0147304402201628ef284db0ae5ed10749b94a4c19015cdc9d44e535167a115d890d6fc55e8d02204e5bc9684b2fd06925dc84e99ea75da9a0976b8f05fa160a076be25a2768de3501ad542103974f49bffe0d5276fb04c1972372f9af676d71cdfe5c3b4192edb13d7a22d77b2102510e29d51e9a4268e6a5253c1fbd8144857945b82acb0accfc235cc7ca36da11210201a819bf549c253c4397bdd1535374de39e9bc278f637afdc27642d52cf7913921039e3aa9ea182ccdaff2d8d150010b27cc4765c1d55ce674e52631af7376354d622103ee980e6334142342fcd9e6facfecfa139981e2276584c91d6a9739d533ac99fc55ae060047304402204d6be5b792667f4ef92bcf2f3dc7a7c753533044919be30c862696d7c0572b6902203e10507b9ef5c599b27a86dfef1434a7ce9889b3f8f773e0ac3594b292d6ad56014730440220427d9bbe40cc9be631858c79dbcd3dd2ab7df4583014baccbfe0faa628994ef60220099397e5579c5e93b71c47946a96d9227589f31c9495b8a5eb304e9d3ce3dccd01473044022056b2812a6c839fa41bf6b10151c59e49cb665e1087c01d943d8b1d13c81f5d1802204137cc6e2df045d83b861681b337bf762759cb5162ec1e888463bbc67325ea470147304402206a179d08718f21a250395d675487f47e8bc5f081d17ed8ce2b4fd791cb7371f1022048c51c1bb6442a2d0984e66ec7f5638a7b745d840b80c96563df3f3868fed03301ad542103974f49bffe0d5276fb04c1972372f9af676d71cdfe5c3b4192edb13d7a22d77b2102510e29d51e9a4268e6a5253c1fbd8144857945b82acb0accfc235cc7ca36da11210201a819bf549c253c4397bdd1535374de39e9bc278f637afdc27642d52cf7913921039e3aa9ea182ccdaff2d8d150010b27cc4765c1d55ce674e52631af7376354d622103ee980e6334142342fcd9e6facfecfa139981e2276584c91d6a9739d533ac99fc55ae060047304402200afa09e07d0efa87fb7041864a6c2d546229ffed9e164648dc23cce1d0ffae4a02206acd69e23627e50ddb59607c44515f0fd32f574304924e2f7e339de2d5b3e253014730440220668da976b3b4b1c7fc07d3e660296f0b139c1898c0dd6fefa252c849b86eb8ad02203b05f03af044b057eb32f11b43960b5e58a66a6ecf625dab74c6db72ec081f7f0147304402206861104c5cc7f162be6566c59bfedd0bf2ff1b5ca01deef8eab7d48aeb0c0cbf022071be913695f3fb07144658298b52cf6da484c169e873ad6706d24cbfc5dbbedc0147304402203ddb7490ed7c3ab798c1f45dde69977578f65494740b3c302a684918048d90ea022056aea75c4f539338ac5d3fbec3eacaf640f8e522b04f2df96492d17797c5685d01ad542103974f49bffe0d5276fb04c1972372f9af676d71cdfe5c3b4192edb13d7a22d77b2102510e29d51e9a4268e6a5253c1fbd8144857945b82acb0accfc235cc7ca36da11210201a819bf549c253c4397bdd1535374de39e9bc278f637afdc27642d52cf7913921039e3aa9ea182ccdaff2d8d150010b27cc4765c1d55ce674e52631af7376354d622103ee980e6334142342fcd9e6facfecfa139981e2276584c91d6a9739d533ac99fc55ae0600473044022014935323818897afbefb265a6aebe76b8d249b0221147c6d3bbf9cf9ca7f927302201459eaf3c028b3d1facc7856262be3f9db27a38bf4c9cbe848e6665bf9d31df801473044022066942aaab171ac8b063c4a5e5ff804217f11576f4c361ba5c92ea2dace5f0aff02206c0766302c34b77e6e836fa6689755a08310b7aa38adb530e1cadcd9f2079cdf01473044022004423b4fd0d446522e0e2f57c8cbca615b29c51acd0f14f2b16a5e026c14271f02203d84b011927819960a43642de92b1c8f0638d011285b6241f77a186d3bc554c60147304402206fc8e07f5cf2c2e6c2b50a68884db69da43b6ab809c2885981275e07e8cc93aa022042cca1dbe4331a65cbcf837005d0fe29bf6051fdedb9ae8583b94b3d0ed0f7ee01ad542103974f49bffe0d5276fb04c1972372f9af676d71cdfe5c3b4192edb13d7a22d77b2102510e29d51e9a4268e6a5253c1fbd8144857945b82acb0accfc235cc7ca36da11210201a819bf549c253c4397bdd1535374de39e9bc278f637afdc27642d52cf7913921039e3aa9ea182ccdaff2d8d150010b27cc4765c1d55ce674e52631af7376354d622103ee980e6334142342fcd9e6facfecfa139981e2276584c91d6a9739d533ac99fc55ae06004730440220496557115f8e567d0a31054564ada9f73d542744a8e749c66520ddeda2c514390220269a8b78db0847e7ef35056fdb10dde5eb76a1fe41b6f4eb87825dce78b39bf30147304402201c8af06fdc96a01df80977c0865db7049f82ac8057cdeb212ae41e8f477a353a022074804bc6095dc729c5ff3fac3ed23039bab24077853c4b55476ecfdbe6721d5201473044022003e06784e0133dc6a9fd896d43ceff63e0f5ed250698e26ccbf05ff3eddc562b0220647807d1e510fd07f8e8e3135baa8e83b2ee1efbdad9bf2f646230b44fea6af3014730440220110b44430b094128ab37af272e276e04653c949554a40b5577482ad2cd7edd23022007687cc5b6cb832c83867a525f9638be546d8a451a0a86d1d46cb3d528ad366d01ad542103974f49bffe0d5276fb04c1972372f9af676d71cdfe5c3b4192edb13d7a22d77b2102510e29d51e9a4268e6a5253c1fbd8144857945b82acb0accfc235cc7ca36da11210201a819bf549c253c4397bdd1535374de39e9bc278f637afdc27642d52cf7913921039e3aa9ea182ccdaff2d8d150010b27cc4765c1d55ce674e52631af7376354d622103ee980e6334142342fcd9e6facfecfa139981e2276584c91d6a9739d533ac99fc55ae060047304402205e94a7e115864e06a54ab3acc7f5622f6908dad86967bdb2ee71e0a08e0fa3b3022058a0cbd9eeea52256ee38859650cb95d560c8506e0e4f2b1f5a0e314530a1ac10147304402203a79b2411ebf0c02e1625ff5f3610ad6f036557703a3274f9d1aea6d593edfa10220120df88db2806777862879fd6679df0e2d6ad4a28615138c68633456fe1441f60147304402202b55a8404f292c3f43d83fb9c45115e0a1fd165d077d53ea48ddf35b7d2eeab602204d269470671749688986cbca65d065bbd6e1f525e0feeb420298c80029c8d4f401473044022028996b7da50179ab0f497a5e970c82be1a6d4e36aa0b25a2d538c06f5e6c137e022040ec1b06aa14e328d6167d55c8da7c1aa2b5311661a4ea8df121e5b673fa9a4101ad542103974f49bffe0d5276fb04c1972372f9af676d71cdfe5c3b4192edb13d7a22d77b2102510e29d51e9a4268e6a5253c1fbd8144857945b82acb0accfc235cc7ca36da11210201a819bf549c253c4397bdd1535374de39e9bc278f637afdc27642d52cf7913921039e3aa9ea182ccdaff2d8d150010b27cc4765c1d55ce674e52631af7376354d622103ee980e6334142342fcd9e6facfecfa139981e2276584c91d6a9739d533ac99fc55ae060047304402202c1dd2ca3bc57f76f9e75228f72cf161885808d20d6c4666b54db3df28f33cdd02203262799779f585837b51aaafd086c8f147180895c9c448d6cb1b99311483e7500147304402203f56ee6036de8582783200f708a039001ab6040b07d9807b32ec10a7f4800ffe02202d70bbae318b000f27a2011e5f1118dd721d99848403b2b4e2ccb798b91b0aa00147304402201897c2e154d6d903a2c44b42ab971e840a49bfaf6d933ac1b4661631ac50bd0702207b4b1ffb29fdc73f2b24a029e468bea9e37855fe765031d195e584852fb92f3d01473044022041075bd90068f809044c07d65e3e00d766bf2936de351c85d2d46d9f3860adad02200e89757ebf48eff9780ed5b959265bcee0e2f2d01e5c1e33cf5c71310c7dd85301ad542103974f49bffe0d5276fb04c1972372f9af676d71cdfe5c3b4192edb13d7a22d77b2102510e29d51e9a4268e6a5253c1fbd8144857945b82acb0accfc235cc7ca36da11210201a819bf549c253c4397bdd1535374de39e9bc278f637afdc27642d52cf7913921039e3aa9ea182ccdaff2d8d150010b27cc4765c1d55ce674e52631af7376354d622103ee980e6334142342fcd9e6facfecfa139981e2276584c91d6a9739d533ac99fc55ae0600473044022031c5a55fe1f360fb50c8c7d4c300b73d5456768d8e2e1a9e5f0b7463f9bf57fb02202e3ee59e1ca6c676838a1577b6df1662a8ceed44db3b9812b21e96e5cf2135740147304402203519363e72c5a0d5ed4f6e7674fa9eb7df4f8e798838629af0b24ab593f51d2002202100dc6ca817d6c25bc52bf32e81acf4195c4d92123b8f205c73f4f749f42384014730440220469afc4d2659cf31c147699f4f894efd364f902324d64e2e81132249cfe89e6902200e9ee3694e407798032f20c0e3f23e732a0cf452d1987f691bb405878c7cd032014730440220027188911c32909d86e1c2afff9a94900d2806c03355497b132b25fa8a210880022038c4c36c500dd9e1da5cc3dc9a7cb2ad9905a66fb2ecab3b8d71047e28aa457c01ad542103974f49bffe0d5276fb04c1972372f9af676d71cdfe5c3b4192edb13d7a22d77b2102510e29d51e9a4268e6a5253c1fbd8144857945b82acb0accfc235cc7ca36da11210201a819bf549c253c4397bdd1535374de39e9bc278f637afdc27642d52cf7913921039e3aa9ea182ccdaff2d8d150010b27cc4765c1d55ce674e52631af7376354d622103ee980e6334142342fcd9e6facfecfa139981e2276584c91d6a9739d533ac99fc55ae060047304402207a4700117eec4715abb553de0850109021ff9f9719dd863faa4b8417957f8f5f02205f86a7f098dfdfe4696b9ec3b0e3e16e5eb5c6057db02b4693456cddcfbd39e701473044022061013500ea068e73df6c57c368b1b8ae1a2e7fef576d2da3911f342a68bcc82f02202aedc060f6d2f4ee1f50a38ec7fbbb691f38be9990296d789e3cdd87f1ab022601473044022026a1aae8a21c0d8264ff5a3e72ed884b8e435c77cd8417550015c014c7293b7202202450716abd8b7770d8b9b1eb22ef2158e00435e9fdc1715aa8fcf46b030214ee014730440220409d7688b6a701107618796fce9d1831c1a835152ed5705288d24b5b342c4f8602203fb904f3dbf04e3319abf3b1ed79f58ea2f474dcf6b8097edcf8e673c57bd29d01ad542103974f49bffe0d5276fb04c1972372f9af676d71cdfe5c3b4192edb13d7a22d77b2102510e29d51e9a4268e6a5253c1fbd8144857945b82acb0accfc235cc7ca36da11210201a819bf549c253c4397bdd1535374de39e9bc278f637afdc27642d52cf7913921039e3aa9ea182ccdaff2d8d150010b27cc4765c1d55ce674e52631af7376354d622103ee980e6334142342fcd9e6facfecfa139981e2276584c91d6a9739d533ac99fc55ae060047304402206ccbabe19e7038e444d7104b117c755118dd6d98a1732d7f4a3973d238102a1c02201b05cfcb7bfff576372807511fe844eb60f598578455c865a320efad03df34e6014730440220179486411bfc134c9b90d6f7f028b3a6a30d5965d32730161fe38f56c1f97cdc02201fee01a1fbba7bd5760db8b354f218061594f26527ad208c987877a1a270c20201473044022024b4c6ec9f434dde29a488e6d391e8b2f30bbccad8362dc6ad4d860cfe67b829022068b09b4386d8d6c88d5645a9f077df7473f3627ecc1d257adba834c268ecc0df014730440220776d99aab713b601a5592f8066a027ac24aed42e83b73ec833606561cbebb739022020ab6aaf0f98153d173394a4a3f15c02115df9eb1a736e6e405a129c0cd9c52701ad542103974f49bffe0d5276fb04c1972372f9af676d71cdfe5c3b4192edb13d7a22d77b2102510e29d51e9a4268e6a5253c1fbd8144857945b82acb0accfc235cc7ca36da11210201a819bf549c253c4397bdd1535374de39e9bc278f637afdc27642d52cf7913921039e3aa9ea182ccdaff2d8d150010b27cc4765c1d55ce674e52631af7376354d622103ee980e6334142342fcd9e6facfecfa139981e2276584c91d6a9739d533ac99fc55ae0600473044022044cbc6e8f6d50f6b67c63c87b44a8dffbe02fc11ce093ee136709dee85a0bacc02201d088a8af13a605285c9db5637d9b84b777f96e89c5664927a731ce740c04082014730440220391b5b84d6061dd6803f4e281f21c49648fdc55e10e4067ba09f3dde673184b50220220240802bec46d2823ed96e51cb2f67142aa1b0a6d46adcc0d19184a1ef269901473044022045b6fc7378794b4a711a64e1134def87db3db88cb85a529ebe420f53ddd0f89b02205d1f96ac712b2eb06ef7b1061b9d9372f4a8bb39da805891c95c9568e776b6460147304402206947118fe72f33408fdad56c57786e430b24bd42ae008a8e9ce48812bbec3442022064ae34e8bbc4e20ea73d2ab9cf520094a43fe16de8964c5788f9d94ad7eb73d001ad542103974f49bffe0d5276fb04c1972372f9af676d71cdfe5c3b4192edb13d7a22d77b2102510e29d51e9a4268e6a5253c1fbd8144857945b82acb0accfc235cc7ca36da11210201a819bf549c253c4397bdd1535374de39e9bc278f637afdc27642d52cf7913921039e3aa9ea182ccdaff2d8d150010b27cc4765c1d55ce674e52631af7376354d622103ee980e6334142342fcd9e6facfecfa139981e2276584c91d6a9739d533ac99fc55ae060047304402206451fb9c96de7eaddd5726219abad66fb510cd8826f67b9f04ab5162afa3130802201d0d851a3daca0c707a4b0cd2f7da27cbf8501fb3dfb67b28414ae29ad44b491014730440220712cbace84d7f0318bfe0de12beaf1d5f04b0802584500aa61b7d973c8722f2402205bc8f4f82dfa1154f8c021338ff1d326b37ace8eea32373bd23e8ef9d347ab9401473044022007f9eb10256e75c20ebe62008be2abb8a3afa2efbc7ac30e50780b06071163cb0220391a39d8bb1d259fa183fb6b827eb59551f5ae4d644c981e94b8e15a74be09840147304402202f28dcb0e27b3c61f99f721540e760bb02dcdcae61b0fb97d912160516f2b24102205b6ec8d02ece399e365c90eae70c6d9b79c8c41e272598f35cfa50d2a841d82c01ad542103974f49bffe0d5276fb04c1972372f9af676d71cdfe5c3b4192edb13d7a22d77b2102510e29d51e9a4268e6a5253c1fbd8144857945b82acb0accfc235cc7ca36da11210201a819bf549c253c4397bdd1535374de39e9bc278f637afdc27642d52cf7913921039e3aa9ea182ccdaff2d8d150010b27cc4765c1d55ce674e52631af7376354d622103ee980e6334142342fcd9e6facfecfa139981e2276584c91d6a9739d533ac99fc55ae060047304402203c91e79b790f4af859483ae7b719e194cbf01d4b6bf7913cbe16237a6724af4e022061723a7cf3a2f40d864eeece957b04f319a245a354cfcb8064496fa3304274ed01473044022073d5b711d7e145ef9dbe3f6b9123225a2725244da0df9641109f1567f2d3ac6302207a51125f18814a62bbcc50ee8d8b83db8eebe5cdff13f2e591300e09ba0ae9b90147304402202a05245db339bd351bf2f848e43177059fc9994811073c4115394c8b20338c4e0220012c5289689e1a77dc83028e7473287dcfd85ce53276d41d053d9daed314cb630147304402200ea768c6acf94b4d2b3edb4b49bac69713c60b73acc1e152a1d18af9f595adbc02207b7ef4be5915b415109f339b69d815ecd9a9b401f1fb5f4be2e308441dbc789301ad542103974f49bffe0d5276fb04c1972372f9af676d71cdfe5c3b4192edb13d7a22d77b2102510e29d51e9a4268e6a5253c1fbd8144857945b82acb0accfc235cc7ca36da11210201a819bf549c253c4397bdd1535374de39e9bc278f637afdc27642d52cf7913921039e3aa9ea182ccdaff2d8d150010b27cc4765c1d55ce674e52631af7376354d622103ee980e6334142342fcd9e6facfecfa139981e2276584c91d6a9739d533ac99fc55ae0600473044022026a206092e1f8f84fb1989c09909b529a89f35db1715b66833c367aae30c841702201651647d6fe47519dfe37e54bbb68e7b4bb0a76bb95c935e845f522becf48aac0147304402205140f2e94b3ed9c37963a5259c605d21d98012915b555bd30f33e8b98a77f72c02204259d05c18efa8d98242bf5515c8429a73fe3442ccaae0cfadbe32b6946252850147304402202736238b04e99eef7433074ac73c5b77d6b543cfff0ddaed274c7cfaa8d8c06c0220739dbb8e80bb992ee42756b85bf6afe2150b84ca8a97be4bc472efc6db6690e2014730440220757272b033cbf9e52f027f5cd67e959194640d261652ab845da7d04c54952cfb02203078ac884c1cdd7a13f27317a47e00f434fad5e64a85312e90f84c5953f8aed801ad542103974f49bffe0d5276fb04c1972372f9af676d71cdfe5c3b4192edb13d7a22d77b2102510e29d51e9a4268e6a5253c1fbd8144857945b82acb0accfc235cc7ca36da11210201a819bf549c253c4397bdd1535374de39e9bc278f637afdc27642d52cf7913921039e3aa9ea182ccdaff2d8d150010b27cc4765c1d55ce674e52631af7376354d622103ee980e6334142342fcd9e6facfecfa139981e2276584c91d6a9739d533ac99fc55ae060047304402204b3f562b6cdd686ff0765760df91966647e5ae05c1fa6d7067516a1967308f1002202e3440c35b3c1a58f796f07e3ad188f0dfe428fe2fa3bd4c2faff78acf82495a0147304402206c0f4aa6d05f8846fe4957f327b46ab6cd8e535c992d1ac28a87071ea059c32d02204aad72ed46b9385cb6befe779a1b119eed7b7ae917253f45796c47498541328c014730440220124224d84aa9d610f85551846ed758e9a8a92f3a23e551d2892105c99ee4b7130220143e6a03b4689e0b2108d49d788d2c207a1c40ff9e240a132a2169636865755e014730440220357fa4c9b04f3d3f0865ec5dedc72544e79e32eb68336fc4a02bc349819210f502207e0b6c805fc60a0c4bf66e4186e161d9059dadc0da9174451a251e6a892c960b01ad542103974f49bffe0d5276fb04c1972372f9af676d71cdfe5c3b4192edb13d7a22d77b2102510e29d51e9a4268e6a5253c1fbd8144857945b82acb0accfc235cc7ca36da11210201a819bf549c253c4397bdd1535374de39e9bc278f637afdc27642d52cf7913921039e3aa9ea182ccdaff2d8d150010b27cc4765c1d55ce674e52631af7376354d622103ee980e6334142342fcd9e6facfecfa139981e2276584c91d6a9739d533ac99fc55ae060047304402201fda6b9c0d16547fde557b12f5b5865b77525a01e5e1d3d441e25f963df1d3930220481e3dc56935e545d2edcfa3c0892bf260fdfa87a8764fd88e9aedcd89367565014730440220649c7c0d9d4f151454c16b3396149ef0331c389362311e9eb264497726ba40fe0220748f54aae582136ac14ac85bcf1d1cbd479279190b456bce4fe4b809a744fae50147304402203dd25a0144c88f0b9cbb0d8885a4fd215576a9a4edd986ec0eedb237b15b56b9022066c040ceb7f9cfaafe19e53a78651037891dc2859f4866289b25a5d76527bff70147304402204464b08502924ab396af57074796a5f468b7ac969b4a3d1a6d177ae5f5001e9e02203f31886b9e78b8a58cdb9578d377c74f6e0cfd5b62a2f7e42873ab1ee5393cbf01ad542103974f49bffe0d5276fb04c1972372f9af676d71cdfe5c3b4192edb13d7a22d77b2102510e29d51e9a4268e6a5253c1fbd8144857945b82acb0accfc235cc7ca36da11210201a819bf549c253c4397bdd1535374de39e9bc278f637afdc27642d52cf7913921039e3aa9ea182ccdaff2d8d150010b27cc4765c1d55ce674e52631af7376354d622103ee980e6334142342fcd9e6facfecfa139981e2276584c91d6a9739d533ac99fc55ae00000000",
4 | "signatures": null
5 | }
6 |
--------------------------------------------------------------------------------
/data/test-cases/json/create_and_sign_transaction_response_sweep_p2pkh.json:
--------------------------------------------------------------------------------
1 | {
2 | "tx_type": "sweep",
3 | "tx_hex": "0100000001388ee4b67ff518b8b6df2a968e56f955ff2a641c2da83c57af71dc6927f069fe000000006a473044022072051d3b7c82d064e6254cb05e555e1aa3b9dd01c485e5f8dd14a9554cba594f02203584a1df0551b022dfdf0c1c8fd43ce8e613561ff0699bbb400854b676f328d40121021499295873879c280c31fff94845a6153142e76b84a29afc92128d482857ed93ffffffff019c7601000000000017a91449e0ff8beaca290b0461e3160b0df0921d4468988700000000",
4 | "signatures": null
5 | }
6 |
--------------------------------------------------------------------------------
/data/test-cases/json/create_and_sign_transaction_response_sweep_p2wpkh.json:
--------------------------------------------------------------------------------
1 | {
2 | "tx_type": "sweep",
3 | "tx_hex": "01000000000101833a790831abeef4bfed5c058cd0829e63ed7bc2376dcd6fdb17e8e487c3032e0000000000ffffffff01c88804000000000017a91449e0ff8beaca290b0461e3160b0df0921d446898870247304402200834738ce6c085a477e12262ce118bef94deaf1dc853d8ee2bb2fd4627c65b8802207453dcf8e92cbd5c4226f6ced9430e4d55f011e58814cc1e9e149af16d5d4e100121021499295873879c280c31fff94845a6153142e76b84a29afc92128d482857ed9300000000",
4 | "signatures": null
5 | }
6 |
--------------------------------------------------------------------------------
/data/test-cases/json/create_and_sign_transaction_response_sweep_p2wpkh_over_p2sh.json:
--------------------------------------------------------------------------------
1 | {
2 | "tx_type": "sweep",
3 | "tx_hex": "01000000000101ca4e083e5a5d1c5324579b5b4ec631152003d4613d327d3fdabc810ca1b282470000000017160014426c9f352ce2c8f06bd8a90a265ded4b3759c243ffffffff01280203000000000017a91449e0ff8beaca290b0461e3160b0df0921d446898870247304402201f96089d0ffa9bd6652c601ea4b8296bd5cb18f44b341ee07df772b4cffdccbc02203afa9197ea471f6f45e4e9d19392581ae22826e639b1b9844d3dad3ead70d82b0121021499295873879c280c31fff94845a6153142e76b84a29afc92128d482857ed9300000000",
4 | "signatures": null
5 | }
6 |
--------------------------------------------------------------------------------
/data/test-cases/json/create_and_sign_transaction_response_with_blockio_fee_and_expected_unsigned_txid.json:
--------------------------------------------------------------------------------
1 | {
2 | "tx_type": "basic",
3 | "tx_hex": "010000000330063782af1e81eddbca00856ff646a6df8164585ae66c784924572f909a5d5d0000000000ffffffffd4ea031218137a06524a7c13c921a3d271fa97905d378e719927d6fedc48c3ad0000000000fffffffff05c2af854968d1d38b1a2e8b3982c13cb0b5dccebb09f09fb098bacce4dfa9f0100000000ffffffff0344d612000000000017a91410eb388a17a299ed87b8962e25efdb15f3cd86fe87393000000000000022002025e75758225bf2706fdd2dac763f0134fdea3be04a3f15414f9bde188280f72d147bfc000000000017a914108dc42df9d23e581d09a512cd38f58b48bd444d8700000000",
4 | "signatures": [
5 | {
6 | "input_index": 0,
7 | "public_key": "034309721935937713a2dd1c33c5c44a10a30674bebe0542aeb145ce4c9790e742",
8 | "signature": "3044022024d0b5749b7c198f5bc57a61e63dddf9b90b5c98ae53b063b647fb98c8be61090220794354afeaae1447fe9d60fb101990d55855ca90c5ac5290f4cb18aadf2ca65a"
9 | },
10 | {
11 | "input_index": 1,
12 | "public_key": "034309721935937713a2dd1c33c5c44a10a30674bebe0542aeb145ce4c9790e742",
13 | "signature": "3044022074abc51fc7f25405053de3e4f861ff75400a64053fe7ccb93cd15b642a428ea5022043ec5b19cabedfe164e1b05fbedb675c060ab5f6f390132442bf712289d29036"
14 | },
15 | {
16 | "input_index": 2,
17 | "public_key": "034309721935937713a2dd1c33c5c44a10a30674bebe0542aeb145ce4c9790e742",
18 | "signature": "304402201591e9a6fe6dee39da9883ed550d3fbe26f347fd2e1ad492f6a59643059d0b8102207c550357b459852d05577c5069391740267878c420e55e9aca47e1c672680348"
19 | }
20 | ]
21 | }
22 |
--------------------------------------------------------------------------------
/data/test-cases/json/create_and_sign_transaction_response_witness_v1_output.json:
--------------------------------------------------------------------------------
1 | {
2 | "tx_type": "basic",
3 | "tx_hex": "0100000001784ecc9b864ff9b8a97eaacd138fdf193a5d889735702a4f726d2f87631de45f0100000000ffffffff03204e000000000000225120000000c4a5cad46221b2a187905e5266362b99d5e91c6ce24d165dab93e86433204e000000000000220020000000c4a5cad46221b2a187905e5266362b99d5e91c6ce24d165dab93e864339f4c00000000000017a914026b9608a26f40f74644cc60622d1067c3696ac98700000000",
4 | "signatures": [
5 | {
6 | "input_index": 0,
7 | "public_key": "02d2cbf77287c1443759abdd35f239e7da2f52c992258653bc8dd577ae63c78628",
8 | "signature": "304402206d093e2a14e80e01f3c8eefc76d651d01c3dedbd1f0dabf78ace5b352ca660b602202aac312b5b7f0b8c79ad22e75d2523ee39d9c62a35be4db102b5c8f3f296f662"
9 | }
10 | ]
11 | }
12 |
--------------------------------------------------------------------------------
/data/test-cases/json/get_balance_response.json:
--------------------------------------------------------------------------------
1 | {
2 | "status" : "success",
3 | "data" : {
4 | "network" : "LTCTEST",
5 | "available_balance" : "0.2432256",
6 | "pending_received_balance" : "0.0"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/data/test-cases/json/prepare_dtrust_transaction_response_p2sh.json:
--------------------------------------------------------------------------------
1 | {
2 | "status": "success",
3 | "data": {
4 | "network": "LTCTEST",
5 | "tx_type": "dtrust",
6 | "inputs": [
7 | {
8 | "input_index": 0,
9 | "previous_txid": "0a574a50754e5acc410d83acbfa19d1d29837c8326d444c2f1aee4e3c05d9634",
10 | "previous_output_index": 1,
11 | "input_value": "0.00936600",
12 | "spending_address": "QZVSzPeaEJxB9bYuDEL7iWrHSdGbAP3pXV"
13 | }
14 | ],
15 | "outputs": [
16 | {
17 | "output_index": 0,
18 | "output_category": "user-specified",
19 | "output_value": "0.00020000",
20 | "receiving_address": "QcnYiN3t3foHxHv7CnqXrmRoiMkADhapZw"
21 | },
22 | {
23 | "output_index": 1,
24 | "output_category": "change",
25 | "output_value": "0.00904900",
26 | "receiving_address": "QZVSzPeaEJxB9bYuDEL7iWrHSdGbAP3pXV"
27 | }
28 | ],
29 | "input_address_data": [
30 | {
31 | "required_signatures": 4,
32 | "public_keys": [
33 | "02c59aa2350d024c456bb5047df28a5cc73fe2799999ed11035adff1dd24eb035a",
34 | "02510e29d51e9a4268e6a5253c1fbd8144857945b82acb0accfc235cc7ca36da11",
35 | "0201a819bf549c253c4397bdd1535374de39e9bc278f637afdc27642d52cf79139",
36 | "039e3aa9ea182ccdaff2d8d150010b27cc4765c1d55ce674e52631af7376354d62",
37 | "03ee980e6334142342fcd9e6facfecfa139981e2276584c91d6a9739d533ac99fc"
38 | ],
39 | "address": "QZVSzPeaEJxB9bYuDEL7iWrHSdGbAP3pXV",
40 | "address_type": "P2SH"
41 | }
42 | ],
43 | "estimated_tx_size": 585
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/data/test-cases/json/prepare_dtrust_transaction_response_p2wsh_over_p2sh.json:
--------------------------------------------------------------------------------
1 | {
2 | "status": "success",
3 | "data": {
4 | "network": "LTCTEST",
5 | "tx_type": "dtrust",
6 | "inputs": [
7 | {
8 | "input_index": 0,
9 | "previous_txid": "cd85a6b9859d998fdeedc3389359c4b069734f8ce227ea65c2f081020d9c95f3",
10 | "previous_output_index": 0,
11 | "input_value": "0.01234567",
12 | "spending_address": "Qg1QzgjUDkwaHeT7Yznuyu2V1keJieDVAF"
13 | }
14 | ],
15 | "outputs": [
16 | {
17 | "output_index": 0,
18 | "output_category": "user-specified",
19 | "output_value": "0.00020000",
20 | "receiving_address": "QcnYiN3t3foHxHv7CnqXrmRoiMkADhapZw"
21 | },
22 | {
23 | "output_index": 1,
24 | "output_category": "change",
25 | "output_value": "0.01209207",
26 | "receiving_address": "Qg1QzgjUDkwaHeT7Yznuyu2V1keJieDVAF"
27 | }
28 | ],
29 | "input_address_data": [
30 | {
31 | "required_signatures": 4,
32 | "public_keys": [
33 | "02bdecaf3813bfb1d3d9a3a20844fb979a2cdd9664fdf5b8a8eaddaff95df67a03",
34 | "02510e29d51e9a4268e6a5253c1fbd8144857945b82acb0accfc235cc7ca36da11",
35 | "0201a819bf549c253c4397bdd1535374de39e9bc278f637afdc27642d52cf79139",
36 | "039e3aa9ea182ccdaff2d8d150010b27cc4765c1d55ce674e52631af7376354d62",
37 | "03ee980e6334142342fcd9e6facfecfa139981e2276584c91d6a9739d533ac99fc"
38 | ],
39 | "address": "Qg1QzgjUDkwaHeT7Yznuyu2V1keJieDVAF",
40 | "address_type": "P2WSH-over-P2SH"
41 | }
42 | ],
43 | "estimated_tx_size": 268
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/data/test-cases/json/prepare_dtrust_transaction_response_witness_v0.json:
--------------------------------------------------------------------------------
1 | {
2 | "status": "success",
3 | "data": {
4 | "network": "LTCTEST",
5 | "tx_type": "dtrust",
6 | "inputs": [
7 | {
8 | "input_index": 0,
9 | "previous_txid": "027a5e03c4472c79a90ddb10e755082237af2dc75362e91f1c56b597ce495fe8",
10 | "previous_output_index": 0,
11 | "input_value": "0.00020000",
12 | "spending_address": "tltc1qtvscupcwnlsykujp98y0jhf8s4x48mzrr3v8v822ytg6vmprvzaqj8jd0h"
13 | },
14 | {
15 | "input_index": 1,
16 | "previous_txid": "f42e3a0a4e5e3e96b6877cab261b39b578edec6e064d20fdc1ba2e49eac90571",
17 | "previous_output_index": 0,
18 | "input_value": "0.00020000",
19 | "spending_address": "tltc1qtvscupcwnlsykujp98y0jhf8s4x48mzrr3v8v822ytg6vmprvzaqj8jd0h"
20 | }
21 | ],
22 | "outputs": [
23 | {
24 | "output_index": 0,
25 | "output_category": "user-specified",
26 | "output_value": "0.00020000",
27 | "receiving_address": "QcnYiN3t3foHxHv7CnqXrmRoiMkADhapZw"
28 | },
29 | {
30 | "output_index": 1,
31 | "output_category": "change",
32 | "output_value": "0.00011960",
33 | "receiving_address": "tltc1qtvscupcwnlsykujp98y0jhf8s4x48mzrr3v8v822ytg6vmprvzaqj8jd0h"
34 | }
35 | ],
36 | "input_address_data": [
37 | {
38 | "required_signatures": 4,
39 | "public_keys": [
40 | "03a37fd7b94d49db3a3ab57d0b07b1408057e63d76d80a1286f841f04fa58e3540",
41 | "02510e29d51e9a4268e6a5253c1fbd8144857945b82acb0accfc235cc7ca36da11",
42 | "0201a819bf549c253c4397bdd1535374de39e9bc278f637afdc27642d52cf79139",
43 | "039e3aa9ea182ccdaff2d8d150010b27cc4765c1d55ce674e52631af7376354d62",
44 | "03ee980e6334142342fcd9e6facfecfa139981e2276584c91d6a9739d533ac99fc"
45 | ],
46 | "address": "tltc1qtvscupcwnlsykujp98y0jhf8s4x48mzrr3v8v822ytg6vmprvzaqj8jd0h",
47 | "address_type": "WITNESS_V0"
48 | }
49 | ],
50 | "estimated_tx_size": 402
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/data/test-cases/json/prepare_sweep_transaction_response_p2pkh.json:
--------------------------------------------------------------------------------
1 | {
2 | "status" : "success",
3 | "data" : {
4 | "network" : "LTCTEST",
5 | "tx_type" : "sweep",
6 | "inputs" : [
7 | {
8 | "input_index" : 0,
9 | "previous_txid" : "fe69f02769dc71af573ca82d1c642aff55f9568e962adfb6b818f57fb6e48e38",
10 | "previous_output_index" : 0,
11 | "input_value" : "0.00100000",
12 | "spending_address" : "mmaB1tz8Nth1WdLx2bQJDavD15gJEyeCAM"
13 | }
14 | ],
15 | "outputs" : [
16 | {
17 | "output_index" : 0,
18 | "output_category" : "sweep-amount",
19 | "output_value" : "0.00095900",
20 | "receiving_address" : "QTLcyTFrH7T6kqUsi1VV2mJVXmX3AmwUNH"
21 | }
22 | ],
23 | "input_address_data" : [
24 | {
25 | "required_signatures" : 1,
26 | "public_keys" : [
27 | "021499295873879c280c31fff94845a6153142e76b84a29afc92128d482857ed93"
28 | ],
29 | "address" : "mmaB1tz8Nth1WdLx2bQJDavD15gJEyeCAM",
30 | "address_type" : "P2PKH"
31 | }
32 | ],
33 | "estimated_tx_size" : 205
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/data/test-cases/json/prepare_sweep_transaction_response_p2wpkh.json:
--------------------------------------------------------------------------------
1 | {
2 | "status": "success",
3 | "data": {
4 | "network": "LTCTEST",
5 | "tx_type": "sweep",
6 | "inputs": [
7 | {
8 | "input_index": 0,
9 | "previous_txid": "2e03c387e4e817db6fcd6d37c27bed639e82d08c055cedbff4eeab3108793a83",
10 | "previous_output_index": 0,
11 | "input_value": "0.00300000",
12 | "spending_address": "tltc1qgfkf7dfvuty0q67c4y9zvh0dfvm4nsjr86ltvf"
13 | }
14 | ],
15 | "outputs": [
16 | {
17 | "output_index": 0,
18 | "output_category": "sweep-amount",
19 | "output_value": "0.00297160",
20 | "receiving_address": "QTLcyTFrH7T6kqUsi1VV2mJVXmX3AmwUNH"
21 | }
22 | ],
23 | "input_address_data": [
24 | {
25 | "required_signatures": 1,
26 | "public_keys": [
27 | "021499295873879c280c31fff94845a6153142e76b84a29afc92128d482857ed93"
28 | ],
29 | "address": "tltc1qgfkf7dfvuty0q67c4y9zvh0dfvm4nsjr86ltvf",
30 | "address_type": "P2WPKH"
31 | }
32 | ],
33 | "estimated_tx_size": 142
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/data/test-cases/json/prepare_sweep_transaction_response_p2wpkh_over_p2sh.json:
--------------------------------------------------------------------------------
1 | {
2 | "status": "success",
3 | "data": {
4 | "network": "LTCTEST",
5 | "tx_type": "sweep",
6 | "inputs": [
7 | {
8 | "input_index": 0,
9 | "previous_txid": "4782b2a10c81bcda3f7d323d61d403201531c64e5b9b5724531c5d5a3e084eca",
10 | "previous_output_index": 0,
11 | "input_value": "0.00200000",
12 | "spending_address": "QQxAAYSmsYGhq3W5NMFTvjV3QABTKErjAV"
13 | }
14 | ],
15 | "outputs": [
16 | {
17 | "output_index": 0,
18 | "output_category": "sweep-amount",
19 | "output_value": "0.00197160",
20 | "receiving_address": "QTLcyTFrH7T6kqUsi1VV2mJVXmX3AmwUNH"
21 | }
22 | ],
23 | "input_address_data": [
24 | {
25 | "required_signatures": 1,
26 | "public_keys": [
27 | "021499295873879c280c31fff94845a6153142e76b84a29afc92128d482857ed93"
28 | ],
29 | "address": "QQxAAYSmsYGhq3W5NMFTvjV3QABTKErjAV",
30 | "address_type": "P2WPKH-over-P2SH"
31 | }
32 | ],
33 | "estimated_tx_size": 142
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/data/test-cases/json/prepare_transaction_response.json:
--------------------------------------------------------------------------------
1 | {
2 | "status": "success",
3 | "data": {
4 | "network": "LTCTEST",
5 | "tx_type": "basic",
6 | "inputs": [
7 | {
8 | "input_index": 0,
9 | "previous_txid": "17b236a68dcb0b338b0d11c2551004917dac868eb8e203715c5cf099c2655473",
10 | "previous_output_index": 0,
11 | "input_value": "0.00020000",
12 | "spending_address": "QcnYiN3t3foHxHv7CnqXrmRoiMkADhapZw"
13 | },
14 | {
15 | "input_index": 1,
16 | "previous_txid": "0a574a50754e5acc410d83acbfa19d1d29837c8326d444c2f1aee4e3c05d9634",
17 | "previous_output_index": 0,
18 | "input_value": "0.00020000",
19 | "spending_address": "QcnYiN3t3foHxHv7CnqXrmRoiMkADhapZw"
20 | },
21 | {
22 | "input_index": 2,
23 | "previous_txid": "020974d1337bb7ba94a3c8df60b9b559e2bbc66652262cf1d85abd7d1a67118f",
24 | "previous_output_index": 0,
25 | "input_value": "0.00020000",
26 | "spending_address": "QcnYiN3t3foHxHv7CnqXrmRoiMkADhapZw"
27 | },
28 | {
29 | "input_index": 3,
30 | "previous_txid": "cd85a6b9859d998fdeedc3389359c4b069734f8ce227ea65c2f081020d9c95f3",
31 | "previous_output_index": 1,
32 | "input_value": "0.00618893",
33 | "spending_address": "QcnYiN3t3foHxHv7CnqXrmRoiMkADhapZw"
34 | },
35 | {
36 | "input_index": 4,
37 | "previous_txid": "4cbe6f4beed21ce4260d350922af62d49c097ed206d5fc94a06ff660fc035546",
38 | "previous_output_index": 0,
39 | "input_value": "0.00020000",
40 | "spending_address": "QcnYiN3t3foHxHv7CnqXrmRoiMkADhapZw"
41 | },
42 | {
43 | "input_index": 5,
44 | "previous_txid": "027a5e03c4472c79a90ddb10e755082237af2dc75362e91f1c56b597ce495fe8",
45 | "previous_output_index": 1,
46 | "input_value": "0.01215660",
47 | "spending_address": "QcnYiN3t3foHxHv7CnqXrmRoiMkADhapZw"
48 | },
49 | {
50 | "input_index": 6,
51 | "previous_txid": "e1ffcd79dfae5c12f44a3a759014a24693b739fefd6aa8fb14f5066ed5b14c14",
52 | "previous_output_index": 0,
53 | "input_value": "0.00020000",
54 | "spending_address": "QcnYiN3t3foHxHv7CnqXrmRoiMkADhapZw"
55 | },
56 | {
57 | "input_index": 7,
58 | "previous_txid": "e37ef8c258318664d68d1ece81573030f6547fddb8db386f857d662918523a34",
59 | "previous_output_index": 0,
60 | "input_value": "0.00500000",
61 | "spending_address": "QUMD2qFssQKXGiTPJqPpGXhBeNuXxjrt1b"
62 | },
63 | {
64 | "input_index": 8,
65 | "previous_txid": "e37ef8c258318664d68d1ece81573030f6547fddb8db386f857d662918523a34",
66 | "previous_output_index": 1,
67 | "input_value": "0.10577860",
68 | "spending_address": "QgAhHtRN25P334nZqg7GaqQ6oDZEBmdGpT"
69 | },
70 | {
71 | "input_index": 9,
72 | "previous_txid": "f30d8aecb0d0d5508a484403cf52e8e3b0b111b9cab646592e1ee009d00139af",
73 | "previous_output_index": 0,
74 | "input_value": "0.00500000",
75 | "spending_address": "QRSVjcGho5ToUPhJ1xMPMArWrzxXLBWVcD"
76 | },
77 | {
78 | "input_index": 10,
79 | "previous_txid": "2ae18dd3edb286f41eeb0b001a9bebb84ef49c845930ec0b879da2793e934af8",
80 | "previous_output_index": 0,
81 | "input_value": "0.05234567",
82 | "spending_address": "tltc1q9ptjkd7rujg8q6pzt2xuv9tevzjfnmltes9ygasxxj3enyvmhskqxprm44"
83 | }
84 | ],
85 | "outputs": [
86 | {
87 | "output_index": 0,
88 | "output_category": "user-specified",
89 | "output_value": "0.12345678",
90 | "receiving_address": "tltc1qxwtaq6y866rk9rz4s8reltfwvkgj0xsxx43smkrk45ekep4usm2q3kqsjy"
91 | },
92 | {
93 | "output_index": 1,
94 | "output_category": "user-specified",
95 | "output_value": "0.01234567",
96 | "receiving_address": "QTLcyTFrH7T6kqUsi1VV2mJVXmX3AmwUNH"
97 | },
98 | {
99 | "output_index": 2,
100 | "output_category": "user-specified",
101 | "output_value": "0.00123456",
102 | "receiving_address": "QgYm5tLdYBaVYiGdhq2XdpwEazSVQQZ5Ya"
103 | },
104 | {
105 | "output_index": 3,
106 | "output_category": "change",
107 | "output_value": "0.05009359",
108 | "receiving_address": "tltc1q9ptjkd7rujg8q6pzt2xuv9tevzjfnmltes9ygasxxj3enyvmhskqxprm44"
109 | }
110 | ],
111 | "input_address_data": [
112 | {
113 | "required_signatures": 2,
114 | "public_keys": [
115 | "0293e2e2c8ffa27cdb686a169ac02edd4cd72845da4080a2c3a25fc1d81f3a1541",
116 | "03f2e51b2f298bab1e4485d7d31a559de73fc29dced12f568e30264dbae4ec326e"
117 | ],
118 | "address": "QcnYiN3t3foHxHv7CnqXrmRoiMkADhapZw",
119 | "address_type": "P2WSH-over-P2SH"
120 | },
121 | {
122 | "required_signatures": 2,
123 | "public_keys": [
124 | "0366b0a62cf55af8fab2c20ac61c8ce0e21577b9502e83af0955ac706fa395e1a9",
125 | "03f2e51b2f298bab1e4485d7d31a559de73fc29dced12f568e30264dbae4ec326e"
126 | ],
127 | "address": "QUMD2qFssQKXGiTPJqPpGXhBeNuXxjrt1b",
128 | "address_type": "P2SH"
129 | },
130 | {
131 | "required_signatures": 2,
132 | "public_keys": [
133 | "02d0ac8192c1bb328c7b1a46cc6974b903995ed4b9746b77ca06c20a9e16b95b8f",
134 | "03f2e51b2f298bab1e4485d7d31a559de73fc29dced12f568e30264dbae4ec326e"
135 | ],
136 | "address": "QgAhHtRN25P334nZqg7GaqQ6oDZEBmdGpT",
137 | "address_type": "P2WSH-over-P2SH"
138 | },
139 | {
140 | "required_signatures": 2,
141 | "public_keys": [
142 | "0327bba51878eb35200f2a15e4a3724d675074eb64610aab109c9ded35b0625128",
143 | "03f2e51b2f298bab1e4485d7d31a559de73fc29dced12f568e30264dbae4ec326e"
144 | ],
145 | "address": "QRSVjcGho5ToUPhJ1xMPMArWrzxXLBWVcD",
146 | "address_type": "P2WSH-over-P2SH"
147 | },
148 | {
149 | "required_signatures": 2,
150 | "public_keys": [
151 | "035bb6a1cb961a9b54e666a366b896dac3ec9ad352a57714ffb22b45dabaed702b",
152 | "03f2e51b2f298bab1e4485d7d31a559de73fc29dced12f568e30264dbae4ec326e"
153 | ],
154 | "address": "tltc1q9ptjkd7rujg8q6pzt2xuv9tevzjfnmltes9ygasxxj3enyvmhskqxprm44",
155 | "address_type": "WITNESS_V0"
156 | }
157 | ],
158 | "user_key": {
159 | "public_key": "03f2e51b2f298bab1e4485d7d31a559de73fc29dced12f568e30264dbae4ec326e",
160 | "encrypted_passphrase": "3FmrhIEX7tV46alsRuMonmLHPIX7hBSMCQmZKcVt2a3pcgDG4cmwLjaiDPJzIAPtBI9NZ5ybnO824EOzreak9bxzPhg/D2CFhGZAl+Iq3RzURwhY6kQ6kdtj5G///5UlJgY17SQa8/i+TXwLj5/AOfNIfMU0QUxVzSsfAJO3VfH9QioLnUJW5ZH1OkSdM849"
161 | },
162 | "estimated_tx_size": 1696
163 | }
164 | }
165 |
--------------------------------------------------------------------------------
/data/test-cases/json/prepare_transaction_response_with_blockio_fee_and_expected_unsigned_txid.json:
--------------------------------------------------------------------------------
1 | {
2 | "status": "success",
3 | "data": {
4 | "network": "LTCTEST",
5 | "tx_type": "basic",
6 | "inputs": [
7 | {
8 | "input_index": 0,
9 | "previous_txid": "5d5d9a902f572449786ce65a586481dfa646f66f8500cadbed811eaf82370630",
10 | "previous_output_index": 0,
11 | "input_value": "0.00249485",
12 | "spending_address": "Qjgm6PfqcCmQr31DKnnch7zwG7sFpVc7xb"
13 | },
14 | {
15 | "input_index": 1,
16 | "previous_txid": "adc348dcfed62799718e375d9097fa71d2a321c9137c4a52067a13181203ead4",
17 | "previous_output_index": 0,
18 | "input_value": "0.00090000",
19 | "spending_address": "QN7WkTwGmTxEnK4EpzsspPofsKPXvjLKxC"
20 | },
21 | {
22 | "input_index": 2,
23 | "previous_txid": "9ffa4dceac8b09fb099fb0ebcc5d0bcb132c98b3e8a2b1381d8d9654f82a5cf0",
24 | "previous_output_index": 1,
25 | "input_value": "0.17464160",
26 | "spending_address": "QN7WkTwGmTxEnK4EpzsspPofsKPXvjLKxC"
27 | }
28 | ],
29 | "outputs": [
30 | {
31 | "output_index": 0,
32 | "output_category": "user-specified",
33 | "output_value": "0.01234500",
34 | "receiving_address": "QN9ShoLwKTnW7ms7bfSrH7YgMPh2cjUkdi"
35 | },
36 | {
37 | "output_index": 1,
38 | "output_category": "blockio-fee",
39 | "output_value": "0.00012345",
40 | "receiving_address": "tltc1qyhn4wkpzt0e8qm7a9kk8v0cpxn775wlqfgl32s20n00p3q5q7uksc4xqfe"
41 | },
42 | {
43 | "output_index": 2,
44 | "output_category": "change",
45 | "output_value": "0.16546580",
46 | "receiving_address": "QN7WkTwGmTxEnK4EpzsspPofsKPXvjLKxC"
47 | }
48 | ],
49 | "input_address_data": [
50 | {
51 | "required_signatures": 2,
52 | "public_keys": [
53 | "0325e036e22b3ee1593baba8052e92505a11446a1ba3e8bc3f9f74b17f2e6b6a3e",
54 | "034309721935937713a2dd1c33c5c44a10a30674bebe0542aeb145ce4c9790e742"
55 | ],
56 | "address": "Qjgm6PfqcCmQr31DKnnch7zwG7sFpVc7xb",
57 | "address_type": "P2WSH-over-P2SH"
58 | },
59 | {
60 | "required_signatures": 2,
61 | "public_keys": [
62 | "03b934de0933b8b80051530d353f5fb22ec7e5d6233d8b6bf2ddcaf7119e2f4fe7",
63 | "034309721935937713a2dd1c33c5c44a10a30674bebe0542aeb145ce4c9790e742"
64 | ],
65 | "address": "QN7WkTwGmTxEnK4EpzsspPofsKPXvjLKxC",
66 | "address_type": "P2WSH-over-P2SH"
67 | }
68 | ],
69 | "user_key": {
70 | "public_key": "034309721935937713a2dd1c33c5c44a10a30674bebe0542aeb145ce4c9790e742",
71 | "encrypted_passphrase": "UMT6oOUQf/hRNCdiSc3HAHRdn95G9iSxvU5pqTCveqaQxpVShoyCBfgDOfzbgrk3Cw4oNY0sBStoWqCc4ha4kFXBblt/6DzxV7YBD2lDkjGYdrYISrGG+kAHEbLxcVwHL/6Feep9ve3NgvCSEV0MTrm2HBNfX1ke/+XBoYhn7ZX9QioLnUJW5ZH1OkSdM849"
72 | },
73 | "estimated_tx_size": 511,
74 | "expected_unsigned_txid": "4ee43869f50d226686a0ad0949e4adeb02f4d93bb7ea2efda62dfc36f2a1b0b7"
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/data/test-cases/json/prepare_transaction_response_witness_v1_output.json:
--------------------------------------------------------------------------------
1 | {
2 | "status": "success",
3 | "data": {
4 | "network": "BTCTEST",
5 | "tx_type": "basic",
6 | "inputs": [
7 | {
8 | "input_index": 0,
9 | "previous_txid": "5fe41d63872f6d724f2a703597885d3a19df8f13cdaa7ea9b8f94f869bcc4e78",
10 | "previous_output_index": 1,
11 | "input_value": "0.00060915",
12 | "spending_address": "2MsU2DsvP7okZ2sZGZUvH3rZZuQt4pf75Am"
13 | }
14 | ],
15 | "outputs": [
16 | {
17 | "output_index": 0,
18 | "output_category": "user-specified",
19 | "output_value": "0.00020000",
20 | "receiving_address": "tb1pqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesf3hn0c"
21 | },
22 | {
23 | "output_index": 1,
24 | "output_category": "user-specified",
25 | "output_value": "0.00020000",
26 | "receiving_address": "tb1qqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesrxh6hy"
27 | },
28 | {
29 | "output_index": 2,
30 | "output_category": "change",
31 | "output_value": "0.00019615",
32 | "receiving_address": "2MsU2DsvP7okZ2sZGZUvH3rZZuQt4pf75Am"
33 | }
34 | ],
35 | "input_address_data": [
36 | {
37 | "required_signatures": 2,
38 | "public_keys": [
39 | "021916c1ea9215990263e2862bcecc85397d199a4411c863983176ee3d44e27f7f",
40 | "02d2cbf77287c1443759abdd35f239e7da2f52c992258653bc8dd577ae63c78628"
41 | ],
42 | "address": "2MsU2DsvP7okZ2sZGZUvH3rZZuQt4pf75Am",
43 | "address_type": "P2WSH-over-P2SH"
44 | }
45 | ],
46 | "user_key": {
47 | "public_key": "02d2cbf77287c1443759abdd35f239e7da2f52c992258653bc8dd577ae63c78628",
48 | "encrypted_passphrase": "jlPuw8CJGTWTb+O4I/IKGWGDdF9G8/MX5meX+IfuLfbb7rRABoSUGYSU2BXxxRqR95K64u8gH46h3zr/NKsj8OFv5gj4JwClM7RN03fvb+3CyXgwy4eYSSpFE6vVsdyoxJ8rshUbpf8tvCerUKC0LhE9d61q7mWYoVAik61WRwc=",
49 | "algorithm": {
50 | "pbkdf2_salt": "7ccf40ce398f0fb475fe91043f7dce57",
51 | "pbkdf2_iterations": 102400,
52 | "pbkdf2_hash_function": "SHA256",
53 | "pbkdf2_phase1_key_length": 16,
54 | "pbkdf2_phase2_key_length": 32,
55 | "aes_iv": "7c69a5e81e53ba05213b35bb",
56 | "aes_cipher": "AES-256-GCM",
57 | "aes_auth_tag": "d19b5e48e068f3b1179a4724b3862650",
58 | "aes_auth_data": ""
59 | }
60 | },
61 | "estimated_tx_size": 260,
62 | "expected_unsigned_txid": "e1925681c6986df2617d41c90b2bbc32cd01f7df0323ca52417b4a8677b2a160"
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/data/test-cases/json/summarize_prepared_transaction_response_with_blockio_fee_and_expected_unsigned_txid.json:
--------------------------------------------------------------------------------
1 | {
2 | "network": "LTCTEST",
3 | "network_fee": "0.00010220",
4 | "blockio_fee": "0.00012345",
5 | "total_amount_to_send": "0.01234500"
6 | }
7 |
--------------------------------------------------------------------------------
/examples/basic.js:
--------------------------------------------------------------------------------
1 | // creates a new destination address, withdraws from the default label to it,
2 | // gets sent transactions, and the current price, using async/await
3 |
4 | const BlockIo = require('../lib/block_io');
5 |
6 | const PIN = process.env.PIN;
7 | const AMOUNT = '50.0';
8 |
9 | // please use a Testnet API key here
10 | const client = new BlockIo({api_key: process.env.API_KEY, pin: PIN});
11 |
12 | async function example() {
13 | try {
14 | let data;
15 |
16 | data = await client.get_balance();
17 | console.log(JSON.stringify(data, null, 2));
18 |
19 | // create a new address
20 | try {
21 | data = await client.get_new_address({ label: 'asyncTest' });
22 | console.log(JSON.stringify(data, null, 2));
23 | } catch (err) {
24 | console.log(err);
25 | }
26 |
27 | // Withdraw to our new address
28 | // get the data to create the transaction
29 | let prepared_transaction = await client.prepare_transaction({
30 | from_labels: 'default',
31 | to_label: 'asyncTest',
32 | amount: AMOUNT
33 | });
34 |
35 | // summarize the transaction we are going to prepare
36 | // for in-depth review, look at prepared_transaction yourself
37 | let summarized_transaction = await client.summarize_prepared_transaction({data: prepared_transaction});
38 |
39 | console.log(JSON.stringify(summarized_transaction, null, 2));
40 |
41 | // after review, if you wish to approve the transaction: create and sign it
42 | let signed_transaction = await client.create_and_sign_transaction({data: prepared_transaction});
43 | console.log(JSON.stringify(signed_transaction,null,2));
44 |
45 | // review the signed transaction (specifically, the tx_hex) and ensure things are as you want them
46 | // if you approve of it, submit the transaction for Block.io's signatures and broadcast to the blockchain network
47 | // if successful, the data below contains the transaction ID on the blockchain network
48 | data = await client.submit_transaction({transaction_data: signed_transaction});
49 | console.log(JSON.stringify(data, null, 2));
50 |
51 | // Show the address associated with the label 'default'
52 | data = await client.get_address_by_label({ label: 'default' });
53 | console.log(JSON.stringify(data, null, 2));
54 |
55 | // Show transactions we sent
56 | data = await client.get_transactions({ type: 'sent' });
57 | console.log(JSON.stringify(data, null, 2));
58 |
59 | // Show the current price with BTC
60 | data = await client.get_current_price({ base_price: 'BTC' });
61 | console.log(JSON.stringify(data, null, 2));
62 |
63 | } catch (error) {
64 | //stop on any errors and log it
65 | console.log("Error occured:", error.message);
66 | }
67 | }
68 |
69 | // run the example
70 | example();
71 |
--------------------------------------------------------------------------------
/examples/dtrust.js:
--------------------------------------------------------------------------------
1 | /* This script demonstrates use of 4 of 5 MultiSig addresses with the Distributed Trust API
2 | * at Block.io. Each key can be signed separately -- perfect for escrow, a variety of security
3 | * architectures, and ofcourse, for personal use + cold storage of savings.
4 | *
5 | * Any questions? Contact support@block.io.
6 | */
7 |
8 | const BlockIo = require('../lib/block_io');
9 | const crypto = require('crypto');
10 |
11 | const VERSION = 2;
12 |
13 | const client = new BlockIo({
14 | api_key: process.env.API_KEY, // your API key
15 | pin: process.env.PIN, // your PIN (for funding the dTrust address you will create
16 | version: VERSION
17 | });
18 |
19 | // create a new address with a random label
20 | const addressLabel = 'dtrust' + crypto.randomBytes(4).toString('hex');
21 |
22 | // create the private key objects for each private key
23 | // NOTE: in production environments you'll do this elsewhere
24 | // WARNING: this is just a demo. DO NOT USE THESE KEYS IN PRODUCTION! Generate new keys using BlockIo.ECKey.makeRandom() or elsewhere:
25 | // let key1 = BlockIo.ECKey.makeRandom();
26 | // key1.pub.toString('hex');
27 | // key1.priv.toString('hex'); // store this somewhere safe!
28 | // let key2 = ...
29 | // let saved_key1 = BlockIo.ECKey.fromHex(key1.priv.toString('hex'));
30 | // let saved_key2 = ...
31 | // const privKeys = [ saved_key1, saved_key2, saved_key3, saved_key4 ]
32 |
33 | const privKeys = [
34 | BlockIo.ECKey.fromPassphraseString('verysecretkey1'),
35 | BlockIo.ECKey.fromPassphraseString('verysecretkey2'),
36 | BlockIo.ECKey.fromPassphraseString('verysecretkey3'),
37 | BlockIo.ECKey.fromPassphraseString('verysecretkey4')
38 | ];
39 |
40 | const pubKeys = [];
41 |
42 | // populate our pubkeys array from the keys we just generated
43 | // pubkey entries are expected in hexadecimal format
44 | console.log('* Collecting public keys...');
45 | privKeys.forEach(function (key) {
46 | const pubkey = key.pub.toString('hex');
47 | console.log('>> Adding pubkey: ' + pubkey);
48 | pubKeys.push(pubkey);
49 | });
50 |
51 | async function dTrust() {
52 |
53 | try {
54 |
55 | // create a dTrust address that requires 4 out of 5 keys (4 of ours, 1 at Block.io).
56 | // Block.io automatically adds +1 to specified required signatures because of its own key
57 | let addrData = await client.get_new_dtrust_address({
58 | label: addressLabel,
59 | public_keys: pubKeys.join(','),
60 | required_signatures: 3 // required signatures out of the set of signatures that we specified
61 | });
62 |
63 | // print out information about the address we just created
64 | const addr = addrData.data.address;
65 | const network = addrData.data.network;
66 | console.log('>> New dTrust Address on network ' + network + ' is ' + addr);
67 |
68 | // save the redeemscript so that you can use the address without depending on block.io services.
69 | console.log('>> Redeem script:', addrData.data.redeem_script);
70 |
71 | // let's send some coins to our new address
72 | console.log('* Sending 50 DOGETEST to', addr);
73 | let prepared_transaction = await client.prepare_transaction({
74 | from_labels: 'default',
75 | to_address: addr,
76 | amount: '50.0',
77 | });
78 | let signed_transaction = await client.create_and_sign_transaction({data: prepared_transaction});
79 | let funding = await client.submit_transaction({transaction_data: signed_transaction});
80 | console.log('>> Transaction ID: ' + funding.data.txid);
81 |
82 | // check if some balance got there
83 | console.log('* Getting address balance for', addr);
84 | let balance = await client.get_dtrust_address_balance({ address: addr });
85 |
86 | const availBalance = balance.data.available_balance;
87 | console.log('>> Available Balance in ' + addr + ' is ' + availBalance + ' ' + network);
88 |
89 | // find our non-dtrust default address so we can send coins back to it
90 | console.log('* Looking up default address');
91 | let defaultData = await client.get_address_by_label({ label: 'default' });
92 | const defaultAddress = defaultData.data.address;
93 |
94 | // the amount available minus the network fee needed to transact it
95 | // in general: never use floats. use high precision big numbers instead. we're just demonstrating a concept here.
96 | const amountToSend = (parseFloat(availBalance) - 1).toFixed(8);
97 |
98 | console.log('* Sending ' + amountToSend + ' ' + network + ' back to ' + defaultAddress);
99 | console.log(' Creating withdrawal request...');
100 |
101 | // let's send the coins back to the default address
102 | let prepared_dtrust_transaction = await client.prepare_dtrust_transaction({
103 | from_address: addr,
104 | to_address: defaultAddress,
105 | amount: amountToSend
106 | });
107 |
108 | // inspect the data yourself in-depth
109 | // here's a summary of the transaction you are going to create and sign
110 | let summarized_dtrust_transaction = await client.summarize_prepared_transaction({data: prepared_dtrust_transaction});
111 | console.log(">> Transaction Summary:");
112 | console.log(JSON.stringify(summarized_dtrust_transaction,null,2));
113 |
114 | // the response contains data to sign and all the public_keys that need to sign it
115 | // you can distribute this response to different processes that stored your
116 | // private keys and have them inform block.io after signing the data. You can
117 | // then finalize the transaction so that it gets broadcasted to the network.
118 | //
119 | // Below, we take this response, extract the data to sign, sign it,
120 | // and inform Block.io of the signatures, for each signer.
121 | let signed_dtrust_transaction = await client.create_and_sign_transaction({
122 | data: prepared_dtrust_transaction,
123 | keys: privKeys.slice(0,3).map((p) => { return p.priv.toString('hex'); }),
124 | });
125 |
126 | // submit the fully or partially signed transaction
127 | // block.io adds its signature if partially signed, and broadcasts the transaction to the peer-to-peer network
128 | let fin = await client.submit_transaction({transaction_data: signed_dtrust_transaction});
129 |
130 | // print the details of our transaction
131 | console.log('>> Transaction ID: ' + fin.data.txid);
132 |
133 | } catch (error) {
134 | console.log('ERROR:' + (error instanceof Error) ? error.stack : error);
135 | process.exit(1);
136 | }
137 |
138 | }
139 |
140 | // call the async function.
141 | dTrust();
142 |
143 | /* Relevant dTrust API calls (Numbers 1 thru 7: they work the same way as their non-dTrust counterparts on https://block.io/api).
144 | * For a list of parameters and how to use these calls, please refer to their equivalent counterparts at https://block.io/api
145 | * For any help whatsoever, please reach support@block.io
146 | * 1. get_dtrust_address_balance
147 | * 2. get_dtrust_address_by_label
148 | * 3. get_my_dtrust_addresses
149 | * 4. get_new_dtrust_address -- as demonstrated above
150 | * 5. get_dtrust_transactions
151 | * 6. prepare_dtrust_transaction -- returns data to create the appropriate transaction, and how to sign the transaction
152 | * 7. submit_transaction -- you can use this call to submit the fully or partially signed transaction. Must contain all relevant signatures for the transaction or the final transaction in hex form
153 | *
154 | * end :)
155 | */
156 |
--------------------------------------------------------------------------------
/examples/sweep.js:
--------------------------------------------------------------------------------
1 | /* Example script for sweeping all coins from a given address
2 | * Must use the API Key for the Network the address belongs to
3 | * Must also provide the Private Key to the sweep address in
4 | * Wallet Import Format (WIF)
5 | *
6 | * Contact support@block.io if you have any issues
7 | */
8 | const BlockIo = require('../lib/block_io');
9 |
10 | // PIN not needed
11 | const client = new BlockIo({api_key: process.env.API_KEY});
12 |
13 | const to_address = process.env.TO_ADDRESS;
14 | const private_key = process.env.WIF;
15 |
16 | async function example() {
17 | try {
18 |
19 | let balance = await client.get_balance();
20 | console.log(JSON.stringify(balance,null,2));
21 |
22 | // get data to create the transaction
23 | // the private key is converted to hex form and used by create_and_sign_transaction. It does not go to Block.io.
24 | let prepared_transaction = await client.prepare_sweep_transaction({
25 | to_address: to_address,
26 | private_key: private_key
27 | });
28 | console.log(JSON.stringify(prepared_transaction,null,2));
29 |
30 | // inspect the transaction in-depth yourself
31 | // this is just a summary of the transaction you are going to create and sign
32 | let summarized_transaction = await client.summarize_prepared_transaction({data: prepared_transaction});
33 | console.log(JSON.stringify(summarized_transaction,null,2));
34 |
35 | // once satisfied, create and sign the transaction
36 | let signed_transaction = await client.create_and_sign_transaction({data: prepared_transaction});
37 |
38 | // when ready to broadcast the transaction to the network, submit it to Block.io
39 | let data = await client.submit_transaction({transaction_data: signed_transaction});
40 |
41 | // if successful, this response now contains the transaction ID on the blockchain network
42 | console.log(JSON.stringify(data,null,2));
43 |
44 | } catch (err) {
45 | console.log(err);
46 | }
47 |
48 | }
49 |
50 | // run the example
51 | example();
52 |
--------------------------------------------------------------------------------
/lib/block_io.js:
--------------------------------------------------------------------------------
1 | /*eslint no-mixed-spaces-and-tabs: ["error", "smart-tabs"]*/
2 |
3 | const HttpsClient = require('./httpsclient');
4 | const qs = require('querystring');
5 | const pkgMeta = require('../package.json');
6 |
7 | const Bitcoin = require('bitcoinjs-lib');
8 | const helper = require('./helper');
9 | const ECKey = require('./key');
10 | const networks = require('./networks');
11 | const ecc = require('tiny-secp256k1');
12 |
13 | Bitcoin.initEccLib(ecc); // bitcoinjs 6.1.0 requires explicit initialization of ecc library
14 |
15 | function BlockIo (config, pin, version, options) {
16 |
17 | this.options = {
18 | allowNoPin: false,
19 | lowR: true,
20 | };
21 |
22 | if (typeof(config) === 'string') {
23 |
24 | this.api_key = config;
25 | this.version = version || BlockIo.DEFAULT_VERSION;
26 | this.server = BlockIo.DEFAULT_SERVER;
27 | this.port = BlockIo.DEFAULT_PORT;
28 | this.keys = {}; // we will store keys for use in between calls here
29 |
30 | if (pin) {
31 | this.pin = pin;
32 | }
33 |
34 | if (options && typeof(options) == 'object') this._cloneOptions(options);
35 |
36 | } else if (config && typeof(config) == 'object') {
37 | this.api_key = config.api_key;
38 | this.version = config.version || BlockIo.DEFAULT_VERSION;
39 | this.server = config.server || BlockIo.DEFAULT_SERVER;
40 | this.port = config.port || BlockIo.DEFAULT_PORT;
41 | this.keys = {}; // we will store keys for use in between calls here
42 |
43 | if (config.pin) {
44 | this.pin = config.pin;
45 | }
46 |
47 | if (config.options) this._cloneOptions(config.options);
48 | }
49 |
50 | const client = new HttpsClient(undefined, BlockIo.USER_AGENT);
51 | Object.defineProperty(this, 'client', {value: client, writeable: false});
52 | }
53 |
54 | // link submodules / subclasses
55 | BlockIo.ECKey = ECKey;
56 | BlockIo.helper = helper;
57 |
58 | BlockIo.DEFAULT_VERSION = 2;
59 | BlockIo.DEFAULT_SERVER = '';
60 | BlockIo.DEFAULT_PORT = '';
61 | BlockIo.HOST = 'block.io';
62 |
63 |
64 | // Error messages
65 | const ERR_PIN_INV = 'Public key mismatch. Invalid Secret PIN detected.';
66 | const ERR_PK_EXTR = 'Could not extract privkey';
67 | const ERR_WIF_MIS = 'Missing mandatory private_key argument';
68 | const ERR_WIF_INV = 'Could not parse private_key as WIF';
69 | const ERR_DEST_MIS = 'Missing mandatory to_address argument';
70 | const ERR_UNKNOWN = 'Unknown error occured';
71 | const ERR_NET_UNK = 'Unknown network';
72 |
73 | // hex key regex
74 | const HEX_KEY_REGEX = new RegExp(/^[a-f0-9]{64}$/);
75 |
76 | // IF YOU EDIT THIS LIB, PLEASE BE A DARLING AND CHANGE THE USER AGENT :)
77 | BlockIo.USER_AGENT = ['npm','block_io', pkgMeta.version].join(':');
78 |
79 | // simple uniform methods that do not need special processing
80 | BlockIo.PASSTHROUGH_METHODS = [
81 | 'get_balance', 'get_new_address', 'get_my_addresses', 'get_address_received',
82 | 'get_address_by_label', 'get_address_balance', 'create_user', 'get_users',
83 | 'get_user_balance', 'get_user_address', 'get_user_received',
84 | 'get_transactions', 'get_new_dtrust_address',
85 | 'get_my_dtrust_addresses', 'get_dtrust_address_by_label',
86 | 'get_dtrust_transactions', 'get_dtrust_address_balance',
87 | 'get_network_fee_estimate', 'archive_address', 'unarchive_address',
88 | 'get_my_archived_addresses', 'archive_dtrust_address',
89 | 'unarchive_dtrust_address', 'get_my_archived_dtrust_addresses',
90 | 'get_dtrust_network_fee_estimate', 'create_notification', 'disable_notification',
91 | 'enable_notification', 'get_notifications', 'get_recent_notification_events',
92 | 'delete_notification',
93 | 'get_my_addresses_without_balances', 'get_raw_transaction', 'get_dtrust_balance',
94 | 'archive_addresses', 'unarchive_addresses', 'archive_dtrust_addresses', 'unarchive_dtrust_addresses',
95 | 'is_valid_address', 'get_current_price', 'get_account_info', 'list_notifications',
96 | 'decode_raw_transaction', 'prepare_transaction', 'prepare_dtrust_transaction',
97 | 'submit_transaction'
98 | ];
99 |
100 | BlockIo.SWEEP_METHODS = ['prepare_sweep_transaction'];
101 |
102 | BlockIo.prototype.summarize_prepared_transaction = function(args, cb) {
103 | // summarizes the prepare_transaction response for the user
104 | // the prepare_transaction response is used to create the actual transaction
105 |
106 | const data = args.data;
107 |
108 | const promise = new Promise((f,r) => {
109 |
110 | let input_sum = 0;
111 | let output_sum = 0;
112 | let blockio_fee = 0;
113 | let change_amount = 0;
114 | let response = undefined;
115 |
116 | try {
117 |
118 | for(let curInput of data.data.inputs) {
119 | // sum all input values
120 | input_sum += helper.to_satoshis(curInput.input_value);
121 | }
122 |
123 | for (let curOutput of data.data.outputs) {
124 | // sum various categories of outputs
125 | if (curOutput.output_category === "blockio-fee")
126 | blockio_fee += helper.to_satoshis(curOutput.output_value);
127 | else if (curOutput.output_category === "change")
128 | change_amount += helper.to_satoshis(curOutput.output_value);
129 | else // user-specified or sweep-amount
130 | output_sum += helper.to_satoshis(curOutput.output_value);
131 | }
132 |
133 | response = {
134 | network: data.data.network,
135 | network_fee: helper.from_satoshis(input_sum - output_sum - blockio_fee - change_amount),
136 | blockio_fee: helper.from_satoshis(blockio_fee),
137 | total_amount_to_send: helper.from_satoshis(output_sum),
138 | };
139 | } catch(err) {
140 | return r(err);
141 | }
142 |
143 | return f(response);
144 |
145 | });
146 |
147 | return cbPromiseHelper(promise, cb);
148 | };
149 |
150 | BlockIo.prototype.create_and_sign_transaction = function (args, cb) {
151 | // given args.data, create the appropriate transaction
152 | // given args.keys or args.pin or this.aeskey, sign the transaction with whichever keys we can
153 | // return tx_hex and signatures
154 |
155 | const data = args.data;
156 | const keys = args.keys || [];
157 |
158 | const promise = new Promise((f,r) => {
159 |
160 | // record the keys provided by the user
161 | if (keys !== undefined) {
162 | if (keys instanceof Array) {
163 | for(let curKey of keys) {
164 | if (typeof(curKey) == 'string' && curKey.length === 64 && HEX_KEY_REGEX.test(curKey)) {
165 | // keys must be strings of 64 char hex
166 | let keyObj = ECKey.fromHex(curKey); //new ECKey(Buffer.from(curKey, 'hex'), {compressed: true});
167 | // honor lowR option
168 | keyObj.lowR = this.options.lowR;
169 | this.keys[keyObj.pub.toString('hex')] = keyObj;
170 | }
171 | }
172 | } else {
173 | return r(new Error("keys must be an array of 64-char hex strings")); // TODO make this a constant
174 | }
175 | }
176 |
177 | if (Object.prototype.hasOwnProperty.call(data.data, 'user_key')) {
178 | if (this.keys[data.data.user_key.public_key] === undefined) {
179 | if (this.pin === undefined && args.pin === undefined) {
180 | throw("Must either instantiate object with a PIN, or provide the PIN for create_and_sign_transaction to decrypt the private key");
181 | } else if (this.pin !== undefined || args.pin !== undefined) {
182 | // the user provided the PIN here, so let's use it
183 |
184 | let privkey = helper.dynamicExtractKey(data.data.user_key, this.pin || args.pin);
185 |
186 | if (!(privkey instanceof ECKey))
187 | return r(new Error(ERR_PK_EXTR));
188 |
189 | // honor lowR option
190 | privkey.lowR = this.options.lowR;
191 |
192 | const pubkey = privkey.pub.toString('hex');
193 | if (pubkey !== data.data.user_key.public_key)
194 | return r(new Error(ERR_PIN_INV));
195 |
196 | // store this key for later use
197 | this.keys[pubkey] = privkey;
198 | }
199 | }
200 | }
201 |
202 | // we're done with the keys
203 |
204 | // index address data for inputs so we can use it later
205 | let input_address_data = {};
206 |
207 | for (let curAddressData of data.data.input_address_data) {
208 | input_address_data[curAddressData.address] = curAddressData;
209 | }
210 |
211 | // create the unsigned transaction
212 |
213 | const network_params = networks.getNetwork(data.data.network);
214 | let can_fully_sign_tx = true; // we will update this as we parse through the inputs
215 |
216 | let psbt = new Bitcoin.Psbt({network: network_params});
217 | psbt.setVersion(1);
218 | psbt.setLocktime(0);
219 |
220 | // for each input
221 | for(let curInput of data.data.inputs) {
222 |
223 | let address_data = input_address_data[curInput.spending_address];
224 |
225 | let psbt_input = {
226 | hash: curInput.previous_txid,
227 | index: curInput.previous_output_index,
228 | sequence: 0xffffffff,
229 | };
230 |
231 | if (["P2SH", "P2WSH-over-P2SH", "WITNESS_V0"].includes(address_data.address_type)) {
232 |
233 | let redeem_script = helper.get_redeem_script(address_data.required_signatures,
234 | address_data.public_keys,
235 | network_params);
236 |
237 | if (address_data.address_type === "P2SH") {
238 | // TODO get raw tx to suppress bitcoinjs-lib warnings
239 | psbt_input['witnessUtxo'] = {script: redeem_script.output, value: helper.to_satoshis(curInput.input_value)};
240 | psbt_input['redeemScript'] = redeem_script.output;
241 | } else if (address_data.address_type === "P2WSH-over-P2SH") {
242 | psbt_input['witnessUtxo'] = {script: Bitcoin.payments.p2wsh({redeem: redeem_script}).output, value: helper.to_satoshis(curInput.input_value)};
243 | psbt_input['witnessScript'] = redeem_script.output;
244 | psbt_input['redeemScript'] = Bitcoin.payments.p2sh({redeem: Bitcoin.payments.p2wsh({redeem: redeem_script})}).output;
245 | } else {
246 | // P2WSH (WITNESS_V0)
247 | psbt_input['witnessUtxo'] = {script: Bitcoin.payments.p2wsh({redeem: redeem_script}).output, value: helper.to_satoshis(curInput.input_value)};
248 | psbt_input['witnessScript'] = redeem_script.output;
249 | }
250 |
251 | } else if (["P2PKH", "P2WPKH-over-P2SH", "P2WPKH"].includes(address_data.address_type)) {
252 | let pkh_pubkey = Buffer.from(address_data.public_keys[0], 'hex');
253 | let p2pkh_address = Bitcoin.payments.p2pkh({pubkey: pkh_pubkey, network: network_params});
254 |
255 | if (address_data.address_type === "P2PKH") {
256 | // TODO get raw tx to suppress bitcoinjs-lib warnings
257 | psbt_input['witnessUtxo'] = {script: p2pkh_address.output, value: helper.to_satoshis(curInput.input_value)};
258 | } else if (address_data.address_type === "P2WPKH") {
259 | psbt_input['witnessUtxo'] = {script: Bitcoin.payments.p2wpkh({pubkey: pkh_pubkey, network: network_params}).output, value: helper.to_satoshis(curInput.input_value)};
260 | } else if (address_data.address_type === "P2WPKH-over-P2SH") {
261 | let p2wpkh_address = Bitcoin.payments.p2wpkh({pubkey: pkh_pubkey, network: network_params});
262 | psbt_input['witnessUtxo'] = {script: Bitcoin.payments.p2sh({redeem: p2wpkh_address}).output, value: helper.to_satoshis(curInput.input_value)};
263 | psbt_input['redeemScript'] = p2wpkh_address.output;
264 | }
265 | } else {
266 | return r(new Error("Unknown address type: " + address_data.address_type));
267 | }
268 |
269 | // add the input to psbt
270 | psbt.addInput(psbt_input);
271 |
272 | // record whether we can fully sign all inputs or not
273 | let can_sign = 0;
274 |
275 | for (let curPubKey of address_data.public_keys) {
276 | if (this.keys[curPubKey] !== undefined) {
277 | can_sign += 1;
278 | }
279 | }
280 |
281 | can_fully_sign_tx = (can_fully_sign_tx && (can_sign >= address_data.required_signatures));
282 | }
283 |
284 | // add the outputs to Psbt
285 | for (let curOutput of data.data.outputs) {
286 | psbt.addOutput({
287 | address: curOutput.receiving_address,
288 | value: helper.to_satoshis(curOutput.output_value),
289 | });
290 | }
291 |
292 | // if we have an expected unsigned txid, ensure our unsigned transaction matches it
293 | if (Object.prototype.hasOwnProperty.call(data.data, 'expected_unsigned_txid') && psbt.__CACHE.__TX.getId() !== data.data.expected_unsigned_txid) {
294 | return r(new Error("Expected unsigned txid did not match. Please report this error to support@block.io."));
295 | }
296 |
297 | // this will cause warnings where we haven't provided full tx hex for non-segwit inputs
298 | // the console.warn messages will only be when using psbt.signInput
299 | psbt.__CACHE.__UNSAFE_SIGN_NONSEGWIT = true;
300 | let res = {tx_type: data.data.tx_type, tx_hex: null, signatures: null}; // using null here so JSON.stringify preserves the signatures key when tx is fully signed
301 |
302 | if (can_fully_sign_tx) {
303 | // we can sign this transaction, so do it and serialize the final transaction
304 |
305 | for (let curInput of data.data.inputs) {
306 | let address_data = input_address_data[curInput.spending_address];
307 | let keys_signed = 0;
308 |
309 | for (let pubkey of address_data.public_keys) {
310 | if (this.keys[pubkey] !== undefined) {
311 | psbt.signInput(curInput.input_index, this.keys[pubkey].ecpair);
312 | keys_signed += 1;
313 | }
314 | if (keys_signed === address_data.required_signatures) {
315 | // we have all the signatures for this input
316 | break;
317 | }
318 | }
319 | }
320 |
321 | // prepare the final transaction and record its serialized hex
322 | // disable fee check (litecoin+dogecoin)
323 | res.tx_hex = psbt.finalizeAllInputs().extractTransaction(true).toHex();
324 |
325 | } else {
326 | // we can't sign the full transaction, so we will return our signatures and the unsigned tx hex to Block.io
327 | // Block.io will then append its signatures to the transaction to finalize and broadcast it
328 |
329 | res.signatures = [];
330 |
331 | for (let curInput of data.data.inputs) {
332 | // for each input, generate the sig hashes and signatures
333 |
334 | let address_data = input_address_data[curInput.spending_address];
335 | let sigHash = null;
336 |
337 | if (["P2SH", "P2WSH-over-P2SH", "WITNESS_V0"].includes(address_data.address_type)) {
338 |
339 | let redeem_script = helper.get_redeem_script(address_data.required_signatures,
340 | address_data.public_keys,
341 | network_params);
342 |
343 | if (address_data.address_type === "P2SH") {
344 | sigHash = psbt.__CACHE.__TX.hashForSignature(curInput.input_index, redeem_script.output, Bitcoin.Transaction.SIGHASH_ALL).toString('hex');
345 | } else {
346 | sigHash = psbt.__CACHE.__TX.hashForWitnessV0(curInput.input_index, redeem_script.output,
347 | helper.to_satoshis(curInput.input_value), Bitcoin.Transaction.SIGHASH_ALL).toString('hex');
348 | }
349 |
350 | } else {
351 | // P2WPKH, P2PKH, P2WPKH-over-P2SH won't get here, so if they do, it's an error
352 | return r(new Error("Cannot have partial signatures for address_type=" + address_data.address_type));
353 | }
354 |
355 | // sign all we can
356 | for (let pubkey of address_data.public_keys) {
357 | if (this.keys[pubkey] !== undefined) {
358 | let sig = this.keys[pubkey].signHex(sigHash);
359 | res.signatures.push({input_index: curInput.input_index, public_key: pubkey, signature: sig});
360 | }
361 | }
362 | }
363 |
364 | // the unsigned transaction we just signed
365 | res.tx_hex = psbt.__CACHE.__TX.toHex();
366 | }
367 |
368 | // remove all stored keys
369 | this.keys = [];
370 |
371 | // return tx_type, tx_hex, signatures
372 | return f(res);
373 | });
374 |
375 | return cbPromiseHelper(promise, cb);
376 |
377 | };
378 |
379 | BlockIo.prototype._prepare_sweep_transaction = function (path, args, cb) {
380 | const promise = new Promise((f,r) => {
381 |
382 | if (!args || typeof(args) !== 'object' ||
383 | typeof(args.private_key) !== 'string') {
384 |
385 | return r(new Error(ERR_WIF_MIS));
386 |
387 | }
388 |
389 | if (!args.to_address) {
390 | return r(new Error(ERR_DEST_MIS));
391 | }
392 |
393 | this.getNetworkParams().then(network => {
394 |
395 | let key = null;
396 | try {
397 | key = ECKey.fromWIF(args.private_key, network);
398 | } catch (ex) {
399 | return r(ex);
400 | }
401 |
402 | if (!(key instanceof ECKey)) {
403 | r(new Error(ERR_WIF_INV))
404 | }
405 |
406 | // honor lowR option
407 | key.lowR = this.options.lowR;
408 | args.public_key = key.pub.toString('hex');
409 |
410 | this.keys[args.public_key] = key; // store for later
411 | delete args.private_key;
412 |
413 | this._request(path, {to_address: args.to_address, public_key: args.public_key}).then(res => {
414 |
415 | if (typeof(res) !== 'object' ||
416 | typeof(res.data) !== 'object' ||
417 | !Object.prototype.hasOwnProperty.call(res.data, 'inputs')) {
418 |
419 | return r(new Error('Invalid response from ' + path));
420 |
421 | } else { return f(res); }
422 |
423 | }).then(f).catch(r);
424 |
425 | }).catch(r);
426 |
427 | });
428 |
429 | return cbPromiseHelper(promise, cb);
430 |
431 | };
432 |
433 | BlockIo.prototype._constructURL = function (path, query) {
434 | return [
435 | 'https://',
436 | this.server, (this.server) ? '.' : '', // eg: 'dev.'
437 | BlockIo.HOST, // block.io
438 | (this.port) ? ':' : '', this.port, // eg: :80
439 | '/api/v', (this.version).toString(10), '/', // eg: /api/v1/
440 | path, // eg: get_balance
441 | query ? ['?', qs.stringify(query)].join('') : '' // eg: ?api_key=abc
442 | ].join('');
443 | };
444 |
445 | BlockIo.prototype._request = function (path, args, cb) {
446 | const url = this._constructURL(path, {api_key: this.api_key});
447 | const method = BlockIo.decideRequestMethod(args);
448 |
449 | const promise = new Promise((f,r) => {
450 | const next = BlockIo.parseResponse(function (e, d) {
451 | if (e) {
452 | if (d) {
453 | e.data = d;
454 | }
455 | return r(e);
456 | }
457 | f(d);
458 | });
459 |
460 | this.client.request(method, url, args)
461 | .then(data => next(null, data.res, data.body))
462 | .catch(r);
463 | });
464 |
465 | return cbPromiseHelper(promise, cb);
466 | };
467 |
468 | BlockIo.decideRequestMethod = function (data) {
469 | return (typeof data != 'object' || data === null ||
470 | Object.keys(data).length === 0) ? 'GET' : 'POST';
471 | }
472 |
473 | BlockIo.parseResponse = function (cb) {
474 |
475 | return function (err, res, body) {
476 | if (err) return cb(err);
477 |
478 | let errOut = null;
479 | let errMsg = ERR_UNKNOWN;
480 | let data = null;
481 |
482 | try {
483 | data = JSON.parse(body);
484 | } catch (ex) {
485 | errOut = ex;
486 | }
487 |
488 | if (data !== null && (typeof(data) !== 'object' || data.status !== 'success')) {
489 | if (data.error_message) errMsg = data.error_message;
490 | if (data.data && typeof(data.data) === 'object' &&
491 | data.data.error_message) errMsg = data.data.error_message;
492 | errOut = new Error(errMsg);
493 | }
494 |
495 | return cb(errOut, data);
496 | };
497 |
498 | };
499 |
500 | BlockIo.prototype.validate_key = function (cb) {
501 | // Test if the key is valid by doing a simple balance check
502 | const promise = new Promise((f,r) => {
503 | this._request('get_balance', {}).then(() => f(true)).catch(r);
504 | });
505 |
506 | return cbPromiseHelper(promise, cb);
507 | };
508 |
509 | BlockIo.prototype.get_network = function (cb) {
510 | // Get the network name from 'get_balance' endpoint
511 | const promise = new Promise((f,r) => {
512 | this._request('get_balance', {}).then(res => {
513 | return res.data.network && f(res.data.network) || r(new Error(ERR_UNKNOWN));
514 | }).catch(r);
515 | });
516 |
517 | return cbPromiseHelper(promise, cb);
518 | };
519 |
520 | BlockIo.prototype.getNetworkParams = function (cb) {
521 | const promise = new Promise((f,r) => {
522 | this.get_network().then(res => {
523 | const params = networks.getNetwork(res);
524 | return params && f(params) || r(new Error(ERR_NET_UNK));
525 | }).catch(r);
526 | });
527 |
528 | return cbPromiseHelper(promise, cb);
529 | }
530 |
531 | BlockIo.prototype._cloneOptions = function (options) {
532 | Object.keys(options).forEach( (k) => this.options[k] = options[k]);
533 | };
534 |
535 | BlockIo._constructMethod = function (type, callMethod) {
536 | const fn = ['_', type].join('');
537 | return function (args, cb) {
538 |
539 | // handle overload for function (cb), without args
540 | if (!cb && typeof(args) === 'function') {
541 | cb = args;
542 | args = {};
543 | }
544 |
545 | return this[fn](callMethod, args, cb);
546 | };
547 | };
548 |
549 | // generate methods for each valid call method
550 | BlockIo.PASSTHROUGH_METHODS.forEach(function (method) {
551 | BlockIo.prototype[method] = BlockIo._constructMethod('request', method);
552 | });
553 |
554 | BlockIo.SWEEP_METHODS.forEach(function (method) {
555 | BlockIo.prototype[method] = BlockIo._constructMethod('prepare_sweep_transaction', method);
556 | });
557 |
558 | function cbPromiseHelper(promise, cb) {
559 | if (typeof cb == 'function') {
560 | promise.then(d => cb(null, d)).catch(e => cb(e));
561 | } else {
562 | return promise;
563 | }
564 | }
565 |
566 | module.exports = BlockIo;
567 |
--------------------------------------------------------------------------------
/lib/helper.js:
--------------------------------------------------------------------------------
1 | const crypto = require('crypto');
2 | const pkbdf2 = require('pbkdf2');
3 | const ECKey = require('./key');
4 | const Bitcoin = require('bitcoinjs-lib');
5 |
6 | const Helper = Object.create(null);
7 |
8 | Helper.get_redeem_script = function(required_signatures, public_keys, network_params) {
9 | // returns the appropriate redeem script
10 | let redeem_script = Bitcoin.payments.p2ms({m: required_signatures,
11 | pubkeys: public_keys.map(function(p){ return Buffer.from(p, 'hex'); }),
12 | network: network_params});
13 | return redeem_script;
14 | };
15 |
16 | Helper.from_satoshis = function(num) {
17 | // return a human readable string (8 decimal places) given value in satoshis
18 |
19 | if (num >= Number.MAX_SAFE_INTEGER)
20 | throw(new Error("Number exceeds or equals MAX_SAFE_INTEGER"));
21 |
22 | let sat_str = ["000000000", num.toString()].join("");
23 | let fraction_str = sat_str.slice(-8);
24 | let whole_str = sat_str.slice(0, -1 * fraction_str.length).replace(/^(0)+/,'');
25 |
26 | if (whole_str.length === 0)
27 | whole_str = '0';
28 |
29 | let response = [whole_str, fraction_str].join('.');
30 |
31 | if (num !== Helper.to_satoshis(response))
32 | throw(new Error("Expected to_satoshis=" + num.toString() + " but got " + Helper.to_satoshis(response).toString()));
33 |
34 | return response;
35 |
36 | };
37 |
38 | Helper.to_satoshis = function (num_str) {
39 | let splits = num_str.split(".");
40 |
41 | if (splits.length !== 2 || splits[1].length !== 8) {
42 | throw(new Error("All numbers must be 8 decimal places exactly"));
43 | }
44 |
45 | let num = parseInt(splits.join(""));
46 |
47 | if (num >= Number.MAX_SAFE_INTEGER)
48 | throw(new Error("Number exceeds or equals MAX_SAFE_INTEGER"));
49 |
50 | return num;
51 |
52 | };
53 |
54 | Helper.encrypt = function (data, key, iv, cipher_type, auth_data) {
55 |
56 | if (!Buffer.isBuffer(key)) key = Buffer.from(key, 'base64');
57 | if (!iv) iv = '';
58 | if (!cipher_type) cipher_type = "AES-256-ECB"; // legacy
59 | if (!auth_data) auth_data = "";
60 |
61 | const cipher = crypto.createCipheriv(cipher_type.toLowerCase(), key, Buffer.from(iv, 'hex'));
62 |
63 | if (cipher_type === "AES-256-GCM") cipher.setAAD(Buffer.from(auth_data, 'hex'));
64 |
65 | const bufs = [];
66 | bufs.push(cipher.update(data, 'utf-8'));
67 | bufs.push(cipher.final());
68 |
69 | const ctLength = bufs[0].length + bufs[1].length;
70 | const ciphertext = Buffer.concat(bufs, ctLength);
71 |
72 | // the response will contain information about how this encryption was done
73 | let response = {};
74 | response.aes_iv = iv;
75 | response.aes_cipher_text = ciphertext.toString('base64');
76 | response.aes_auth_tag = null;
77 | response.aes_auth_data = null;
78 | response.aes_cipher = cipher_type;
79 | if (cipher_type === "AES-256-GCM") {
80 | response.aes_auth_tag = cipher.getAuthTag().toString('hex');
81 | response.aes_auth_data = auth_data;
82 | }
83 |
84 | return response;
85 | };
86 |
87 | Helper.decrypt = function (ciphertext, key, iv, cipher_type, auth_tag, auth_data) {
88 |
89 | if (!Buffer.isBuffer(key)) key = Buffer.from(key, 'base64');
90 | if (!iv) iv = '';
91 | if (!cipher_type) cipher_type = "AES-256-ECB"; // legacy
92 | if (!auth_data) auth_data = "";
93 |
94 | const cipher = crypto.createDecipheriv(cipher_type.toLowerCase(), key, Buffer.from(iv, 'hex'));
95 |
96 | if (cipher_type === "AES-256-GCM") {
97 | // set the auth tag and auth data for GCM
98 |
99 | if (auth_tag.length !== 32) throw new Error("Auth tag must be 16 bytes exactly.");
100 |
101 | cipher.setAuthTag(Buffer.from(auth_tag, 'hex'));
102 | cipher.setAAD(Buffer.from(auth_data, 'hex'));
103 | }
104 |
105 | const bufs = [];
106 | bufs.push(cipher.update(ciphertext, 'base64'));
107 | bufs.push(cipher.final());
108 |
109 | const tLength = bufs[0].length + bufs[1].length;
110 | const text = Buffer.concat(bufs, tLength);
111 |
112 | return text.toString('utf-8');
113 | };
114 |
115 | Helper.pinToKey = function (pin, salt, iterations, hash_function, phase1_key_length, phase2_key_length) {
116 | if (!salt) salt = '';
117 | if (!iterations) iterations = 2048;
118 | if (!phase1_key_length) phase1_key_length = 16;
119 | if (!phase2_key_length) phase2_key_length = 32;
120 | if (!hash_function) hash_function = 'sha256';
121 |
122 | if (hash_function.toLowerCase() !== "sha256")
123 | throw(new Error("Unknown hash function specified. Are you using current version of this library?"));
124 |
125 | let buf;
126 | buf = pkbdf2.pbkdf2Sync(pin, salt, iterations / 2, phase1_key_length, hash_function);
127 | buf = pkbdf2.pbkdf2Sync(buf.toString('hex'), salt, iterations / 2, phase2_key_length, hash_function);
128 |
129 | return buf.toString('base64');
130 | };
131 |
132 | Helper.dynamicExtractKey = function (user_key, pin) {
133 | // uses the appropriate algorithm to decrypt user's private key
134 |
135 | // legacy
136 | let algorithm = JSON.parse("{\"pbkdf2_salt\":\"\",\"pbkdf2_iterations\":2048,\"pbkdf2_hash_function\":\"SHA256\",\"pbkdf2_phase1_key_length\":16,\"pbkdf2_phase2_key_length\":32,\"aes_iv\":null,\"ae\
137 | s_cipher\":\"AES-256-ECB\",\"aes_auth_tag\":null,\"aes_auth_data\":null}");
138 |
139 | // we got the algorithm, so use that instead
140 | if (user_key.algorithm) algorithm = user_key.algorithm;
141 |
142 | const aes_key = this.pinToKey(pin, algorithm.pbkdf2_salt, algorithm.pbkdf2_iterations, algorithm.pbkdf2_hash_function, algorithm.pbkdf2_phase1_key_length,
143 | algorithm.pbkdf2_phase2_key_length);
144 | const decrypted = this.decrypt(user_key.encrypted_passphrase, aes_key,
145 | algorithm.aes_iv, algorithm.aes_cipher,
146 | algorithm.aes_auth_tag,
147 | algorithm.aes_auth_data);
148 |
149 | return ECKey.fromPassphrase(decrypted);
150 | };
151 |
152 | Helper.extractKey = function (encrypted_data, b64_enc_key) {
153 | const decrypted = this.decrypt(encrypted_data, b64_enc_key);
154 | return ECKey.fromPassphrase(decrypted);
155 | };
156 |
157 | Helper.signInputs = function (privkey, inputs) {
158 | if (!(privkey instanceof ECKey)) return inputs;
159 |
160 | const pubkey = privkey.pub.toString('hex');
161 |
162 | inputs.forEach(function (input) {
163 | input.signers.forEach(function (signer) {
164 |
165 | if (signer.signer_public_key !== pubkey) return;
166 | signer.signed_data = privkey.signHex(input.data_to_sign);
167 |
168 | });
169 | });
170 |
171 | return inputs;
172 | };
173 |
174 | module.exports = Helper;
175 |
--------------------------------------------------------------------------------
/lib/httpsclient.js:
--------------------------------------------------------------------------------
1 | const https = require('https');
2 | const axios = require('axios');
3 |
4 | const DEFAULT_AGENT_OPTIONS = {
5 | keepAlive: true,
6 | keepAliveMsecs: 60000,
7 | timeout: 60000,
8 | };
9 |
10 | function HttpsClient(options, userAgent) {
11 | const agentOpts = cloneOrReplace(DEFAULT_AGENT_OPTIONS, options || {});
12 | setProp(this, 'agent', new https.Agent(agentOpts));
13 | setProp(this, 'userAgent', userAgent);
14 | }
15 |
16 | HttpsClient.prototype.request = function (method, url, data) {
17 |
18 | return new Promise((f) => {
19 | axios({
20 | timeout: DEFAULT_AGENT_OPTIONS.timeout,
21 | headers: {'User-Agent': this.userAgent, 'Content-Type': 'application/json'},
22 | method: method,
23 | httpsAgent: this.agent,
24 | url: url,
25 | data: data
26 | }).then(function (response) {
27 | f({res: response, body: JSON.stringify(response.data)});
28 | }).catch(function(e){
29 | if(Object.prototype.hasOwnProperty.call(e, 'response') && Object.prototype.hasOwnProperty.call(e.response, 'data')) {
30 | f({res: e.response, body: JSON.stringify(e.response.data)});
31 | } else {
32 | f({res: {}, body: JSON.stringify({data: {error_message: e.code}})});
33 | }
34 | });
35 | });
36 |
37 | }
38 |
39 | // convenience function for setting property to an obj
40 | function setProp(obj, name, value) {
41 | Object.defineProperty(obj, name, {value: value, writeable: false, enumerable: true});
42 | }
43 |
44 | // for each key in a, if a key in b exists, use b's value, otherwise use a's
45 | function cloneOrReplace(a, b) {
46 | const out = Object.create(null);
47 |
48 | Object.keys(a).forEach(k => {
49 | if (['__proto__', 'constructor'].indexOf(k) !== -1) return;
50 |
51 | const value = Object.prototype.hasOwnProperty.call(b, k) ? b[k] : a[k];
52 | setProp(out, k, value);
53 | });
54 |
55 | return out;
56 | }
57 |
58 | module.exports = HttpsClient;
59 |
--------------------------------------------------------------------------------
/lib/key.js:
--------------------------------------------------------------------------------
1 | const crypto = require('crypto');
2 | const Bitcoin = require('bitcoinjs-lib');
3 | const ecc = require('tiny-secp256k1');
4 | const ecpair = require('ecpair');
5 | const ecpairFactory = ecpair.ECPairFactory(ecc);
6 |
7 | function fixed_size_low_r_sign(d, hash, lowR) {
8 | // forces low R signatures so 3rd byte of DER sig (size of R) is 0x20 exactly
9 | // our tests use this because our other libraries use fixed size R values
10 | if (!d) throw new Error('Missing private key');
11 | if (lowR === undefined) lowR = true;
12 | if (lowR === false) {
13 | return Buffer.from(ecc.sign(hash, d).buffer);
14 | } else {
15 | let sig = Buffer.from(ecc.sign(hash, d).buffer);
16 | const extraData = Buffer.alloc(32, 0);
17 | let counter = 0;
18 | // if first try is lowR, skip the loop
19 | // for second try and on, add extra entropy counting up
20 | while (Bitcoin.script.signature.encode(sig, Bitcoin.Transaction.SIGHASH_ALL)[3] !== 0x20 || sig[0] > 0x7f) {
21 | counter++;
22 | extraData.writeUIntLE(counter, 0, 6);
23 | sig = Buffer.from(ecc.sign(hash, d, extraData).buffer);
24 | }
25 | return sig;
26 | }
27 |
28 | }
29 |
30 | function Key (d, compressed) {
31 | const pair = Buffer.isBuffer(d) ? ecpairFactory.fromPrivateKey(d, {compressed: compressed}) : d;
32 |
33 | Object.defineProperty(this, 'ecpair', { value: pair, writable: false });
34 |
35 | Object.defineProperty(this, 'pub', { get: () => this.ecpair.publicKey });
36 | Object.defineProperty(this, 'priv', { get: () => this.ecpair.privateKey });
37 | Object.defineProperty(this, 'lowR', {
38 | get: () => this.ecpair.lowR,
39 | set: val => { this.ecpair.lowR = !!val },
40 | });
41 |
42 | // lowR is default
43 | this.lowR = true;
44 |
45 | }
46 |
47 | // inherit factory ECKey.makeRandom();
48 | Key.makeRandom = function () {
49 | const pair = ecpairFactory.makeRandom.apply(ecpair.ECPairAPI, arguments);
50 | return new Key(pair);
51 | };
52 |
53 | // inherit factory ECKey.makeRandom();
54 | Key.fromWIF = function () {
55 | const pair = ecpairFactory.fromWIF.apply(ecpair.ECPairAPI, arguments);
56 | return new Key(pair);
57 | };
58 |
59 | Key.prototype.signHex = function (hexData) {
60 | const buf = Buffer.from(hexData, 'hex');
61 | const sig = fixed_size_low_r_sign(this.ecpair.__D, buf, this.lowR); //Buffer.from(this.ecpair.sign(buf.buffer)
62 | const scriptSig = Bitcoin.script.signature.encode(sig, Bitcoin.Transaction.SIGHASH_ALL);
63 | return scriptSig.slice(0, scriptSig.length-1).toString('hex');
64 | };
65 |
66 | Key.fromBuffer = function (buf) {
67 | //assert(Buffer.isBuffer(buf));
68 | const pair = ecpairFactory.fromPrivateKey(buf, {compressed: true});
69 | return new Key(pair);
70 | };
71 |
72 | Key.fromHex = function (hexKey) {
73 | return this.fromBuffer(Buffer.from(hexKey, 'hex'));
74 | };
75 |
76 | Key.fromPassphrase = function(pass) {
77 | const buf = Buffer.isBuffer(pass) ? pass : Buffer.from(pass, 'hex');
78 | const hash = crypto.createHash('sha256').update(buf).digest();
79 | return this.fromBuffer(hash);
80 | };
81 |
82 | Key.fromPassphraseString = function(pass) {
83 | return this.fromPassphrase(Buffer.from(pass));
84 | };
85 |
86 | module.exports = Key;
87 |
--------------------------------------------------------------------------------
/lib/networks.js:
--------------------------------------------------------------------------------
1 | const Bitcoin = require('bitcoinjs-lib');
2 |
3 | // make sure network data is extended
4 | require('../data/networks');
5 |
6 | const networks = module.exports = {};
7 | const MAPPING = networks.MAPPING = {
8 | 'BTC': 'bitcoin',
9 | 'DOGE': 'dogecoin',
10 | 'LTC': 'litecoin',
11 | 'BTCTEST': 'testnet',
12 | 'DOGETEST': 'dogecoin_testnet',
13 | 'LTCTEST': 'litecoin_testnet'
14 | };
15 |
16 | networks.getNetwork = function (net) {
17 | return Bitcoin.networks[MAPPING[net]];
18 | };
19 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "block_io",
3 | "description": "Block.io API wrapper for node.js",
4 | "keywords": [
5 | "block.io",
6 | "block_io",
7 | "bitcoin",
8 | "litecoin",
9 | "dogecoin",
10 | "wallet"
11 | ],
12 | "version": "4.2.2",
13 | "preferGlobal": false,
14 | "homepage": "https://github.com/BlockIo/block_io-nodejs",
15 | "author": "Patrick Lodder (https://github.com/patricklodder)",
16 | "repository": {
17 | "type": "git",
18 | "url": "git://github.com/BlockIo/block_io-nodejs.git"
19 | },
20 | "bugs": {
21 | "url": "https://github.com/BlockIo/block_io-nodejs/issues"
22 | },
23 | "directories": {
24 | "lib": "./lib"
25 | },
26 | "main": "./lib/block_io.js",
27 | "dependencies": {
28 | "axios": "^1.5.1",
29 | "bitcoinjs-lib": "^6.1.5",
30 | "ecpair": "^2.1.0",
31 | "pbkdf2": "^3.1.2",
32 | "tiny-secp256k1": "^2.2.3"
33 | },
34 | "devDependencies": {
35 | "eslint": "^8.0",
36 | "tape": "^5.6"
37 | },
38 | "scripts": {
39 | "test": "node test/unit/cryptohelper.js && node test/unit/key.js && node test/unit/bitcoinjs-lib.js && node test/unit/prepare_transaction.js && node test/unit/httpsclient.js && npx eslint ."
40 | },
41 | "license": "MIT",
42 | "engines": {
43 | "node": "^21 || ^20 || ^19 || ^18 || ^17 || ^16 || ^14"
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/test/helpers/cache.js:
--------------------------------------------------------------------------------
1 | const c = {};
2 | const l = {};
3 |
4 | const cache = function (k, v) {
5 | if (v) {
6 | c[k] = v;
7 | if (l[k]) l[k].forEach(fn => fn(v));
8 | } else return c[k];
9 | };
10 |
11 | cache.lazy = function (k) { return function () { return cache(k); }}
12 | cache.on = function (k, fn) { if (!l[k]) l[k] = []; l[k].push(fn); if (c[k]) fn(c[k]); }
13 |
14 | cache.require = function (keys, fn) {
15 |
16 | const _wrapper = function () {
17 | if (keys.some(k => !Object.prototype.hasOwnProperty.call(c, k))) return;
18 | fn(keys.map(k => c[k]));
19 | }
20 |
21 | keys.forEach(k => cache.on(k, _wrapper));
22 | }
23 |
24 | module.exports = cache;
25 |
--------------------------------------------------------------------------------
/test/helpers/clienttest.js:
--------------------------------------------------------------------------------
1 | const SUCCESS_CHECKS = [
2 | function (t, args) {
3 | t.equal(args[0], null, 'must not return an error');
4 | },
5 | function (t, args) {
6 | t.equal((typeof args[1]), 'object', 'must return a result object');
7 | },
8 | function (t, args) {
9 | t.equal((args[1] && args[1].status), 'success', 'must return status success');
10 | },
11 | ];
12 |
13 | const TX_CHECKS = [
14 | function (t, args) {
15 | t.equal(typeof args[1].data.txid, 'string', 'must return a txid');
16 | },
17 | checkNumeric('amount_withdrawn', 'must return an amount withdrawn'),
18 | checkNumeric('amount_sent', 'must return an amount sent'),
19 | checkNumeric('network_fee', 'must return a network fee'),
20 | checkNumeric('blockio_fee', 'must return a blockio fee'),
21 | ];
22 |
23 | const ERR_CHECKS = [
24 | (t, args) => {
25 | t.ok(args[0] instanceof Error, 'must return an error in callback');
26 | }
27 | ];
28 |
29 | const ERR_DATA_CHECK = (t, args) => {
30 | t.ok(typeof args[0].data == 'object', 'must return response data');
31 | }
32 |
33 | function ClientTest(framework, client) {
34 | this.framework = framework;
35 | this.client = client;
36 | this._tests = [];
37 | this._postProcess = [];
38 | this._title = "test";
39 | this._payload = {};
40 | }
41 |
42 | ClientTest.prototype.method = function (method) {
43 | this._method = method;
44 | return this;
45 | }
46 |
47 | ClientTest.prototype.payload = function (obj) {
48 | this._payload = obj;
49 | return this;
50 | }
51 |
52 | ClientTest.prototype.title = function (str) {
53 | this._title = str;
54 | return this;
55 | }
56 |
57 | ClientTest.prototype.succeeds = function () {
58 | SUCCESS_CHECKS.forEach(fn => this._tests.push(fn));
59 | return this;
60 | }
61 |
62 | ClientTest.prototype.fails = function () {
63 | ERR_CHECKS.forEach(fn => this._tests.push(fn));
64 | return this;
65 | }
66 |
67 | ClientTest.prototype.failsServerSide = function () {
68 | this._tests.push(ERR_DATA_CHECK);
69 | return this.fails();
70 | }
71 |
72 | ClientTest.prototype.returnsTx = function () {
73 | TX_CHECKS.forEach(fn => this._tests.push(fn));
74 | return this;
75 | }
76 |
77 | ClientTest.prototype.check = function (fn) {
78 | this._tests.push(fn);
79 | return this;
80 | }
81 |
82 | ClientTest.prototype.numericResult = function (attr, text) {
83 | this._tests.push(checkNumeric(attr, text));
84 | return this;
85 | }
86 |
87 | ClientTest.prototype.returnsAttrs = function (attrs) {
88 | if (!Array.isArray(attrs)) attrs = [attrs];
89 | attrs.forEach(a => this._tests.push(checkDataAttr(a)));
90 | return this;
91 | }
92 |
93 | ClientTest.prototype.postProcess = function (fn) {
94 | this._postProcess.push(fn);
95 | return this;
96 | }
97 |
98 | ClientTest.prototype.execute = function () {
99 | const test = this;
100 |
101 | return new Promise(f => {
102 | test.framework(test._title, t => {
103 | t.plan(1 + this._tests.length);
104 |
105 | t.doesNotThrow(() => {
106 | test.client[test._method].call(test.client, test._payload, function () {
107 | test._result = arguments;
108 |
109 | test._tests.forEach(fn => fn(t, test._result));
110 |
111 | if (test._postProcess.length) {
112 | test._postProcess.forEach(fn => process.nextTick(() => fn(test._result)));
113 | }
114 |
115 | f(test);
116 | });
117 | }, undefined, 'must not throw an Error');
118 | });
119 | });
120 | }
121 |
122 | ClientTest.create = function (framework, client) {
123 | return new ClientTest(framework, client);
124 | }
125 |
126 | function checkNumeric(attr, text) {
127 | return function (t, args) {
128 | t.ok(!isNaN(parseFloat(args[1].data[attr])), text);
129 | };
130 | }
131 |
132 | function checkDataAttr(attr) {
133 | return function (t, args) {
134 | t.ok(
135 | Object.prototype.hasOwnProperty.call(args[1].data, attr),
136 | 'must return ' + attr
137 | );
138 | };
139 | }
140 |
141 | module.exports = ClientTest;
142 |
--------------------------------------------------------------------------------
/test/helpers/generic.js:
--------------------------------------------------------------------------------
1 | const cache = require('./cache');
2 |
3 | let loggedEnvError = false;
4 |
5 | module.exports = {
6 | FEES: {BTC: 0.00001, BTCTEST: 0.00001, DOGE: 1, DOGETEST: 1, LTC: 0.0001, LTCTEST: 0.0001},
7 |
8 | checkEnv: function () {
9 | if (!process.env.BLOCK_IO_API_KEY || !process.env.BLOCK_IO_PIN) {
10 | if (!loggedEnvError) {
11 | console.log('ERROR: Need valid BLOCK_IO_API_KEY and BLOCK_IO_PIN environment variables!');
12 | console.log([
13 | ' provided: BLOCK_IO_API_KEY: "', process.env.BLOCK_IO_API_KEY,
14 | '"; BLOCK_IO_PIN: "', process.env.BLOCK_IO_PIN ? '[masked]' : '', '"'
15 | ].join(''));
16 | loggedEnvError = true;
17 | }
18 | return false;
19 | }
20 | return true;
21 | },
22 |
23 | calcWithdrawalAmount: function () {
24 | return (cache('minFee') * 3).toFixed(5);
25 | },
26 |
27 | calcDTrustWithdrawalAmount: function () {
28 | return (cache('minFeeDTrust') * 5).toFixed(5);
29 | },
30 |
31 | hasProp: function (obj, prop) {
32 | return Object.prototype.hasOwnProperty.call(obj, prop);
33 | },
34 |
35 | };
36 |
--------------------------------------------------------------------------------
/test/integration/api.js:
--------------------------------------------------------------------------------
1 | const test = require('tape');
2 | const cache = require('../helpers/cache');
3 | const helper = require('../helpers/generic');
4 | const CT = require('../helpers/clienttest');
5 |
6 | const BlockIo = require('../../lib/block_io');
7 |
8 | const API_KEY = process.env.BLOCK_IO_API_KEY;
9 | const PIN = process.env.BLOCK_IO_PIN;
10 | const VERSION = process.env.BLOCK_IO_VERSION || BlockIo.DEFAULT_VERSION;
11 | const SERVER = process.env.BLOCK_IO_SERVER || '';
12 | const PORT = process.env.BLOCK_IO_PORT || '';
13 | const NEWLABEL = (new Date()).getTime().toString(36);
14 |
15 | if (process.env.DEBUG) process.on('uncaughtException', function (e) { console.log(e.stack); });
16 |
17 | const client = new BlockIo({
18 | api_key: API_KEY,
19 | version: VERSION,
20 | server: SERVER,
21 | port: PORT
22 | });
23 |
24 | const pinLessClient = new BlockIo({
25 | api_key: API_KEY,
26 | version: VERSION,
27 | server: SERVER,
28 | port: PORT,
29 | options: { allowNoPin: true }
30 | });
31 |
32 | const badApiKeyClient = new BlockIo({
33 | api_key: "1111-1111-1111-1111",
34 | version: VERSION,
35 | server: SERVER,
36 | port: PORT
37 | });
38 |
39 | console.log('URL:', client._constructURL(''));
40 |
41 | if (!helper.checkEnv()) process.exit(1);
42 |
43 | function testInnerTxResult(desc, tx) {
44 | test(desc, t => {
45 | t.plan(6);
46 | t.equal(typeof tx.txid, 'string', 'must return txid');
47 | t.equal(typeof tx.from_green_address, 'boolean', 'must return green address indicator');
48 | t.equal(typeof tx.confirmations, 'number', 'must return a number of confirmations');
49 | t.ok(
50 | Array.isArray(tx.amounts_received) || Array.isArray(tx.amounts_sent),
51 | 'must return an amounts array'
52 | );
53 | t.ok(Array.isArray(tx.senders), 'must return an senders array');
54 | t.equal(typeof tx.confidence, 'number', 'must return confidence');
55 | });
56 | }
57 |
58 | test('Block.IO Node.js API Wrapper', t => {
59 | t.plan(1);
60 | t.ok(VERSION == 1 || VERSION == 2, '[meta] Tests currently support version 1 and 2');
61 | });
62 |
63 | CT.create(test, client).title('Get Balance')
64 | .method('get_balance')
65 | .succeeds()
66 | .execute();
67 |
68 | CT.create(test, client).title('Get New Address')
69 | .method('get_new_address')
70 | .succeeds()
71 | .execute();
72 |
73 | CT.create(test, client).title('Get New Address (with label)')
74 | .method('get_new_address')
75 | .payload({label: NEWLABEL})
76 | .succeeds()
77 | .check((t, args) => t.equal(typeof args[1].data.address, 'string', 'must return an address'))
78 | .check((t, args) => t.equal(args[1].data.label, NEWLABEL, 'must assign the given label'))
79 | .postProcess(args => cache('newAddress', args[1].data.address))
80 | .execute();
81 |
82 | CT.create(test, client).title('Get Addresses')
83 | .method('get_my_addresses')
84 | .succeeds()
85 | .check((t, args) => t.ok(
86 | helper.hasProp(helper.FEES, args[1].data.network),
87 | 'must specify a known network'))
88 | .check((t, args) => t.ok(
89 | Array.isArray(args[1].data.addresses),
90 | 'must return an array of addresses'))
91 | .check((t, args) => t.ok(args[1].data.addresses[0], 'must return at least one address'))
92 | .postProcess(args => {
93 | cache('minFee', helper.FEES[args[1].data.network]);
94 | const hasBalance = args[1].data.addresses.some(function (addr) {
95 | if (parseFloat(addr.available_balance, 10) > (20 * cache('minFee'))) {
96 | cache('fromAddress', addr.address);
97 | cache('fromLabel', addr.label);
98 | return true;
99 | }
100 | return false;
101 | });
102 |
103 | if (!hasBalance) {
104 | console.log('ERROR: Not enough balance to continue tests!');
105 | process.exit(1);
106 | }
107 | })
108 | .execute();
109 |
110 | if (["BTCTEST", "LTCTEST"].indexOf(cache('network')) !== -1) {
111 |
112 | CT.create(test, pinLessClient).title('Get New Address (with witness_v0 type)')
113 | .method('get_new_address')
114 | .payload({ address_type: "WITNESS_V0" })
115 | .succeeds()
116 | .check((t, args) => t.equal(args[1].data.address.length, 62, 'must create a WITNESS_V0 address'))
117 | .execute();
118 |
119 | }
120 |
121 | cache.require(['minFee', 'newAddress', 'fromAddress', 'fromLabel'], () => {
122 |
123 | CT.create(test, client).title('Prepare Transaction From Address')
124 | .method('prepare_transaction')
125 | .payload({
126 | from_addresses: cache('fromAddress'),
127 | to_label: NEWLABEL,
128 | amount: helper.calcWithdrawalAmount()
129 | })
130 | .succeeds()
131 | .returnsAttrs(['expected_unsigned_txid', 'user_key', 'inputs', 'outputs', 'input_address_data'])
132 | .postProcess(args => cache('preparedTransactionFromAddress', args[1]))
133 | .execute();
134 |
135 | CT.create(test, client).title('Prepare Transaction From Label')
136 | .method('prepare_transaction')
137 | .payload({
138 | from_labels: cache('fromLabel'),
139 | payment_address: cache('newAddress'),
140 | amount: helper.calcWithdrawalAmount()
141 | })
142 | .succeeds()
143 | .returnsAttrs(['expected_unsigned_txid', 'user_key', 'inputs', 'outputs', 'input_address_data'])
144 | .execute();
145 |
146 | cache.require(['preparedTransactionFromAddress'], () => {
147 | client.create_and_sign_transaction({data: cache('preparedTransactionFromAddress'), pin: PIN}).then((f) => {
148 | CT.create(test, client).title('Submit Transaction')
149 | .method('submit_transaction')
150 | .payload({
151 | transaction_data: f
152 | })
153 | .succeeds()
154 | .returnsAttrs(['network', 'txid'])
155 | .execute();
156 | });
157 | });
158 |
159 | // for now, since we only have v2,
160 | // assume forward compatibility of everything else
161 |
162 | CT.create(test, client).title('Validate API key (valid key)')
163 | .method('get_balance')
164 | .succeeds()
165 | .returnsAttrs('network')
166 | .execute();
167 |
168 | CT.create(test, badApiKeyClient).title('Validate API key (invalid key)')
169 | .method('get_balance')
170 | .failsServerSide()
171 | .execute();
172 |
173 | CT.create(test, client).title('Get network fee estimate')
174 | .method('get_network_fee_estimate')
175 | .payload({
176 | from_address: cache('fromAddress'),
177 | amounts: helper.calcWithdrawalAmount(),
178 | to_addresses: cache('newAddress')
179 | })
180 | .succeeds()
181 | .numericResult('estimated_network_fee', 'must return fee estimation data')
182 | .execute();
183 |
184 | CT.create(test, client).title('Get address balance (by address)')
185 | .method('get_address_balance')
186 | .payload({ address: cache('fromAddress') })
187 | .succeeds()
188 | .numericResult('available_balance', 'must return available balance')
189 | .numericResult('pending_received_balance', 'must return pending balance')
190 | .execute();
191 |
192 | CT.create(test, client).title('Get address balance (by label)')
193 | .method('get_address_balance')
194 | .payload({ label: cache('fromLabel') })
195 | .succeeds()
196 | .numericResult('available_balance', 'must return available balance')
197 | .numericResult('pending_received_balance', 'must return pending balance')
198 | .execute();
199 |
200 | CT.create(test, client).title('Get Received Transactions')
201 | .method('get_transactions')
202 | .payload({ type: 'received' })
203 | .succeeds()
204 | .returnsAttrs('network')
205 | .check((t, args) => t.ok(Array.isArray(args[1].data.txs), 'must return array of transactions'))
206 | .execute()
207 | .then(pt => {
208 | const txs = pt._result[1].data.txs;
209 | if (txs.length == 0) return;
210 | testInnerTxResult('Get Received Transactions ... inner tx result', txs[0]);
211 | });
212 |
213 | CT.create(test, client).title('Get Sent Transactions')
214 | .method('get_transactions')
215 | .payload({ type: 'sent' })
216 | .succeeds()
217 | .returnsAttrs('network')
218 | .check((t, args) => t.ok(Array.isArray(args[1].data.txs), 'must return array of transactions'))
219 | .execute()
220 | .then(pt => {
221 | const txs = pt._result[1].data.txs;
222 | if (txs.length == 0) return;
223 | testInnerTxResult('Get Sent Transactions ... inner tx result', txs[0]);
224 | });
225 |
226 | CT.create(test, client).title('Archive Address')
227 | .method('archive_address')
228 | .payload({ address: cache('newAddress') })
229 | .succeeds()
230 | .check((t, args) => t.ok(Array.isArray(args[1].data.addresses), 'must return an array of addresses'))
231 | .check((t, args) => t.ok(
232 | args[1].data.addresses.some(a => a.address == cache('newAddress') && a.archived)
233 | , 'must have archived said address'))
234 | .execute()
235 | .then(() => {
236 |
237 | CT.create(test, client).title('After Archive, Search Non-archived Address')
238 | .method('get_my_addresses')
239 | .succeeds()
240 | .check((t, args) => t.equal(
241 | args[1].data.addresses.filter(a => a.address == cache('newAddress')).length,
242 | 0, 'must not contain archived address'
243 | ))
244 | .postProcess(() => cache('archiveSubTest1Done', true))
245 | .execute();
246 |
247 | CT.create(test, client).title('After Archive, Search Archived Address')
248 | .method('get_my_archived_addresses')
249 | .succeeds()
250 | .check((t, args) => t.ok(
251 | args[1].data.addresses.some(a => a.address == cache('newAddress')),
252 | 'must contain archived address'
253 | ))
254 | .postProcess(() => cache('archiveSubTest2Done', true))
255 | .execute();
256 |
257 | });
258 |
259 | cache.require(['archiveSubTest1Done', 'archiveSubTest2Done'], () => {
260 |
261 | CT.create(test, client).title('Unarchive Address')
262 | .method('unarchive_address')
263 | .payload({ address: cache('newAddress') })
264 | .succeeds()
265 | .check((t, args) => t.ok(Array.isArray(args[1].data.addresses), 'must return an array of addresses'))
266 | .check((t, args) => t.ok(
267 | args[1].data.addresses.some(a => a.address == cache('newAddress') && !a.archived)
268 | , 'must have unarchived said address'))
269 | .execute()
270 | .then(() => {
271 |
272 | CT.create(test, client).title('After Unarchive, Search Archived Address')
273 | .method('get_my_archived_addresses')
274 | .succeeds()
275 | .check((t, args) => t.equal(
276 | args[1].data.addresses.filter(a => a.address == cache('newAddress')).length,
277 | 0, 'must not contain archived address'
278 | ))
279 | .execute();
280 |
281 | CT.create(test, client).title('After Unarchive, Search Non-archived Address')
282 | .method('get_my_addresses')
283 | .succeeds()
284 | .check((t, args) => t.ok(
285 | args[1].data.addresses.some(a => a.address == cache('newAddress')),
286 | 'must contain archived address'
287 | ))
288 | .execute();
289 |
290 | });
291 |
292 | });
293 |
294 | });
295 |
--------------------------------------------------------------------------------
/test/integration/dtrust.js:
--------------------------------------------------------------------------------
1 | const test = require('tape');
2 | const cache = require('../helpers/cache');
3 | const helper = require('../helpers/generic');
4 | const CT = require('../helpers/clienttest');
5 |
6 | const BlockIo = require('../../lib/block_io');
7 | const Bitcoin = require('bitcoinjs-lib');
8 |
9 | if (!helper.checkEnv()) process.exit(1);
10 |
11 | if (process.env.DEBUG) process.on('uncaughtException', function (e) { console.log(e.stack); });
12 |
13 | const API_KEY = process.env.BLOCK_IO_API_KEY;
14 | //const PIN = process.env.BLOCK_IO_PIN;
15 | const VERSION = process.env.BLOCK_IO_VERSION || BlockIo.DEFAULT_VERSION;
16 | const SERVER = process.env.BLOCK_IO_SERVER || '';
17 | const PORT = process.env.BLOCK_IO_PORT || '';
18 | const DTRUSTLABEL = ((new Date()).getTime() + 11).toString(36);
19 |
20 | const REQUIRED_SIGS = 2;
21 |
22 | // insecure keys for testing ;)
23 | const KEYS = [
24 | BlockIo.ECKey.fromPassphrase(Buffer.from('key1')),
25 | BlockIo.ECKey.fromPassphrase(Buffer.from('key2')),
26 | BlockIo.ECKey.fromPassphrase(Buffer.from('key3'))
27 | ];
28 |
29 | // Key 0 signs with lowR
30 | KEYS[0].lowR = true;
31 |
32 | const SIG_ADDRS = [
33 | 'nZ7QHcpJ5tpzxQUV8vEnGU4m7zLjkNiMBU',
34 | 'nj8visBXviBNZs5zXkn6DYG6Nc97Nv995g',
35 | 'nUknbqqhSXHATS7SMH7wqf9e9tJcEZb3HY'
36 | ];
37 |
38 | const client = new BlockIo({api_key: API_KEY, version: VERSION, server: SERVER, port: PORT});
39 |
40 | test('Block.IO Node.js DTrust API Wrapper', t => {
41 | t.plan(1);
42 | t.ok(VERSION == 2, '[meta] DTrust currently only supports version 2');
43 | });
44 |
45 | CT.create(test, client).title('Get New DTrust Address')
46 | .method('get_new_dtrust_address')
47 | .payload({
48 | label: DTRUSTLABEL,
49 | required_signatures: REQUIRED_SIGS,
50 | public_keys: KEYS.map(key => key.pub.toString('hex')).join(',')
51 | })
52 | .succeeds()
53 | .returnsAttrs('address')
54 | .check((t, args) => t.equal(
55 | args[1].data.label, DTRUSTLABEL,
56 | 'must assign the given label'))
57 | .check((t, args) => t.equal(
58 | args[1].data.additional_required_signatures, REQUIRED_SIGS,
59 | 'must assign the given number of required signatures'))
60 | .check((t, args) => t.deepEqual(
61 | args[1].data.additional_signers, SIG_ADDRS,
62 | 'must assign the correct addresses'))
63 | .check((t, args) => t.ok(
64 | Bitcoin.script.fromASM(args[1].data.redeem_script),
65 | 'must create a valid redeem script'))
66 | .postProcess(args => cache('newDtrustAddress', args[1].data.address))
67 | .execute();
68 |
69 | CT.create(test, client).title('Get New DTrust Address (too high required sigs)')
70 | .method('get_new_dtrust_address')
71 | .payload({
72 | label: DTRUSTLABEL,
73 | required_signatures: KEYS.length + 1,
74 | public_keys: KEYS.map(key => key.pub.toString('hex')).join(',')
75 | })
76 | .failsServerSide()
77 | .execute();
78 |
79 | CT.create(test, client).title('Get New DTrust Address (duplicate signers)')
80 | .method('get_new_dtrust_address')
81 | .payload({
82 | label: DTRUSTLABEL,
83 | required_signatures: KEYS.length,
84 | public_keys: KEYS.map(() => KEYS[0].pub.toString('hex')).join(',')
85 | })
86 | .failsServerSide()
87 | .execute();
88 |
89 | CT.create(test, client).title('Get DTrust Addresses')
90 | .method('get_my_dtrust_addresses')
91 | .succeeds()
92 | .check((t, args) => t.ok(
93 | helper.hasProp(helper.FEES, args[1].data.network),
94 | 'must specify a known network'))
95 | .check((t, args) => t.ok(
96 | Array.isArray(args[1].data.addresses),
97 | 'must return an array of addresses'))
98 | .check((t, args) => t.ok(
99 | args[1].data.addresses[0],
100 | 'must return at least one address'))
101 | .postProcess(args => {
102 | cache('minFeeDTrust', helper.FEES[args[1].data.network]);
103 | const hasBalance = args[1].data.addresses.some(function (addr) {
104 | if (parseFloat(addr.available_balance, 10) > (20 * cache('minFeeDTrust'))) {
105 | cache('fromDTrustAddress', addr.address);
106 | cache('fromDTrustLabel', addr.label);
107 | return true;
108 | }
109 | return false;
110 | });
111 |
112 | if (!hasBalance) {
113 | console.log('ERROR: Not enough balance to continue tests!');
114 | process.exit(1);
115 | }
116 | })
117 | .execute();
118 |
119 | cache.require(['newDtrustAddress'], () => {
120 | CT.create(test, client).title('Archive Address')
121 | .method('archive_dtrust_address')
122 | .payload({ address: cache('newDtrustAddress') })
123 | .succeeds()
124 | .check((t, args) => t.ok(Array.isArray(args[1].data.addresses), 'must return an array of addresses'))
125 | .check((t, args) => t.ok(
126 | args[1].data.addresses.some(a => a.address == cache('newDtrustAddress') && a.archived)
127 | , 'must have archived said address'))
128 | .execute()
129 | .then(() => {
130 |
131 | CT.create(test, client).title('After Archive, Search Non-archived Address')
132 | .method('get_my_dtrust_addresses')
133 | .succeeds()
134 | .check((t, args) => t.equal(
135 | args[1].data.addresses.filter(a => a.address == cache('newDtrustAddress')).length,
136 | 0, 'must not contain archived address'
137 | ))
138 | .postProcess(() => cache('archiveSubTest1DTrust', true))
139 | .execute();
140 |
141 | CT.create(test, client).title('After Archive, Search Archived Address')
142 | .method('get_my_archived_dtrust_addresses')
143 | .succeeds()
144 | .check((t, args) => t.ok(
145 | args[1].data.addresses.some(a => a.address == cache('newDtrustAddress')),
146 | 'must contain archived address'
147 | ))
148 | .postProcess(() => cache('archiveSubTest2DTrust', true))
149 | .execute();
150 |
151 | });
152 |
153 | cache.require(['archiveSubTest1DTrust', 'archiveSubTest2DTrust'], () => {
154 |
155 | CT.create(test, client).title('Unarchive Address')
156 | .method('unarchive_dtrust_address')
157 | .payload({ address: cache('newDtrustAddress') })
158 | .succeeds()
159 | .check((t, args) => t.ok(Array.isArray(args[1].data.addresses), 'must return an array of addresses'))
160 | .check((t, args) => t.ok(
161 | args[1].data.addresses.some(a => a.address == cache('newDtrustAddress') && !a.archived)
162 | , 'must have unarchived said address'))
163 | .execute()
164 | .then(() => {
165 |
166 | CT.create(test, client).title('After Unarchive, Search Archived Address')
167 | .method('get_my_archived_dtrust_addresses')
168 | .succeeds()
169 | .check((t, args) => t.equal(
170 | args[1].data.addresses.filter(a => a.address == cache('newDtrustAddress')).length,
171 | 0, 'must not contain archived address'
172 | ))
173 | .postProcess(() => cache('unarchiveSubTest1DTrust', true))
174 | .execute();
175 |
176 | CT.create(test, client).title('After Unarchive, Search Non-archived Address')
177 | .method('get_my_dtrust_addresses')
178 | .succeeds()
179 | .check((t, args) => t.ok(
180 | args[1].data.addresses.some(a => a.address == cache('newDtrustAddress')),
181 | 'must contain archived address'
182 | ))
183 | .postProcess(() => cache('unarchiveSubTest2DTrust', true))
184 | .execute();
185 |
186 | });
187 |
188 | });
189 | })
190 |
191 | cache.require([
192 | 'minFeeDTrust', 'newDtrustAddress', 'fromDTrustLabel', 'fromDTrustAddress',
193 | 'unarchiveSubTest1DTrust', 'unarchiveSubTest2DTrust'], () => {
194 |
195 | CT.create(test, client).title('Get network fee estimate')
196 | .method('get_dtrust_network_fee_estimate')
197 | .payload({
198 | from_address: cache('fromDTrustAddress'),
199 | amounts: helper.calcDTrustWithdrawalAmount(),
200 | to_addresses: cache('newDtrustAddress') })
201 | .succeeds()
202 | .numericResult('estimated_network_fee', 'must return fee estimation data')
203 | .execute();
204 |
205 | CT.create(test, client).title('Withdraw from DTrust Address')
206 | .method('prepare_dtrust_transaction')
207 | .payload({
208 | from_addresses: cache('fromDTrustAddress'),
209 | to_label: DTRUSTLABEL,
210 | amount: helper.calcDTrustWithdrawalAmount()
211 | })
212 | .succeeds()
213 | .returnsAttrs(['expected_unsigned_txid', 'inputs', 'outputs', 'input_address_data'])
214 | .check((t, args) => t.ok(
215 | args[1].data.user_key === undefined,
216 | 'must not return an encrypted passphrase'))
217 | .check((t, args) => t.equal(
218 | args[1].data.input_address_data.filter(i => i.required_signatures != REQUIRED_SIGS+1).length, // +1 because Block.io added a required signature when we created the address
219 | 0, 'must require the correct amounts of sigs'))
220 | .postProcess(args => cache('dtrustWithdrawal', args[1]))
221 | .execute();
222 |
223 | });
224 |
225 | cache.require(['dtrustWithdrawal'], () => {
226 | client.create_and_sign_transaction({data: cache('dtrustWithdrawal'), keys: [KEYS[0].priv.toString('hex'), KEYS[1].priv.toString('hex')]}).then((f) => {
227 | CT.create(test,client).title('Sending in 2-key withdrawal sigs')
228 | .method('submit_transaction')
229 | .payload({transaction_data: f})
230 | .succeeds()
231 | .returnsAttrs(['network', 'txid'])
232 | .execute();
233 | });
234 | });
235 |
236 | /* TODO more dTrust integration tests with submit_transaction */
237 | /*
238 | cache.require(['dtrustWithdrawal'], () => {
239 |
240 | const w = cache('dtrustWithdrawal');
241 |
242 | client.create_and_sign_transaction({data: w, keys: [KEYS[0].priv.toString('hex')]}).then((f,r) => {
243 |
244 | // sign with 1 key
245 | CT.create(test, client).title('Sending in single-key withdrawal sigs with low R')
246 | .method('submit_transaction')
247 | .payload({ transaction_data: f })
248 | // .failsServerSide()
249 | .execute();
250 |
251 | // sign with all keys
252 | const s2 = client.create_and_sign_transaction({data: w, keys: KEYS.map(key => key.priv.toString('hex');)});
253 | CT.create(test, client).title('Sending in 3-key withdrawal sigs')
254 | .method('submit_transaction')
255 | .payload({ transaction_data: s3 })
256 | .succeeds()
257 | .returnsTx()
258 | .execute();
259 |
260 | });
261 | */
262 |
--------------------------------------------------------------------------------
/test/unit/bitcoinjs-lib.js:
--------------------------------------------------------------------------------
1 | /*eslint no-mixed-spaces-and-tabs: ["error", "smart-tabs"]*/
2 |
3 | const test = require('tape');
4 | const bitcoin = require('bitcoinjs-lib');
5 | const ecc = require('tiny-secp256k1');
6 | const ecpair = require('ecpair');
7 | const ecpairFactory = ecpair.ECPairFactory(ecc);
8 |
9 | const KEYS = ["ef4fc6cfd682494093bbadf041ba4341afbe22b224432e21a4bc4470c5b939d4",
10 | "123f37eb9a7f24a120969a1b2d6ac4859fb8080cfc2e8d703abae0f44305fc12"];
11 |
12 | const PUBLIC_KEYS = [ecpairFactory.fromPrivateKey(Buffer.from(KEYS[0], 'hex')).publicKey.toString('hex'),
13 | ecpairFactory.fromPrivateKey(Buffer.from(KEYS[1], 'hex')).publicKey.toString('hex')];
14 |
15 | const LTCTEST = {
16 | messagePrefix: '\x19Litecoin Signed Message:\n',
17 | bech32: 'tltc',
18 | bip32: {
19 | public: 0x0436ef7d,
20 | private: 0x0436f6e1,
21 | },
22 | pubKeyHash: 0x6f,
23 | scriptHash: 0x3a,
24 | wif: 0xef,
25 | };
26 |
27 | // these keys will use low R signatures
28 | const KEY1 = ecpairFactory.fromPrivateKey(Buffer.from(KEYS[0], 'hex'));
29 | KEY1.lowR = true;
30 | const KEY2 = ecpairFactory.fromPrivateKey(Buffer.from(KEYS[1], 'hex'));
31 | KEY2.lowR = true;
32 |
33 | const REDEEM_SCRIPT = bitcoin.payments.p2ms({m: 2, pubkeys: [Buffer.from(PUBLIC_KEYS[0], 'hex'), Buffer.from(PUBLIC_KEYS[1], 'hex')], network: LTCTEST});
34 |
35 | const P2PKH_ADDRESS = bitcoin.payments.p2pkh({pubkey: KEY1.publicKey, network: LTCTEST})
36 | const P2WPKH_ADDRESS = bitcoin.payments.p2wpkh({pubkey: KEY1.publicKey, network: LTCTEST});
37 | const P2WPKH_OVER_P2SH_ADDRESS = bitcoin.payments.p2sh({redeem: P2WPKH_ADDRESS});
38 |
39 | const P2SH_ADDRESS = bitcoin.payments.p2sh({redeem: REDEEM_SCRIPT});
40 | const P2WSH_ADDRESS = bitcoin.payments.p2wsh({redeem: REDEEM_SCRIPT});
41 | const P2WSH_OVER_P2SH_ADDRESS = bitcoin.payments.p2sh({redeem: bitcoin.payments.p2wsh({redeem: REDEEM_SCRIPT})})
42 |
43 | const OUTPUT_VALUE = 1000000000;
44 | const FEE = 10000;
45 |
46 | test('generates correct LTCTEST addresses', t => {
47 |
48 | t.plan(7);
49 |
50 | t.doesNotThrow(() => {
51 |
52 | t.equal(P2PKH_ADDRESS.address.toString(), "mwop54ocwGjeErSTLCKgKxrdYp1k9o6Cgk");
53 | t.equal(P2WPKH_ADDRESS.address.toString(), "tltc1qk2erszs7fp407kh94e6v3yhfq2njczjvg4hnz6");
54 | t.equal(P2WPKH_OVER_P2SH_ADDRESS.address.toString(), "Qgn9vENxxnNCPun8CN6KR1PPB7WCo9oxqc");
55 |
56 | t.equal(P2SH_ADDRESS.address.toString(), "QPZMy7ivpYdkJRLhtTx7tj5Fa4doQ2auWk");
57 | t.equal(P2WSH_ADDRESS.address.toString(), "tltc1q6s4cxsg5q4vm0ksst6rxn68h6ksrwumy9tvzgqa6jxuqllxyzh0qxt7q8g");
58 | t.equal(P2WSH_OVER_P2SH_ADDRESS.address.toString(), "QeyxkrKbgKvxbBY1HLiBYjMnZx1HDRMYmd");
59 |
60 | }, undefined, "must not throw any Errors");
61 |
62 | });
63 |
64 |
65 | test('TEST P2SH to P2WSH-over-P2SH', t => {
66 |
67 | t.plan(5);
68 |
69 | let cur_input_value = OUTPUT_VALUE + 0;
70 | let cur_output_value = OUTPUT_VALUE - FEE;
71 |
72 | t.doesNotThrow(() => {
73 |
74 | let psbt = new bitcoin.Psbt({network: LTCTEST});
75 |
76 | psbt.setVersion(1);
77 | psbt.setLocktime(0);
78 |
79 | psbt.addInput({
80 | hash: '4ad80b9776f574a125f89e96bda75bb6fe046f7560847d16446bbdcdc160be62',
81 | index: 1,
82 | sequence: 0xffffffff,
83 | witnessUtxo: {script: REDEEM_SCRIPT.output, value: cur_input_value},
84 | redeemScript: REDEEM_SCRIPT.output
85 | });
86 |
87 | psbt.addOutput({
88 | address: P2WSH_OVER_P2SH_ADDRESS.address.toString(),
89 | value: cur_output_value
90 | });
91 |
92 | // unsigned payload must match
93 | t.equal(psbt.__CACHE.__TX.toHex(),
94 | "010000000162be60c1cdbd6b44167d8460756f04feb65ba7bd969ef825a174f576970bd84a0100000000ffffffff01f0a29a3b0000000017a914c99a494597ade09b5194f9ec8e02d96607ae64798700000000");
95 |
96 | let sigHash0 = psbt.__CACHE.__TX.hashForSignature(0, REDEEM_SCRIPT.output, bitcoin.Transaction.SIGHASH_ALL).toString('hex');
97 |
98 | // sighash0 must match
99 | t.equal(sigHash0, "93a075651d1b6b79cd9bf128bf5e15001fe65865defea6cedab0a1da438f565e");
100 |
101 | psbt.__CACHE.__UNSAFE_SIGN_NONSEGWIT = true;
102 |
103 | psbt.signInput(0, KEY1);
104 | psbt.signInput(0, KEY2);
105 |
106 | let signed_payload = psbt.finalizeAllInputs().extractTransaction();
107 |
108 | // signed payload must match
109 | t.equal(signed_payload.toHex(),
110 | "010000000162be60c1cdbd6b44167d8460756f04feb65ba7bd969ef825a174f576970bd84a01000000d900473044022009143b07279ef6d5317865672e9fc28ada31314abf242ae786917b92cf027ac002207544d055f2b8bb249dc0294d565c6d538f4e04f9b142331fa103d82e0498a181014730440220561f9c23560c6d994c666b9b327f3ef1d9c0b29d0404396d1d6c7a86fc45fc7d02201909041cbe02fc9367f8ce019278629e3f8eae9b7a33fc8223e6fa89e368bd810147522103820317ad251bca573c8fda2b8f26ffc9aae9d5ecb15b50ee08d8f9e009def38e210238de8c9eb2842ecaf0cc61ee6ba23fe4e46f1cfd82eac0910e1d8e865bd76df952aeffffffff01f0a29a3b0000000017a914c99a494597ade09b5194f9ec8e02d96607ae64798700000000");
111 |
112 | t.equal(signed_payload.getId(), "2464c6122378ee5ed9a42d5192e15713b107924d05d15b58254eb7b2030118c7");
113 |
114 | }, undefined, "must not throw any Errors");
115 | });
116 |
117 | test('TEST P2WSH-over-P2SH to P2WPKH', t => {
118 | t.plan(5);
119 |
120 | let cur_input_value = OUTPUT_VALUE - FEE;
121 | let cur_output_value = OUTPUT_VALUE - FEE - FEE;
122 |
123 | t.doesNotThrow(() => {
124 |
125 | let psbt = new bitcoin.Psbt({network: LTCTEST});
126 |
127 | psbt.setVersion(1);
128 | psbt.setLocktime(0);
129 |
130 | psbt.addInput({
131 | hash: "2464c6122378ee5ed9a42d5192e15713b107924d05d15b58254eb7b2030118c7",
132 | index: 0,
133 | sequence: 0xffffffff,
134 | witnessUtxo: {script: bitcoin.payments.p2wsh({redeem: REDEEM_SCRIPT}).output, value: cur_input_value},
135 | witnessScript: REDEEM_SCRIPT.output,
136 | redeemScript: P2WSH_OVER_P2SH_ADDRESS.output
137 | });
138 |
139 | psbt.addOutput({
140 | address: P2WPKH_ADDRESS.address.toString(),
141 | value: cur_output_value
142 | });
143 |
144 | t.equal(psbt.__CACHE.__TX.toHex(),
145 | "0100000001c7180103b2b74e25585bd1054d9207b11357e192512da4d95eee782312c664240000000000ffffffff01e07b9a3b00000000160014b2b2380a1e486aff5ae5ae74c892e902a72c0a4c00000000");
146 |
147 | let sigHash0 = psbt.__CACHE.__TX.hashForWitnessV0(0, REDEEM_SCRIPT.output, cur_input_value, bitcoin.Transaction.SIGHASH_ALL).toString('hex');
148 |
149 | t.equal(sigHash0, "e1c684f769c0e186be215ece3b7c1f3f23985ecbafafe0c8d43936fcd79eafdc");
150 |
151 | psbt.signInput(0, KEY1);
152 | psbt.signInput(0, KEY2);
153 |
154 | let signed_payload = psbt.finalizeAllInputs().extractTransaction();
155 |
156 | t.equal(signed_payload.toHex(),
157 | "01000000000101c7180103b2b74e25585bd1054d9207b11357e192512da4d95eee782312c664240000000023220020d42b8341140559b7da105e8669e8f7d5a03773642ad82403ba91b80ffcc415deffffffff01e07b9a3b00000000160014b2b2380a1e486aff5ae5ae74c892e902a72c0a4c0400473044022067c9f8ed5c8f0770be1b7d44ade72c4d976a2b0e6c4df39ea70923daff26ea5e02205894350de5304d446343fbf95245cd656876a11c94025554bf878b3ecf90db720147304402204ee76a1814b3eb289e492409bd29ebb77088c9c20645c8a63c75bfe44eac41f70220232bcd35a0cc78e88dfa59dc15331023c3d3bb3a8b63e6b753c8ab4599b7bd290147522103820317ad251bca573c8fda2b8f26ffc9aae9d5ecb15b50ee08d8f9e009def38e210238de8c9eb2842ecaf0cc61ee6ba23fe4e46f1cfd82eac0910e1d8e865bd76df952ae00000000");
158 |
159 | t.equal(signed_payload.getId(), "66a78d3cda988e4c90611b192ae5bd02e0fa70c08c3219110c02594802a42c01");
160 |
161 | }, undefined, "must not throw any Errors");
162 | });
163 |
164 |
165 | test('TEST P2WPKH to P2WSH (WITNESS_V0)', t => {
166 | t.plan(5);
167 |
168 | let cur_input_value = OUTPUT_VALUE - FEE - FEE;
169 | let cur_output_value = OUTPUT_VALUE - FEE - FEE - FEE;
170 |
171 | t.doesNotThrow(() => {
172 |
173 | let psbt = new bitcoin.Psbt({network: LTCTEST});
174 | psbt.setVersion(1);
175 | psbt.setLocktime(0);
176 |
177 | psbt.addInput({
178 | hash: '66a78d3cda988e4c90611b192ae5bd02e0fa70c08c3219110c02594802a42c01',
179 | index: 0,
180 | sequence: 0xffffffff,
181 | witnessUtxo: {script: P2WPKH_ADDRESS.output, value: cur_input_value},
182 | });
183 |
184 | psbt.addOutput({
185 | address: P2WSH_ADDRESS.address.toString(),
186 | value: cur_output_value
187 | });
188 |
189 | t.equal(psbt.__CACHE.__TX.toHex(),
190 | "0100000001012ca4024859020c1119328cc070fae002bde52a191b61904c8e98da3c8da7660000000000ffffffff01d0549a3b00000000220020d42b8341140559b7da105e8669e8f7d5a03773642ad82403ba91b80ffcc415de00000000");
191 |
192 | // p2wpkh uses the p2pkh script template for signing
193 | let sigHash0 = psbt.__CACHE.__TX.hashForWitnessV0(0, P2PKH_ADDRESS.output, cur_input_value, bitcoin.Transaction.SIGHASH_ALL).toString('hex');
194 |
195 | t.equal(sigHash0, "ff94560e1ca289de4d661695029f495dde37b16bddd6645fb65c8f61decec22c");
196 |
197 | psbt.signInput(0, KEY1);
198 |
199 | let signed_payload = psbt.finalizeAllInputs().extractTransaction();
200 |
201 | t.equal(signed_payload.toHex(),
202 | "01000000000101012ca4024859020c1119328cc070fae002bde52a191b61904c8e98da3c8da7660000000000ffffffff01d0549a3b00000000220020d42b8341140559b7da105e8669e8f7d5a03773642ad82403ba91b80ffcc415de024730440220436f9c0d1bb66cb507da29ba3a583b152b5265d9eba1f4067612124ea7f536470220414213d205f4becf61481a1c816684b0e9912f4abcc174211b20c88b6158b005012103820317ad251bca573c8fda2b8f26ffc9aae9d5ecb15b50ee08d8f9e009def38e00000000");
203 |
204 | t.equal(signed_payload.getId(), "d14891128bc4c72dfa45269f302edf690289214874c5ee40b118c1d5465319e6");
205 |
206 | }, undefined, "must not throw any Errors");
207 | });
208 |
209 |
210 | test('TEST P2WSH (WITNESS_V0) to P2WPKH-over-P2SH', t => {
211 | t.plan(5);
212 |
213 | let cur_input_value = OUTPUT_VALUE - FEE - FEE - FEE;
214 | let cur_output_value = OUTPUT_VALUE - FEE - FEE - FEE - FEE;
215 |
216 | t.doesNotThrow(() => {
217 |
218 | let psbt = new bitcoin.Psbt({network: LTCTEST});
219 | psbt.setVersion(1);
220 | psbt.setLocktime(0);
221 |
222 | psbt.addInput({
223 | hash: "d14891128bc4c72dfa45269f302edf690289214874c5ee40b118c1d5465319e6",
224 | index: 0,
225 | sequence: 0xffffffff,
226 | witnessUtxo: {script: P2WSH_ADDRESS.output, value: cur_input_value},
227 | witnessScript: REDEEM_SCRIPT.output,
228 | });
229 |
230 | psbt.addOutput({
231 | address: P2WPKH_OVER_P2SH_ADDRESS.address.toString(),
232 | value: cur_output_value
233 | });
234 |
235 | t.equal(psbt.__CACHE.__TX.toHex(),
236 | "0100000001e6195346d5c118b140eec5744821890269df2e309f2645fa2dc7c48b129148d10000000000ffffffff01c02d9a3b0000000017a914dd4edd1406541e476450fda7924720fe19f337b98700000000");
237 |
238 | let sigHash0 = psbt.__CACHE.__TX.hashForWitnessV0(0, REDEEM_SCRIPT.output, cur_input_value, bitcoin.Transaction.SIGHASH_ALL).toString('hex');
239 |
240 | t.equal(sigHash0, "bd77fd23a1e80c3670d7a547ce45031f5f611e4dc49a2eb65def2e6db841e011");
241 |
242 | psbt.signInput(0, KEY1);
243 | psbt.signInput(0, KEY2);
244 |
245 | let signed_payload = psbt.finalizeAllInputs().extractTransaction();
246 |
247 | t.equal(signed_payload.toHex(),
248 | "01000000000101e6195346d5c118b140eec5744821890269df2e309f2645fa2dc7c48b129148d10000000000ffffffff01c02d9a3b0000000017a914dd4edd1406541e476450fda7924720fe19f337b987040047304402205f2cabd4fa34e0947f07454a9d905073a21bb0818a009356481c1acd6f915f6f02203ba6bb8148a1790f4e1939ea74a21dcc8a837595e54323bbba0615a15b779c4901473044022033d8136791bc5658700b385ca5728b9e188a3ba1aa3bc691d6adfd1b8431cee6022073d565e5d1e96c0257f7cefdab946e48fb3857248f49048e00f6b701e97457c30147522103820317ad251bca573c8fda2b8f26ffc9aae9d5ecb15b50ee08d8f9e009def38e210238de8c9eb2842ecaf0cc61ee6ba23fe4e46f1cfd82eac0910e1d8e865bd76df952ae00000000");
249 |
250 | t.equal(signed_payload.getId(), "d76dd93d5afbc8cb3bfd487445fac9f81d7ae409723990f7744f398feae9c0e4");
251 |
252 | }, undefined, "must not throw any Errors");
253 | });
254 |
255 |
256 | test('TEST P2WPKH-over-P2SH to P2PKH', t => {
257 | t.plan(5);
258 |
259 | let cur_input_value = OUTPUT_VALUE - FEE - FEE - FEE - FEE;
260 | let cur_output_value = OUTPUT_VALUE - FEE - FEE - FEE - FEE - FEE;
261 |
262 | t.doesNotThrow(() => {
263 |
264 | let psbt = new bitcoin.Psbt({network: LTCTEST});
265 | psbt.setVersion(1);
266 | psbt.setLocktime(0);
267 |
268 | psbt.addInput({
269 | hash: "d76dd93d5afbc8cb3bfd487445fac9f81d7ae409723990f7744f398feae9c0e4",
270 | index: 0,
271 | sequence: 0xffffffff,
272 | witnessUtxo: {script: P2WPKH_OVER_P2SH_ADDRESS.output, value: cur_input_value},
273 | redeemScript: P2WPKH_ADDRESS.output,
274 | });
275 |
276 | psbt.addOutput({
277 | address: P2PKH_ADDRESS.address.toString(),
278 | value: cur_output_value
279 | });
280 |
281 | t.equal(psbt.__CACHE.__TX.toHex(),
282 | "0100000001e4c0e9ea8f394f74f790397209e47a1df8c9fa457448fd3bcbc8fb5a3dd96dd70000000000ffffffff01b0069a3b000000001976a914b2b2380a1e486aff5ae5ae74c892e902a72c0a4c88ac00000000");
283 |
284 | let sigHash0 = psbt.__CACHE.__TX.hashForWitnessV0(0, P2PKH_ADDRESS.output, cur_input_value, bitcoin.Transaction.SIGHASH_ALL).toString('hex');
285 |
286 | t.equal(sigHash0, "59e2322a152dbad2c283232bd098a55c61bc0cd324dfd85311a0a9e73053d46b");
287 |
288 | psbt.signInput(0, KEY1);
289 |
290 | let signed_payload = psbt.finalizeAllInputs().extractTransaction();
291 |
292 | t.equal(signed_payload.toHex(),
293 | "01000000000101e4c0e9ea8f394f74f790397209e47a1df8c9fa457448fd3bcbc8fb5a3dd96dd70000000017160014b2b2380a1e486aff5ae5ae74c892e902a72c0a4cffffffff01b0069a3b000000001976a914b2b2380a1e486aff5ae5ae74c892e902a72c0a4c88ac02473044022067efbe904404b388bf11cf8af610f2efa95ac943a67071c3c5fe0332286d672e02205f3917d8967d7f32fb65c0808c6c0de7dda8a080bf92f80c1ee13d33757fd1df012103820317ad251bca573c8fda2b8f26ffc9aae9d5ecb15b50ee08d8f9e009def38e00000000");
294 |
295 | t.equal(signed_payload.getId(), "74b178c39268acd0663c88d3a56665b2f5335b60711445a5f8cd8aa59c2c7d38");
296 |
297 | }, undefined, "must not throw any Errors");
298 | });
299 |
300 |
301 | test('TEST P2PKH to P2SH', t => {
302 | t.plan(5);
303 |
304 | let cur_input_value = OUTPUT_VALUE - FEE - FEE - FEE - FEE - FEE;
305 | let cur_output_value = OUTPUT_VALUE - FEE - FEE - FEE - FEE - FEE - FEE;
306 |
307 | t.doesNotThrow(() => {
308 |
309 | let psbt = new bitcoin.Psbt({network: LTCTEST});
310 | psbt.setVersion(1);
311 | psbt.setLocktime(0);
312 |
313 | psbt.addInput({
314 | hash: "74b178c39268acd0663c88d3a56665b2f5335b60711445a5f8cd8aa59c2c7d38",
315 | index: 0,
316 | sequence: 0xffffffff,
317 | witnessUtxo: {script: P2PKH_ADDRESS.output, value: cur_input_value},
318 | });
319 |
320 | psbt.addOutput({
321 | address: P2SH_ADDRESS.address.toString(),
322 | value: cur_output_value
323 | });
324 |
325 | t.equal(psbt.__CACHE.__TX.toHex(),
326 | "0100000001387d2c9ca58acdf8a5451471605b33f5b26566a5d3883c66d0ac6892c378b1740000000000ffffffff01a0df993b0000000017a9142069605a7742286aef950b68ae7818f7294e876c8700000000");
327 |
328 | let sigHash0 = psbt.__CACHE.__TX.hashForSignature(0, P2PKH_ADDRESS.output, bitcoin.Transaction.SIGHASH_ALL).toString('hex');
329 |
330 | t.equal(sigHash0, "ae52a447200543a0e5a5ca8de0bad10eebb411748d137f7b2fba380b98ea6651");
331 |
332 | psbt.__CACHE.__UNSAFE_SIGN_NONSEGWIT = true;
333 | psbt.signInput(0, KEY1);
334 |
335 | let signed_payload = psbt.finalizeAllInputs().extractTransaction();
336 |
337 | t.equal(signed_payload.toHex(),
338 | "0100000001387d2c9ca58acdf8a5451471605b33f5b26566a5d3883c66d0ac6892c378b174000000006a47304402200baec9555d3852ff2627971e5b4f26c186792bb4960bacf1270372c3b540b85b0220461100cd59a45fe922df4c4d8a3c14f358bfdc68f500811ac3cde0fb8d8c1f2a012103820317ad251bca573c8fda2b8f26ffc9aae9d5ecb15b50ee08d8f9e009def38effffffff01a0df993b0000000017a9142069605a7742286aef950b68ae7818f7294e876c8700000000");
339 |
340 | t.equal(signed_payload.getId(), "e4dd8c000f65fcf42598ff332ef81852b44bac9dcdecac72d69a4c56b8c59b73");
341 |
342 | }, undefined, "must not throw any Errors");
343 | });
344 |
345 |
346 |
--------------------------------------------------------------------------------
/test/unit/cryptohelper.js:
--------------------------------------------------------------------------------
1 | const test = require('tape');
2 | const CryptoHelper = require('../../lib/helper');
3 |
4 | const PIN = '123456';
5 | const PIN_KEY = '0EeMOVtm5YihUYzdCNgleqIUWkwgvNBcRmr7M0t9GOc=';
6 | const CLEARTEXT = 'I\'m a little tea pot short and stout';
7 | const CIPHERTEXT = '7HTfNBYJjq09+vi8hTQhy6lCp3IHv5rztNnKCJ5RB7cSL+NjHrFVv1jl7qkxJsOg';
8 | const PIN_KEY_500000 = CryptoHelper.pinToKey("deadbeef", "922445847c173e90667a19d90729e1fb", 500000);
9 |
10 | test('Deriving a PIN into an AES key', t => {
11 | t.plan(2);
12 |
13 | t.doesNotThrow(() => {
14 | const key = CryptoHelper.pinToKey(PIN);
15 | t.equal(key, PIN_KEY, 'must return the correct derived key');
16 | }, undefined, 'must not throw any Errors');
17 |
18 | });
19 |
20 | test('Deriving a PIN into an AES key with Salt', t => {
21 | t.plan(2);
22 |
23 | t.doesNotThrow(() => {
24 | t.equal(Buffer.from(PIN_KEY_500000, "base64").toString("hex"), "f206403c6bad20e1c8cb1f3318e17cec5b2da0560ed6c7b26826867452534172", 'must return the correct derived key');
25 | }, undefined, 'must not throw any Errors');
26 |
27 | });
28 |
29 | test('Encrypt using AES-256-ECB', t => {
30 | t.plan(2);
31 |
32 | let enc;
33 | t.doesNotThrow(() => {
34 | enc = CryptoHelper.encrypt(CLEARTEXT, PIN_KEY);
35 | t.equal(enc.aes_cipher_text, CIPHERTEXT, 'must return the correct ciphertext');
36 | }, undefined, 'does not throw any Errors');
37 |
38 | test('Decrypt using AES-256-ECB', t => {
39 | t.plan(2);
40 | t.doesNotThrow(() => {
41 | const dec = CryptoHelper.decrypt(enc.aes_cipher_text, PIN_KEY)
42 | t.equal(dec, CLEARTEXT, 'must return the correct cleartext');
43 | }, undefined, 'does not throw any Errors');
44 |
45 | });
46 |
47 | });
48 |
49 | test('Encrypt using AES-256-CBC', t => {
50 | t.plan(2);
51 |
52 | let enc;
53 | t.doesNotThrow(() => {
54 | enc = CryptoHelper.encrypt("beadbeef", PIN_KEY_500000, "11bc22166c8cf8560e5fa7e5c622bb0f", "AES-256-CBC");
55 | t.equal(enc.aes_cipher_text, "LExu1rUAtIBOekslc328Lw==", 'must return the correct ciphertext');
56 | }, undefined, 'does not throw any Errors');
57 |
58 | test('Decrypt using AES-256-CBC', t => {
59 | t.plan(2);
60 | t.doesNotThrow(() => {
61 | const dec = CryptoHelper.decrypt(enc.aes_cipher_text, PIN_KEY_500000, "11bc22166c8cf8560e5fa7e5c622bb0f", "AES-256-CBC")
62 | t.equal(dec, "beadbeef", 'must return the correct cleartext');
63 | }, undefined, 'does not throw any Errors');
64 |
65 | });
66 |
67 | });
68 |
69 | test('Encrypt using AES-256-GCM', t => {
70 | t.plan(3);
71 |
72 | let enc;
73 | t.doesNotThrow(() => {
74 | enc = CryptoHelper.encrypt("beadbeef", PIN_KEY_500000, "a57414b88b67f977829cbdca", "AES-256-GCM");
75 | t.equal(enc.aes_cipher_text, "ELV56Z57KoA=", 'must return the correct ciphertext');
76 | t.equal(enc.aes_auth_tag, "adeb7dfe53027bdda5824dc524d5e55a", 'must return the correct auth_tag');
77 | }, undefined, 'does not throw any Errors');
78 |
79 | test('Decrypt using AES-256-GCM', t => {
80 | t.plan(2);
81 | t.doesNotThrow(() => {
82 | const dec = CryptoHelper.decrypt(enc.aes_cipher_text, PIN_KEY_500000, "a57414b88b67f977829cbdca", "AES-256-GCM", enc.aes_auth_tag, enc.aes_auth_data)
83 | t.equal(dec, "beadbeef", 'must return the correct cleartext');
84 | }, undefined, 'does not throw any Errors');
85 |
86 | });
87 |
88 | test('Decrypt using AES-256-GCM with small auth tag', t => {
89 | t.plan(1);
90 | t.throws(() => {
91 | CryptoHelper.decrypt(enc.aes_cipher_text, PIN_KEY_500000, "a57414b88b67f977829cbdca", "AES-256-GCM", enc.aes_auth_tag.slice(0,30), enc.aes_auth_data);
92 | }, "Auth tag must be 16 bytes exactly.", 'throws an Error');
93 |
94 | });
95 |
96 | });
97 |
98 | test("DynamicExtractKey using AES-256-ECB", t => {
99 | t.plan(2)
100 |
101 | let user_key = JSON.parse('{"encrypted_passphrase":"3wIJtPoC8KO6S7x6LtrN0g==","public_key":"02f87f787bffb30396984cb6b3a9d6830f32d5b656b3e39b0abe4f3b3c35d99323","algorithm":{"pbkdf2_salt":"","pbkdf2_iterations":2048,"pbkdf2_hash_function":"SHA256","pbkdf2_phase1_key_length":16,"pbkdf2_phase2_key_length":32,"aes_iv":null,"aes_cipher":"AES-256-ECB","aes_auth_tag":null,"aes_auth_data":null}}');
102 |
103 | t.doesNotThrow(() => {
104 | let key = CryptoHelper.dynamicExtractKey(user_key, "deadbeef");
105 | t.equal(key.pub.toString('hex'), user_key.public_key, 'must return correct public key');
106 | }, undefined, 'does not throw any Errors');
107 | });
108 |
109 | test("DynamicExtractKey using AES-256-CBC", t => {
110 | t.plan(2)
111 |
112 | let user_key = JSON.parse('{"encrypted_passphrase":"LExu1rUAtIBOekslc328Lw==","public_key":"02f87f787bffb30396984cb6b3a9d6830f32d5b656b3e39b0abe4f3b3c35d99323","algorithm":{"pbkdf2_salt":"922445847c173e90667a19d90729e1fb","pbkdf2_iterations":500000,"pbkdf2_hash_function":"SHA256","pbkdf2_phase1_key_length":16,"pbkdf2_phase2_key_length":32,"aes_iv":"11bc22166c8cf8560e5fa7e5c622bb0f","aes_cipher":"AES-256-CBC","aes_auth_tag":null,"aes_auth_data":null}}');
113 |
114 | t.doesNotThrow(() => {
115 | let key = CryptoHelper.dynamicExtractKey(user_key, "deadbeef");
116 | t.equal(key.pub.toString('hex'), user_key.public_key, 'must return correct public key');
117 | }, undefined, 'does not throw any Errors');
118 | });
119 |
120 | test("DynamicExtractKey using AES-256-GCM", t => {
121 | t.plan(2)
122 |
123 | let user_key = JSON.parse('{"encrypted_passphrase":"ELV56Z57KoA=","public_key":"02f87f787bffb30396984cb6b3a9d6830f32d5b656b3e39b0abe4f3b3c35d99323","algorithm":{"pbkdf2_salt":"922445847c173e90667a19d90729e1fb","pbkdf2_iterations":500000,"pbkdf2_hash_function":"SHA256","pbkdf2_phase1_key_length":16,"pbkdf2_phase2_key_length":32,"aes_iv":"a57414b88b67f977829cbdca","aes_cipher":"AES-256-GCM","aes_auth_tag":"adeb7dfe53027bdda5824dc524d5e55a","aes_auth_data":""}}');
124 |
125 | t.doesNotThrow(() => {
126 | let key = CryptoHelper.dynamicExtractKey(user_key, "deadbeef");
127 | t.equal(key.pub.toString('hex'), user_key.public_key, 'must return correct public key');
128 | }, undefined, 'does not throw any Errors');
129 | });
130 |
131 | test('Satoshis from value string', t => {
132 | t.plan(4);
133 |
134 | t.equal(CryptoHelper.to_satoshis("1.00000000"), 100000000);
135 | t.equal(CryptoHelper.to_satoshis("1.12345678"), 112345678);
136 | t.equal(CryptoHelper.to_satoshis("0.00000001"), 1);
137 | t.equal(CryptoHelper.to_satoshis("0.00112500"), 112500);
138 | });
139 |
140 | test('Value string from Satoshis', t => {
141 | t.plan(4);
142 |
143 | t.equal(CryptoHelper.from_satoshis(100000000), "1.00000000");
144 | t.equal(CryptoHelper.from_satoshis(112345678), "1.12345678");
145 | t.equal(CryptoHelper.from_satoshis(1), "0.00000001");
146 | t.equal(CryptoHelper.from_satoshis(112500), "0.00112500");
147 | });
148 |
--------------------------------------------------------------------------------
/test/unit/httpsclient.js:
--------------------------------------------------------------------------------
1 | const test = require('tape');
2 | const BlockIo = require('../../lib/block_io');
3 |
4 | test('HTTPS Client: connecting to host', t => {
5 | t.plan(2);
6 |
7 | const client = new BlockIo({
8 | api_key: "0000-0000-0000-0000",
9 | version: 2,
10 | pin: 'unused',
11 | });
12 |
13 | t.doesNotThrow(() => {
14 | client.get_balance({})
15 | .then(data => console.log(data))
16 | .catch(r => t.equal(r.data.status, 'fail', 'must return JSON object with status fail'));
17 | }, undefined, 'must not throw any Errors');
18 |
19 | });
20 |
21 |
--------------------------------------------------------------------------------
/test/unit/key.js:
--------------------------------------------------------------------------------
1 | const test = require('tape');
2 | const Key = require('../../lib/key');
3 |
4 | const PRIVKEY = '6b0e34587dece0ef042c4c7205ce6b3d4a64d0bc484735b9325f7971a0ead963';
5 | const PUBKEY = '029c06f988dc6b44696e002e8abf496a13c73c2f1db3bde2dfb69be129f3711b01';
6 |
7 | const HEXDATA_TO_SIGN = 'feedfacedeadbeeffeedfacedeadbeeffeedfacedeadbeeffeedfacedeadbeef';
8 | const SIG = '3045022100b633aaa7cd5b7af455211531f193b61d34d20fe5ea19d23dd40d6074126150530220676617cd427db7d85923ebe4426ccecc47fb5826e3e24b60e62244e2a4811086';
9 | const SIG_LOW_R = '3044022042b9b4d673c85798f226c85f55ea6e114a0805bd5a0efba35f14c05235bb67b2022016333edae230c0ab607e948b48ceaefb5cab07300fb869d9da0a1b0f6bb53f65';
10 |
11 | const PASSPHRASE = 'deadbeeffeedface';
12 | const PASS_PRIV = 'ae9f07f3d627531db09562bbabad4c5e023f6505b4b06122730744261953e48f';
13 | const PASS_PUB = '029023d9738c623cdd7e5fdd0f41666accb82f21df5d27dc5ef07040f7bdc5d9f5';
14 |
15 | test('Creates new ECKey from Buffer', t => {
16 | t.plan(1);
17 | let key = new Key(Buffer.from(PRIVKEY, 'hex'));
18 | t.equals(key.pub.toString('hex'), PUBKEY, 'must return correct public key');
19 | });
20 |
21 | test('ECKey generates new keys', t => {
22 | t.plan(1);
23 | let key1 = Key.makeRandom().pub.toString('hex');
24 | let key2 = Key.makeRandom().pub.toString('hex');
25 |
26 | t.notEquals(key1, key2, 'must generate random keys');
27 | });
28 |
29 | test('ECKey extensions: deriving a pubkey from hex', t => {
30 | t.plan(2);
31 |
32 | let key;
33 | t.doesNotThrow(() => {
34 | key = Key.fromHex(PRIVKEY);
35 | key.lowR = false;
36 |
37 | t.equal(key.pub.toString('hex'), PUBKEY, 'must return the correct pubkey');
38 | }, undefined, 'must not throw any Errors');
39 |
40 | test('ECKey extensions: signing hexdata', t => {
41 | t.plan(2);
42 | t.doesNotThrow(() => {
43 | const sd = key.signHex(HEXDATA_TO_SIGN);
44 | t.equal(sd, SIG, 'must return the correct signature')
45 | }, undefined, 'must not throw any Errors');
46 | });
47 |
48 | });
49 |
50 | test('ECKey extensions: signing with low R', t => {
51 | t.plan(3);
52 |
53 | let key;
54 | t.doesNotThrow(() => {
55 | key = Key.fromHex(PRIVKEY);
56 | }, undefined, 'must not throw any Errors');
57 |
58 | key.lowR = true;
59 |
60 | t.doesNotThrow(() => {
61 | const sd = key.signHex(HEXDATA_TO_SIGN);
62 | t.equal(sd, SIG_LOW_R, 'must return the correct signature')
63 | }, undefined, 'must not throw any Errors');
64 |
65 | });
66 |
67 | test('ECKey extensions: deriving a pubkey from passphrase', t => {
68 | t.plan(3);
69 |
70 | let key;
71 | t.doesNotThrow(() => {
72 | key = Key.fromPassphrase(PASSPHRASE);
73 | t.equal(key.priv.toString('hex'), PASS_PRIV, 'must return the correct privkey');
74 | t.equal(key.pub.toString('hex'), PASS_PUB, 'must return the correct pubkey');
75 | }, undefined, 'must not throw any Errors');
76 |
77 | });
78 |
--------------------------------------------------------------------------------
/test/unit/prepare_transaction.js:
--------------------------------------------------------------------------------
1 | /*eslint no-mixed-spaces-and-tabs: ["error", "smart-tabs"]*/
2 | // all tests here use 32-byte low R values
3 |
4 | const test = require('tape');
5 | const fs = require('fs');
6 | const BlockIo = require('../../lib/block_io');
7 | const Key = require('../../lib/key');
8 | const Networks = require('../../lib/networks');
9 | const path = require('path');
10 |
11 | const PIN = "d1650160bd8d2bb32bebd139d0063eb6063ffa2f9e4501ad";
12 | const SWEEP_WIF = "cTj8Ydq9LhZgttMpxb7YjYSqsZ2ZfmyzVprQgjEzAzQ28frQi4ML";
13 | const SWEEP_HEX = Key.fromWIF(SWEEP_WIF, Networks.getNetwork("LTCTEST")).ecpair.privateKey.toString('hex');
14 | const DTRUST_KEYS = ["b515fd806a662e061b488e78e5d0c2ff46df80083a79818e166300666385c0a2",
15 | "001584b821c62ecdc554e185222591720d6fe651ed1b820d83f92cdc45c5e21f",
16 | "2f9090b8aa4ddb32c3b0b8371db1b50e19084c720c30db1d6bb9fcd3a0f78e61",
17 | "06c1cefdfd9187b36b36c3698c1362642083dcc1941dc76d751481d3aa29ca65"];
18 |
19 | const client = new BlockIo({
20 | api_key: "0000-0000-0000-0000",
21 | version: 2,
22 | pin: PIN,
23 | });
24 |
25 | function full_path(relative_path) {
26 | return path.resolve(__dirname, relative_path);
27 | }
28 |
29 | function read_json_file(relative_path) {
30 | return JSON.parse(fs.readFileSync(full_path(relative_path)));
31 | }
32 |
33 | test('prepare_transaction_response.json', t => {
34 | // tests mix of P2SH, P2WSH-over-P2SH and WITNESS_V0 (P2WSH) inputs and signatures
35 |
36 | const prepare_transaction_response = read_json_file("../../data/test-cases/json/prepare_transaction_response.json");
37 | const create_and_sign_transaction_response = read_json_file("../../data/test-cases/json/create_and_sign_transaction_response.json");
38 |
39 | t.plan(1);
40 |
41 | client.create_and_sign_transaction({data: prepare_transaction_response, keys: []}).then((f) => {
42 | t.deepEqual(f,create_and_sign_transaction_response);
43 | }).catch((r) => {
44 | t.equal(typeof(r),'undefined', "should not throw exception");
45 | });
46 |
47 | });
48 |
49 | test('prepare_transaction_response_P2WSH-over-P2SH_1of2_251inputs.json', t => {
50 |
51 | const prepare_transaction_response = read_json_file("../../data/test-cases/json/prepare_transaction_response_P2WSH-over-P2SH_1of2_251inputs.json");
52 | const create_and_sign_transaction_response = read_json_file("../../data/test-cases/json/create_and_sign_transaction_response_P2WSH-over-P2SH_1of2_251inputs.json");
53 |
54 | t.plan(1);
55 |
56 | client.create_and_sign_transaction({data: prepare_transaction_response, keys: []}).then((f) => {
57 | t.deepEqual(f,create_and_sign_transaction_response);
58 | }).catch((r) => {
59 | t.equal(typeof(r),'undefined', "should not throw exception");
60 | });
61 |
62 | });
63 |
64 | test('prepare_transaction_response_P2WSH-over-P2SH_1of2_252inputs.json', t => {
65 |
66 | const prepare_transaction_response = read_json_file("../../data/test-cases/json/prepare_transaction_response_P2WSH-over-P2SH_1of2_252inputs.json");
67 | const create_and_sign_transaction_response = read_json_file("../../data/test-cases/json/create_and_sign_transaction_response_P2WSH-over-P2SH_1of2_252inputs.json");
68 |
69 | t.plan(1);
70 |
71 | client.create_and_sign_transaction({data: prepare_transaction_response, keys: []}).then((f) => {
72 | t.deepEqual(f,create_and_sign_transaction_response);
73 | }).catch((r) => {
74 | t.equal(typeof(r),'undefined', "should not throw exception");
75 | });
76 |
77 | });
78 |
79 | test('prepare_transaction_response_P2WSH-over-P2SH_1of2_253inputs.json', t => {
80 |
81 | const prepare_transaction_response = read_json_file("../../data/test-cases/json/prepare_transaction_response_P2WSH-over-P2SH_1of2_253inputs.json");
82 | const create_and_sign_transaction_response = read_json_file("../../data/test-cases/json/create_and_sign_transaction_response_P2WSH-over-P2SH_1of2_253inputs.json");
83 |
84 | t.plan(1);
85 |
86 | client.create_and_sign_transaction({data: prepare_transaction_response, keys: []}).then((f) => {
87 | t.deepEqual(f,create_and_sign_transaction_response);
88 | }).catch((r) => {
89 | t.equal(typeof(r),'undefined', "should not throw exception");
90 | });
91 |
92 | });
93 |
94 | test('prepare_transaction_response_P2WSH-over-P2SH_1of2_762inputs.json', t => {
95 |
96 | const prepare_transaction_response = read_json_file("../../data/test-cases/json/prepare_transaction_response_P2WSH-over-P2SH_1of2_762inputs.json");
97 | const create_and_sign_transaction_response = read_json_file("../../data/test-cases/json/create_and_sign_transaction_response_P2WSH-over-P2SH_1of2_762inputs.json");
98 |
99 | t.plan(1);
100 |
101 | client.create_and_sign_transaction({data: prepare_transaction_response, keys: []}).then((f) => {
102 | t.deepEqual(f,create_and_sign_transaction_response);
103 | }).catch((r) => {
104 | t.equal(typeof(r),'undefined', "should not throw exception");
105 | });
106 |
107 | });
108 |
109 | test('prepare_dtrust_transaction_response_p2sh.json (3of5)', t => {
110 |
111 | const prepare_transaction_response = read_json_file("../../data/test-cases/json/prepare_dtrust_transaction_response_p2sh.json");
112 | const create_and_sign_transaction_response = read_json_file("../../data/test-cases/json/create_and_sign_transaction_response_dtrust_p2sh_3_of_5_keys.json");
113 |
114 | t.plan(1);
115 |
116 | client.create_and_sign_transaction({data: prepare_transaction_response, keys: DTRUST_KEYS.slice(0,3)}).then((f) => {
117 | t.deepEqual(f,create_and_sign_transaction_response);
118 | }).catch((r) => {
119 | t.equal(typeof(r),'undefined', "should not throw exception");
120 | });
121 |
122 | });
123 |
124 | test('prepare_dtrust_transaction_response_p2sh.json (4of5)', t => {
125 |
126 | const prepare_transaction_response = read_json_file("../../data/test-cases/json/prepare_dtrust_transaction_response_p2sh.json");
127 | const create_and_sign_transaction_response = read_json_file("../../data/test-cases/json/create_and_sign_transaction_response_dtrust_p2sh_4_of_5_keys.json");
128 |
129 | t.plan(1);
130 |
131 | client.create_and_sign_transaction({data: prepare_transaction_response, keys: DTRUST_KEYS.slice(0,4)}).then((f) => {
132 | t.deepEqual(f,create_and_sign_transaction_response);
133 | }).catch((r) => {
134 | t.equal(typeof(r),'undefined', "should not throw exception");
135 | });
136 |
137 | });
138 |
139 | test('prepare_dtrust_transaction_response_p2wsh_over_p2sh.json (3of5)', t => {
140 |
141 | const prepare_transaction_response = read_json_file("../../data/test-cases/json/prepare_dtrust_transaction_response_p2wsh_over_p2sh.json");
142 | const create_and_sign_transaction_response = read_json_file("../../data/test-cases/json/create_and_sign_transaction_response_dtrust_p2wsh_over_p2sh_3_of_5_keys.json");
143 |
144 | t.plan(1);
145 |
146 | client.create_and_sign_transaction({data: prepare_transaction_response, keys: DTRUST_KEYS.slice(0,3)}).then((f) => {
147 | t.deepEqual(f,create_and_sign_transaction_response);
148 | }).catch((r) => {
149 | t.equal(typeof(r),'undefined', "should not throw exception");
150 | });
151 |
152 | });
153 |
154 | test('prepare_dtrust_transaction_response_p2wsh_over_p2sh.json (4of5)', t => {
155 |
156 | const prepare_transaction_response = read_json_file("../../data/test-cases/json/prepare_dtrust_transaction_response_p2wsh_over_p2sh.json");
157 | const create_and_sign_transaction_response = read_json_file("../../data/test-cases/json/create_and_sign_transaction_response_dtrust_p2wsh_over_p2sh_4_of_5_keys.json");
158 |
159 | t.plan(1);
160 |
161 | client.create_and_sign_transaction({data: prepare_transaction_response, keys: DTRUST_KEYS.slice(0,4)}).then((f) => {
162 | t.deepEqual(f,create_and_sign_transaction_response);
163 | }).catch((r) => {
164 | t.equal(typeof(r),'undefined', "should not throw exception");
165 | });
166 |
167 | });
168 |
169 | test('prepare_dtrust_transaction_response_witness_v0.json (3of5)', t => {
170 |
171 | const prepare_transaction_response = read_json_file("../../data/test-cases/json/prepare_dtrust_transaction_response_witness_v0.json");
172 | const create_and_sign_transaction_response = read_json_file("../../data/test-cases/json/create_and_sign_transaction_response_dtrust_witness_v0_3_of_5_keys.json");
173 |
174 | t.plan(1);
175 |
176 | client.create_and_sign_transaction({data: prepare_transaction_response, keys: DTRUST_KEYS.slice(0,3)}).then((f) => {
177 | t.deepEqual(f,create_and_sign_transaction_response);
178 | }).catch((r) => {
179 | t.equal(typeof(r),'undefined', "should not throw exception");
180 | });
181 |
182 | });
183 |
184 | test('prepare_dtrust_transaction_response_witness_v0.json (4of5)', t => {
185 |
186 | const prepare_transaction_response = read_json_file("../../data/test-cases/json/prepare_dtrust_transaction_response_witness_v0.json");
187 | const create_and_sign_transaction_response = read_json_file("../../data/test-cases/json/create_and_sign_transaction_response_dtrust_witness_v0_4_of_5_keys.json");
188 |
189 | t.plan(1);
190 |
191 | client.create_and_sign_transaction({data: prepare_transaction_response, keys: DTRUST_KEYS.slice(0,4)}).then((f) => {
192 | t.deepEqual(f,create_and_sign_transaction_response);
193 | }).catch((r) => {
194 | t.equal(typeof(r),'undefined', "should not throw exception");
195 | });
196 |
197 | });
198 |
199 | test('prepare_sweep_transaction_response_p2pkh.json', t => {
200 |
201 | const prepare_transaction_response = read_json_file("../../data/test-cases/json/prepare_sweep_transaction_response_p2pkh.json");
202 | const create_and_sign_transaction_response = read_json_file("../../data/test-cases/json/create_and_sign_transaction_response_sweep_p2pkh.json");
203 |
204 | t.plan(1);
205 |
206 | client.create_and_sign_transaction({data: prepare_transaction_response, keys: [SWEEP_HEX]}).then((f) => {
207 | t.deepEqual(f,create_and_sign_transaction_response);
208 | }).catch((r) => {
209 | t.equal(typeof(r),'undefined', "should not throw exception");
210 | });
211 |
212 | });
213 |
214 | test('prepare_sweep_transaction_response_p2wpkh_over_p2sh.json', t => {
215 |
216 | const prepare_transaction_response = read_json_file("../../data/test-cases/json/prepare_sweep_transaction_response_p2wpkh_over_p2sh.json");
217 | const create_and_sign_transaction_response = read_json_file("../../data/test-cases/json/create_and_sign_transaction_response_sweep_p2wpkh_over_p2sh.json");
218 |
219 | t.plan(1);
220 |
221 | client.create_and_sign_transaction({data: prepare_transaction_response, keys: [SWEEP_HEX]}).then((f) => {
222 | t.deepEqual(f,create_and_sign_transaction_response);
223 | }).catch((r) => {
224 | t.equal(typeof(r),'undefined', "should not throw exception");
225 | });
226 |
227 | });
228 |
229 | test('prepare_sweep_transaction_response_p2wpkh.json', t => {
230 |
231 | const prepare_transaction_response = read_json_file("../../data/test-cases/json/prepare_sweep_transaction_response_p2wpkh.json");
232 | const create_and_sign_transaction_response = read_json_file("../../data/test-cases/json/create_and_sign_transaction_response_sweep_p2wpkh.json");
233 |
234 | t.plan(1);
235 |
236 | client.create_and_sign_transaction({data: prepare_transaction_response, keys: [SWEEP_HEX]}).then((f) => {
237 | t.deepEqual(f,create_and_sign_transaction_response);
238 | }).catch((r) => {
239 | t.equal(typeof(r),'undefined', "should not throw exception");
240 | });
241 |
242 | });
243 |
244 | test('prepare_dtrust_transaction_response_P2SH_3of5_195inputs.json', t => {
245 |
246 | const prepare_transaction_response = read_json_file("../../data/test-cases/json/prepare_dtrust_transaction_response_P2SH_3of5_195inputs.json");
247 | const create_and_sign_transaction_response = read_json_file("../../data/test-cases/json/create_and_sign_transaction_response_dtrust_P2SH_3of5_195inputs.json");
248 |
249 | t.plan(1);
250 |
251 | client.create_and_sign_transaction({data: prepare_transaction_response, keys: DTRUST_KEYS.slice(0,3)}).then((f) => {
252 | t.deepEqual(f,create_and_sign_transaction_response);
253 | }).catch((r) => {
254 | t.equal(typeof(r),'undefined', "should not throw exception");
255 | });
256 |
257 | });
258 |
259 | /*
260 | omit 4of5 tests due to difference in low R signature generation (see fixed_low_r_sign in lib/key.js)
261 | */
262 | /*
263 | test('prepare_dtrust_transaction_response_P2SH_4of5_195inputs.json', t => {
264 |
265 | const prepare_transaction_response = read_json_file("../../data/test-cases/json/prepare_dtrust_transaction_response_P2SH_4of5_195inputs.json");
266 | const create_and_sign_transaction_response = read_json_file("../../data/test-cases/json/create_and_sign_transaction_response_dtrust_P2SH_4of5_195inputs.json");
267 |
268 | t.plan(1);
269 |
270 | client.create_and_sign_transaction({data: prepare_transaction_response, keys: DTRUST_KEYS.slice(0,4)}).then((f) => {
271 | t.deepEqual(f,create_and_sign_transaction_response);
272 | }).catch((r) => {
273 | t.equal(typeof(r),'undefined', "should not throw exception");
274 | });
275 |
276 | });
277 | */
278 |
279 | test('prepare_dtrust_transaction_response_P2WSH-over-P2SH_3of5_251inputs.json', t => {
280 |
281 | const prepare_transaction_response = read_json_file("../../data/test-cases/json/prepare_dtrust_transaction_response_P2WSH-over-P2SH_3of5_251inputs.json");
282 | const create_and_sign_transaction_response = read_json_file("../../data/test-cases/json/create_and_sign_transaction_response_dtrust_P2WSH-over-P2SH_3of5_251inputs.json");
283 |
284 | t.plan(1);
285 |
286 | client.create_and_sign_transaction({data: prepare_transaction_response, keys: DTRUST_KEYS.slice(0,3)}).then((f) => {
287 | t.deepEqual(f,create_and_sign_transaction_response);
288 | }).catch((r) => {
289 | t.equal(typeof(r),'undefined', "should not throw exception");
290 | });
291 |
292 | });
293 |
294 | test('prepare_dtrust_transaction_response_P2WSH-over-P2SH_3of5_252inputs.json', t => {
295 |
296 | const prepare_transaction_response = read_json_file("../../data/test-cases/json/prepare_dtrust_transaction_response_P2WSH-over-P2SH_3of5_252inputs.json");
297 | const create_and_sign_transaction_response = read_json_file("../../data/test-cases/json/create_and_sign_transaction_response_dtrust_P2WSH-over-P2SH_3of5_252inputs.json");
298 |
299 | t.plan(1);
300 |
301 | client.create_and_sign_transaction({data: prepare_transaction_response, keys: DTRUST_KEYS.slice(0,3)}).then((f) => {
302 | t.deepEqual(f,create_and_sign_transaction_response);
303 | }).catch((r) => {
304 | t.equal(typeof(r),'undefined', "should not throw exception");
305 | });
306 |
307 | });
308 |
309 | test('prepare_dtrust_transaction_response_P2WSH-over-P2SH_3of5_253inputs.json', t => {
310 |
311 | const prepare_transaction_response = read_json_file("../../data/test-cases/json/prepare_dtrust_transaction_response_P2WSH-over-P2SH_3of5_253inputs.json");
312 | const create_and_sign_transaction_response = read_json_file("../../data/test-cases/json/create_and_sign_transaction_response_dtrust_P2WSH-over-P2SH_3of5_253inputs.json");
313 |
314 | t.plan(1);
315 |
316 | client.create_and_sign_transaction({data: prepare_transaction_response, keys: DTRUST_KEYS.slice(0,3)}).then((f) => {
317 | t.deepEqual(f,create_and_sign_transaction_response);
318 | }).catch((r) => {
319 | t.equal(typeof(r),'undefined', "should not throw exception");
320 | });
321 |
322 | });
323 |
324 | test('prepare_dtrust_transaction_response_WITNESS_V0_3of5_251inputs.json', t => {
325 |
326 | const prepare_transaction_response = read_json_file("../../data/test-cases/json/prepare_dtrust_transaction_response_WITNESS_V0_3of5_251inputs.json");
327 | const create_and_sign_transaction_response = read_json_file("../../data/test-cases/json/create_and_sign_transaction_response_dtrust_WITNESS_V0_3of5_251inputs.json");
328 |
329 | t.plan(1);
330 |
331 | client.create_and_sign_transaction({data: prepare_transaction_response, keys: DTRUST_KEYS.slice(0,3)}).then((f) => {
332 | t.deepEqual(f,create_and_sign_transaction_response);
333 | }).catch((r) => {
334 | t.equal(typeof(r),'undefined', "should not throw exception");
335 | });
336 |
337 | });
338 |
339 | test('prepare_dtrust_transaction_response_WITNESS_V0_3of5_252inputs.json', t => {
340 |
341 | const prepare_transaction_response = read_json_file("../../data/test-cases/json/prepare_dtrust_transaction_response_WITNESS_V0_3of5_252inputs.json");
342 | const create_and_sign_transaction_response = read_json_file("../../data/test-cases/json/create_and_sign_transaction_response_dtrust_WITNESS_V0_3of5_252inputs.json");
343 |
344 | t.plan(1);
345 |
346 | client.create_and_sign_transaction({data: prepare_transaction_response, keys: DTRUST_KEYS.slice(0,3)}).then((f) => {
347 | t.deepEqual(f,create_and_sign_transaction_response);
348 | }).catch((r) => {
349 | t.equal(typeof(r),'undefined', "should not throw exception");
350 | });
351 |
352 | });
353 |
354 | test('prepare_dtrust_transaction_response_WITNESS_V0_3of5_253inputs.json', t => {
355 |
356 | const prepare_transaction_response = read_json_file("../../data/test-cases/json/prepare_dtrust_transaction_response_WITNESS_V0_3of5_253inputs.json");
357 | const create_and_sign_transaction_response = read_json_file("../../data/test-cases/json/create_and_sign_transaction_response_dtrust_WITNESS_V0_3of5_253inputs.json");
358 |
359 | t.plan(1);
360 |
361 | client.create_and_sign_transaction({data: prepare_transaction_response, keys: DTRUST_KEYS.slice(0,3)}).then((f) => {
362 | t.deepEqual(f,create_and_sign_transaction_response);
363 | }).catch((r) => {
364 | t.equal(typeof(r),'undefined', "should not throw exception");
365 | });
366 |
367 | });
368 |
369 | test('prepare_dtrust_transaction_response_witness_v0_3of5_251outputs.json', t => {
370 |
371 | const prepare_transaction_response = read_json_file("../../data/test-cases/json/prepare_dtrust_transaction_response_witness_v0_3of5_251outputs.json");
372 | const create_and_sign_transaction_response = read_json_file("../../data/test-cases/json/create_and_sign_transaction_response_dtrust_witness_v0_3of5_251outputs.json");
373 |
374 | t.plan(1);
375 |
376 | client.create_and_sign_transaction({data: prepare_transaction_response, keys: DTRUST_KEYS.slice(0,3)}).then((f) => {
377 | t.deepEqual(f,create_and_sign_transaction_response);
378 | }).catch((r) => {
379 | t.equal(typeof(r),'undefined', "should not throw exception");
380 | });
381 |
382 | });
383 |
384 | test('prepare_dtrust_transaction_response_witness_v0_3of5_252outputs.json', t => {
385 |
386 | const prepare_transaction_response = read_json_file("../../data/test-cases/json/prepare_dtrust_transaction_response_witness_v0_3of5_252outputs.json");
387 | const create_and_sign_transaction_response = read_json_file("../../data/test-cases/json/create_and_sign_transaction_response_dtrust_witness_v0_3of5_252outputs.json");
388 |
389 | t.plan(1);
390 |
391 | client.create_and_sign_transaction({data: prepare_transaction_response, keys: DTRUST_KEYS.slice(0,3)}).then((f) => {
392 | t.deepEqual(f,create_and_sign_transaction_response);
393 | }).catch((r) => {
394 | t.equal(typeof(r),'undefined', "should not throw exception");
395 | });
396 |
397 | });
398 |
399 | test('prepare_dtrust_transaction_response_witness_v0_3of5_253outputs.json', t => {
400 |
401 | const prepare_transaction_response = read_json_file("../../data/test-cases/json/prepare_dtrust_transaction_response_witness_v0_3of5_253outputs.json");
402 | const create_and_sign_transaction_response = read_json_file("../../data/test-cases/json/create_and_sign_transaction_response_dtrust_witness_v0_3of5_253outputs.json");
403 |
404 | t.plan(1);
405 |
406 | client.create_and_sign_transaction({data: prepare_transaction_response, keys: DTRUST_KEYS.slice(0,3)}).then((f) => {
407 | t.deepEqual(f,create_and_sign_transaction_response);
408 | }).catch((r) => {
409 | t.equal(typeof(r),'undefined', "should not throw exception");
410 | });
411 |
412 | });
413 |
414 | test('prepare_transaction_response_with_blockio_fee_and_expected_unsigned_txid.json', t => {
415 |
416 | const prepare_transaction_response = read_json_file("../../data/test-cases/json/prepare_transaction_response_with_blockio_fee_and_expected_unsigned_txid.json");
417 | const summarize_prepared_transaction_response = read_json_file("../../data/test-cases/json/summarize_prepared_transaction_response_with_blockio_fee_and_expected_unsigned_txid.json");
418 | const create_and_sign_transaction_response = read_json_file("../../data/test-cases/json/create_and_sign_transaction_response_with_blockio_fee_and_expected_unsigned_txid.json");
419 |
420 | t.plan(3);
421 |
422 | client.summarize_prepared_transaction({data: prepare_transaction_response}).then((f) => {
423 | t.deepEqual(f,summarize_prepared_transaction_response);
424 | }).catch((r) => {
425 | console.log(r);
426 | t.equal(typeof(r),'undefined', "should not throw exception");
427 | });
428 |
429 | client.create_and_sign_transaction({data: prepare_transaction_response, keys: []}).then((f) => {
430 | t.deepEqual(f,create_and_sign_transaction_response);
431 | }).catch((r) => {
432 | t.equal(typeof(r),'undefined', "should not throw exception");
433 | });
434 |
435 | // change the expected unsigned txid
436 | prepare_transaction_response.data.expected_unsigned_txid = 'x';
437 | client.create_and_sign_transaction({data: prepare_transaction_response, keys: []}).then((f) => {
438 | t.equal(typeof(f),'undefined', "should have no response");
439 | }).catch((r) => {
440 | t.notEqual(typeof(r),'undefined', "should throw exception");
441 | });
442 |
443 | });
444 |
445 | test('prepare_transaction_response_witness_v1_output.json', t => {
446 |
447 | const prepare_transaction_response = read_json_file("../../data/test-cases/json/prepare_transaction_response_witness_v1_output.json");
448 | const create_and_sign_transaction_response = read_json_file("../../data/test-cases/json/create_and_sign_transaction_response_witness_v1_output.json");
449 |
450 | t.plan(1);
451 |
452 | client.create_and_sign_transaction({data: prepare_transaction_response}).then((f) => {
453 | t.deepEqual(f,create_and_sign_transaction_response);
454 | }).catch((r) => {
455 | t.equal(typeof(r),'undefined', "should not throw exception");
456 | });
457 |
458 | });
459 |
460 |
--------------------------------------------------------------------------------