├── .editorconfig ├── .travis.yml ├── .gitignore ├── LICENSE ├── package.json ├── README.md ├── index.js └── test └── index.js /.editorconfig: -------------------------------------------------------------------------------- 1 | ; editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.json] 13 | indent_size = 2 14 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - "0.10" 5 | - "0.12" 6 | - "4" 7 | - "5" 8 | env: 9 | matrix: 10 | - TEST_SUITE=test 11 | matrix: 12 | include: 13 | - node_js: "4" 14 | env: TEST_SUITE=lint 15 | script: npm run $TEST_SUITE 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # Compiled binary addons (http://nodejs.org/api/addons.html) 20 | build/Release 21 | 22 | # Dependency directory 23 | # Commenting this out is preferred by some people, see 24 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git- 25 | node_modules 26 | 27 | # Users Environment Variables 28 | .lock-wscript 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014, 2015 AJoseph 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 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bitcoin-proof", 3 | "version": "2.0.0", 4 | "description": "Merkle proof for a Bitcoin transaction", 5 | "main": "index.js", 6 | "keywords": [ 7 | "bitcoin", 8 | "tx", 9 | "transaction", 10 | "proof", 11 | "merkle", 12 | "root", 13 | "light", 14 | "client", 15 | "ethereum" 16 | ], 17 | "homepage": "https://github.com/ethers/bitcoin-proof", 18 | "bugs": { 19 | "url": "https://github.com/ethers/bitcoin-proof/issues" 20 | }, 21 | "license": "MIT", 22 | "author": "AJoseph", 23 | "contributors": [ 24 | "Kirill Fomichev " 25 | ], 26 | "files": [ 27 | "index.js", 28 | "LICENSE", 29 | "README.md" 30 | ], 31 | "main": "./index.js", 32 | "repository": { 33 | "type": "git", 34 | "url": "https://github.com/ethers/bitcoin-proof.git" 35 | }, 36 | "scripts": { 37 | "prepublish": "npm run lint && npm run test", 38 | "test": "mocha test/index.js", 39 | "lint": "semistandard" 40 | }, 41 | "dependencies": { 42 | }, 43 | "devDependencies": { 44 | "mocha": "^2.0.1", 45 | "semistandard": "^7.0.0" 46 | }, 47 | "engines": { 48 | "node": ">=0.10" 49 | }, 50 | "semistandard": { 51 | "globals": [ 52 | "describe", 53 | "it" 54 | ] 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # bitcoin-proof 2 | 3 | [![NPM Package](https://img.shields.io/npm/v/bitcoin-proof.svg?style=flat-square)](https://www.npmjs.org/package/bitcoin-proof) 4 | [![Build Status](https://img.shields.io/travis/ethers/bitcoin-proof.svg?branch=master&style=flat-square)](https://travis-ci.org/ethers/bitcoin-proof) 5 | [![js-standard-style](https://img.shields.io/badge/code%20style-semistandard-brightgreen.svg?style=flat-square)](https://github.com/Flet/semistandard) 6 | [![Dependency status](https://img.shields.io/david/ethers/bitcoin-proof.svg?style=flat-square)](https://david-dm.org/ethers/bitcoin-proof#info=dependencies) 7 | 8 | Merkle proof for a Bitcoin transaction. 9 | 10 | ## Install 11 | 12 | ``` 13 | npm install bitcoin-proof 14 | ``` 15 | 16 | ## API 17 | 18 | - [`getProof(String[] txIds, Number txIndex)`](#getproofstring-txids-number-txindex---txid-string-txindex-number-sibling-string) 19 | - [`getTxMerkle({txId: String, txIndex: Number, sibling: String[]})`](#gettxmerkletxid-string-txindex-number-sibling-string---string) 20 | - [`getMerkleRoot(String[] txIds)`](#getmerklerootstring-txids---string) 21 | 22 | ---- 23 | 24 | #####`getProof(String[] txIds, Number txIndex)` -> `{txId: String, txIndex: Number, sibling: String[]}` 25 | 26 | Computes the Merkle proof of a given transaction. 27 | 28 | * `txIds` - array of transaction hashes (as hex string) 29 | * `txIndex` - index of which transaction to compute a Merkle proof for 30 | 31 | Returns an object with the following keys: 32 | * `txId` - transaction hash that the Merkle proof is computed for 33 | * `txIndex` - index of `txId` among `txIds` 34 | * `sibling` - sibling hashes of `txId` which comprise the Merkle proof 35 | 36 | ---- 37 | 38 | #####`getTxMerkle({txId: String, txIndex: Number, sibling: String[]})` -> `String` 39 | 40 | Computes the Merkle root of a given proof. 41 | 42 | * `proof` - an object of the form returned by `getProof` 43 | 44 | Returns the Merkle root as a hex string. 45 | 46 | ---- 47 | 48 | #####`getMerkleRoot(String[] txIds)` -> `String` 49 | 50 | Computes the Merkle root of a set of transactions. 51 | 52 | * `txIds` - array of transaction hashes (as hex string) 53 | 54 | Returns the Merkle root as a hex string. 55 | 56 | ## Usage 57 | 58 | ```javascript 59 | var btcProof = require('bitcoin-proof'); 60 | 61 | var BLOCK_100K_TRANSACTIONS = [ 62 | '8c14f0db3df150123e6f3dbbf30f8b955a8249b62ac1d1ff16284aefa3d06d87', 63 | 'fff2525b8931402dd09222c50775608f75787bd2b87e56995a7bdd30f79702c4', 64 | '6359f0868171b1d194cbee1af2f16ea598ae8fad666d9b012c8ed2b79a236ec4', 65 | 'e9a66845e05d5abc0ad04ec80f774a7e585c6e8db975962d069a522137b80c1d' 66 | ]; 67 | 68 | var proofOfFirstTx = btcProof.getProof(BLOCK_100K_TRANSACTIONS, 0); 69 | // { 70 | // txId: '8c14f0db3df150123e6f3dbbf30f8b955a8249b62ac1d1ff16284aefa3d06d87', 71 | // txIndex: 0, 72 | // sibling: [ 73 | // 'fff2525b8931402dd09222c50775608f75787bd2b87e56995a7bdd30f79702c4', 74 | // '8e30899078ca1813be036a073bbf80b86cdddde1c96e9e9c99e9e3782df4ae49' 75 | // ] 76 | // } 77 | 78 | var proofOfLastTx = btcProof.getProof(BLOCK_100K_TRANSACTIONS, 3); 79 | // { 80 | // txId: 'e9a66845e05d5abc0ad04ec80f774a7e585c6e8db975962d069a522137b80c1d', 81 | // txIndex: 0, 82 | // sibling: [ 83 | // '6359f0868171b1d194cbee1af2f16ea598ae8fad666d9b012c8ed2b79a236ec4', 84 | // 'ccdafb73d8dcd0173d5d5c3c9a0770d0b3953db889dab99ef05b1907518cb815' 85 | // ] 86 | // } 87 | ``` 88 | 89 | ## License 90 | 91 | [MIT](LICENSE) 92 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var createHash = require('crypto').createHash; 2 | 3 | /** 4 | * @param {Buffer} buf1 5 | * @return {Buffer} 6 | */ 7 | function sha256x2 (buf1, buf2) { 8 | var buf = createHash('sha256').update(buf1).update(buf2).digest(); 9 | return createHash('sha256').update(buf).digest(); 10 | } 11 | 12 | /** 13 | * Reverse CURRENT buffer 14 | * @param {Buffer} buf 15 | * @return {Buffer} 16 | */ 17 | function reverse (buf) { 18 | return Array.prototype.reverse.call(buf); 19 | } 20 | 21 | /** 22 | * @param {Buffer} buf1 23 | * @param {Buffer} buf2 24 | * @return {Buffer} 25 | */ 26 | function isEqual (buf1, buf2) { 27 | if (buf1.length !== buf2.length) { 28 | return false; 29 | } 30 | 31 | for (var i = 0; i < buf1.length; ++i) { 32 | if (buf1[i] !== buf2[i]) { 33 | return false; 34 | } 35 | } 36 | 37 | return true; 38 | } 39 | 40 | /** 41 | * @typedef {Object} ProofObject 42 | * @param {string} txId 43 | * @param {number} txIndex 44 | * @param {string[]} sibling 45 | */ 46 | 47 | /** 48 | * @param {string[]} txIds 49 | * @param {number} txIndex 50 | * @return {ProofObject} 51 | */ 52 | exports.getProof = function (txIds, txIndex) { 53 | var proof = { 54 | txId: txIds[txIndex], 55 | txIndex: txIndex, 56 | sibling: [] 57 | }; 58 | 59 | var tree = new Array(txIds.length); 60 | for (var i = 0; i < tree.length; ++i) { 61 | tree[i] = reverse(new Buffer(txIds[i], 'hex')); 62 | } 63 | var target = tree[txIndex]; 64 | 65 | while (tree.length !== 1) { 66 | var newTree = new Array(~~((tree.length + 1) / 2)); 67 | for (var j = 0; j < tree.length; j += 2) { 68 | var hash1 = tree[j]; 69 | var hash2 = tree[Math.min(j + 1, tree.length - 1)]; 70 | 71 | newTree[j / 2] = sha256x2(hash1, hash2); 72 | 73 | if (isEqual(target, hash1)) { 74 | proof.sibling.push(reverse(hash2).toString('hex')); 75 | target = newTree[j / 2]; 76 | } else if (isEqual(target, hash2)) { 77 | proof.sibling.push(reverse(hash1).toString('hex')); 78 | target = newTree[j / 2]; 79 | } 80 | } 81 | 82 | tree = newTree; 83 | } 84 | 85 | return proof; 86 | }; 87 | 88 | /** 89 | * @param {ProofObject} proofObj 90 | * @return {string} 91 | */ 92 | exports.getTxMerkle = function (proofObj) { 93 | var target = reverse(new Buffer(proofObj.txId, 'hex')); 94 | var txIndex = proofObj.txIndex; 95 | var sibling = proofObj.sibling; 96 | 97 | for (var i = 0; i < proofObj.sibling.length; ++i, txIndex = ~~(txIndex / 2)) { 98 | if (txIndex % 2 === 1) { 99 | target = sha256x2(reverse(new Buffer(sibling[i], 'hex')), target); 100 | } else { 101 | target = sha256x2(target, reverse(new Buffer(sibling[i], 'hex'))); 102 | } 103 | } 104 | 105 | return reverse(target).toString('hex'); 106 | }; 107 | 108 | /** 109 | * @param {string[]} txIds 110 | * @return {string} 111 | */ 112 | exports.getMerkleRoot = function (txIds) { 113 | var tree = new Array(txIds.length); 114 | for (var i = 0; i < tree.length; ++i) { 115 | tree[i] = reverse(new Buffer(txIds[i], 'hex')); 116 | } 117 | 118 | while (tree.length !== 1) { 119 | var newTree = new Array(~~((tree.length + 1) / 2)); 120 | for (var j = 0; j < tree.length; j += 2) { 121 | var hash1 = tree[j]; 122 | var hash2 = tree[Math.min(j + 1, tree.length - 1)]; 123 | 124 | newTree[j / 2] = sha256x2(hash1, hash2); 125 | } 126 | 127 | tree = newTree; 128 | } 129 | 130 | return reverse(tree[0]).toString('hex'); 131 | }; 132 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | var btcProof = require('../index'); 2 | var assert = require('assert'); 3 | 4 | var BLOCK_100K = { 5 | txs: [ 6 | '8c14f0db3df150123e6f3dbbf30f8b955a8249b62ac1d1ff16284aefa3d06d87', 7 | 'fff2525b8931402dd09222c50775608f75787bd2b87e56995a7bdd30f79702c4', 8 | '6359f0868171b1d194cbee1af2f16ea598ae8fad666d9b012c8ed2b79a236ec4', 9 | 'e9a66845e05d5abc0ad04ec80f774a7e585c6e8db975962d069a522137b80c1d' 10 | ], 11 | merkleRoot: 'f3e94742aca4b5ef85488dc37c06c3282295ffec960994b2c0d5ac2a25a95766', 12 | // >>> i = '8c14f0db3df150123e6f3dbbf30f8b955a8249b62ac1d1ff16284aefa3d06d87'.decode('hex')[::-1] 13 | // >>> j = 'fff2525b8931402dd09222c50775608f75787bd2b87e56995a7bdd30f79702c4'.decode('hex')[::-1] 14 | // >>> dbl_sha256(i+j).decode('hex')[::-1].encode('hex') 15 | // 'ccdafb73d8dcd0173d5d5c3c9a0770d0b3953db889dab99ef05b1907518cb815' 16 | hashLeftPair: 'ccdafb73d8dcd0173d5d5c3c9a0770d0b3953db889dab99ef05b1907518cb815', 17 | // >>> i = '6359f0868171b1d194cbee1af2f16ea598ae8fad666d9b012c8ed2b79a236ec4'.decode('hex')[::-1] 18 | // >>> j = 'e9a66845e05d5abc0ad04ec80f774a7e585c6e8db975962d069a522137b80c1d'.decode('hex')[::-1] 19 | // >>> dbl_sha256(i+j).decode('hex')[::-1].encode('hex') 20 | // '8e30899078ca1813be036a073bbf80b86cdddde1c96e9e9c99e9e3782df4ae49' 21 | hashRightPair: '8e30899078ca1813be036a073bbf80b86cdddde1c96e9e9c99e9e3782df4ae49' 22 | }; 23 | 24 | var BLOCK_106022 = { 25 | txs: [ 26 | '3a459eab5f0cf8394a21e04d2ed3b2beeaa59795912e20b9c680e9db74dfb18c', 27 | 'be38f46f0eccba72416aed715851fd07b881ffb7928b7622847314588e06a6b7', 28 | 'd173f2a12b6ff63a77d9fe7bbb590bdb02b826d07739f90ebb016dc9297332be', 29 | '59d1e83e5268bbb491234ff23cbbf2a7c0aa87df553484afee9e82385fc7052f', 30 | 'f1ce77a69d06efb79e3b08a0ff441fa3b1deaf71b358df55244d56dd797ac60c', 31 | '84053cba91fe659fd3afa1bf2fd0e3746b99215b50cd74e44bda507d8edf52e0' 32 | ] 33 | }; 34 | 35 | describe('getProof', function () { 36 | describe('for block 100k', function () { 37 | it('tx[0]', function () { 38 | var txs = BLOCK_100K.txs; 39 | var txIndex = 0; 40 | var expProof = { 41 | txId: txs[txIndex], 42 | txIndex: txIndex, 43 | sibling: [txs[1], BLOCK_100K.hashRightPair] 44 | }; 45 | 46 | var proof = btcProof.getProof(txs, txIndex); 47 | assert.strictEqual(JSON.stringify(proof), JSON.stringify(expProof)); 48 | }); 49 | 50 | it('tx[1]', function () { 51 | var txs = BLOCK_100K.txs; 52 | var txIndex = 1; 53 | var expProof = { 54 | txId: txs[txIndex], 55 | txIndex: txIndex, 56 | sibling: [txs[0], BLOCK_100K.hashRightPair] 57 | }; 58 | 59 | var proof = btcProof.getProof(txs, 1); 60 | assert.strictEqual(JSON.stringify(proof), JSON.stringify(expProof)); 61 | }); 62 | 63 | it('tx[2]', function () { 64 | var txs = BLOCK_100K.txs; 65 | var txIndex = 2; 66 | var expProof = { 67 | txId: txs[txIndex], 68 | txIndex: txIndex, 69 | sibling: [txs[3], BLOCK_100K.hashLeftPair] 70 | }; 71 | 72 | var proof = btcProof.getProof(txs, 2); 73 | assert.strictEqual(JSON.stringify(proof), JSON.stringify(expProof)); 74 | }); 75 | 76 | it('tx[3]', function () { 77 | var txs = BLOCK_100K.txs; 78 | var txIndex = 3; 79 | var expProof = { 80 | txId: txs[txIndex], 81 | txIndex: txIndex, 82 | sibling: [txs[2], BLOCK_100K.hashLeftPair] 83 | }; 84 | 85 | var proof = btcProof.getProof(txs, 3); 86 | assert.strictEqual(JSON.stringify(proof), JSON.stringify(expProof)); 87 | }); 88 | }); 89 | 90 | it('for block 106022 tx[0]', function () { 91 | var txs = BLOCK_106022.txs; 92 | var txIndex = 0; 93 | var expProof = { 94 | txId: txs[txIndex], 95 | txIndex: txIndex, 96 | sibling: [ 97 | txs[1], 98 | 'f6ae335dc2d2aecb6a255ebd03caaf6820e6c0534531051066810080e0d822c8', 99 | '15eca0aa3e2cc2b9b4fbe0629f1dda87f329500fcdcd6ef546d163211266b3b3' 100 | ] 101 | }; 102 | 103 | var proof = btcProof.getProof(txs, 0); 104 | assert.strictEqual(JSON.stringify(proof), JSON.stringify(expProof)); 105 | }); 106 | }); 107 | 108 | describe('getMerkleRoot', function () { 109 | it('should return merkle root of block 100k', function () { 110 | var txs = BLOCK_100K.txs; 111 | var merkle = btcProof.getMerkleRoot(txs); 112 | assert.strictEqual(merkle, BLOCK_100K.merkleRoot); 113 | }); 114 | 115 | it('should return merkle root of block 100k-1 (odd num of tx)', function () { 116 | var txs = [ 117 | '110ed92f558a1e3a94976ddea5c32f030670b5c58c3cc4d857ac14d7a1547a90' 118 | ]; 119 | var expMerkle = '110ed92f558a1e3a94976ddea5c32f030670b5c58c3cc4d857ac14d7a1547a90'; 120 | 121 | var merkle = btcProof.getMerkleRoot(txs); 122 | assert.strictEqual(merkle, expMerkle); 123 | }); 124 | 125 | it('should return merkle root of block 106022', function () { 126 | var txs = BLOCK_106022.txs; 127 | var expMerkle = '9cdf7722eb64015731ba9794e32bdefd9cf69b42456d31f5e59aedb68c57ed52'; 128 | 129 | var merkle = btcProof.getMerkleRoot(txs); 130 | assert.strictEqual(merkle, expMerkle); 131 | }); 132 | }); 133 | 134 | describe('getTxMerkle', function () { 135 | describe('for block 100k', function () { 136 | it('tx[0]', function () { 137 | var txs = BLOCK_100K.txs; 138 | var txIndex = 0; 139 | var proof = { 140 | txId: txs[txIndex], 141 | txIndex: txIndex, 142 | sibling: [txs[1], BLOCK_100K.hashRightPair] 143 | }; 144 | 145 | var merkle = btcProof.getTxMerkle(proof); 146 | assert.strictEqual(merkle, BLOCK_100K.merkleRoot); 147 | }); 148 | 149 | it('tx[1]', function () { 150 | var txs = BLOCK_100K.txs; 151 | var txIndex = 1; 152 | var proof = { 153 | txId: txs[txIndex], 154 | txIndex: txIndex, 155 | sibling: [txs[0], BLOCK_100K.hashRightPair] 156 | }; 157 | 158 | var merkle = btcProof.getTxMerkle(proof); 159 | assert.strictEqual(merkle, BLOCK_100K.merkleRoot); 160 | }); 161 | 162 | it('tx[2]', function () { 163 | var txs = BLOCK_100K.txs; 164 | var txIndex = 2; 165 | var proof = { 166 | txId: txs[txIndex], 167 | txIndex: txIndex, 168 | sibling: [txs[3], BLOCK_100K.hashLeftPair] 169 | }; 170 | 171 | var merkle = btcProof.getTxMerkle(proof); 172 | assert.strictEqual(merkle, BLOCK_100K.merkleRoot); 173 | }); 174 | 175 | it('tx[3]', function () { 176 | var txs = BLOCK_100K.txs; 177 | var txIndex = 3; 178 | var proof = { 179 | txId: txs[txIndex], 180 | txIndex: txIndex, 181 | sibling: [txs[2], BLOCK_100K.hashLeftPair] 182 | }; 183 | 184 | var merkle = btcProof.getTxMerkle(proof); 185 | assert.strictEqual(merkle, BLOCK_100K.merkleRoot); 186 | }); 187 | }); 188 | 189 | it('for block 106022 tx[0]', function () { 190 | var txs = BLOCK_106022.txs; 191 | var txIndex = 0; 192 | var proof = { 193 | txId: txs[txIndex], 194 | txIndex: txIndex, 195 | sibling: [ 196 | txs[1], 197 | 'f6ae335dc2d2aecb6a255ebd03caaf6820e6c0534531051066810080e0d822c8', 198 | '15eca0aa3e2cc2b9b4fbe0629f1dda87f329500fcdcd6ef546d163211266b3b3' 199 | ] 200 | }; 201 | var expMerkle = '9cdf7722eb64015731ba9794e32bdefd9cf69b42456d31f5e59aedb68c57ed52'; 202 | 203 | var merkle = btcProof.getTxMerkle(proof); 204 | assert.strictEqual(merkle, expMerkle); 205 | }); 206 | 207 | it('for testnet tx[9] block 350559', function () { 208 | var proof = { 209 | txId: 'a51a71f8094f9b4e266fcccd55068e809277ec79bfa44b7bdb8f1355e9bb8460', 210 | txIndex: '9', 211 | sibling: [ 212 | '4f5d49d7a06fd3ace3d5f2e571546934653211b139222cc8284ab863d1f6e29a', 213 | '0fb8ebfdb2bcdb24ac10faf5cd474f07eef52da052805b8de5619be4190c992f', 214 | '16dfbab76bbdc3e7306d185ce7853c20cc067c3a5614aed3684b5755cf036a10', 215 | '474de8433d89421ca53879d33f0e8c19f64c7b5683c47dd7b0cc1db52c4fb3bc', 216 | '5ccbd3dfc316ab4b32b7281ec29f085716ab0320746240905a97f331f0da8c3c' 217 | ] 218 | }; 219 | var expMerkle = '5f14eb3b7ae064698e458cd75e3c40821688d4abe79cebded2f616495140ac56'; 220 | 221 | var merkle = btcProof.getTxMerkle(proof); 222 | assert.strictEqual(merkle, expMerkle); 223 | }); 224 | }); 225 | --------------------------------------------------------------------------------