├── .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 | --------------------------------------------------------------------------------