diff --git a/.gitignore b/.gitignore index adcb09ced..0abc934bd 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ -node_modules -bitcoinjs-min.js +bitcoin.js coverage +node_modules diff --git a/.npmignore b/.npmignore new file mode 100644 index 000000000..74e448642 --- /dev/null +++ b/.npmignore @@ -0,0 +1,4 @@ +.gitignore +.travis.yml +jshint.json +test/ diff --git a/LICENSE b/LICENSE index a9c891371..7f5a4ce67 100644 --- a/LICENSE +++ b/LICENSE @@ -1,7 +1,21 @@ -Copyright (c) 2011 Stefan Thomas +The MIT License (MIT) -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +Copyright (c) 2011-2015 Bitcoinjs-lib contributors -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 56599fcd1..587bbb09a 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,11 @@ # BitcoinJS (bitcoinjs-lib) -[![Build Status](https://travis-ci.org/bitcoinjs/bitcoinjs-lib.png?branch=master)](https://travis-ci.org/bitcoinjs/bitcoinjs-lib) [![Coverage Status](https://coveralls.io/repos/bitcoinjs/bitcoinjs-lib/badge.png)](https://coveralls.io/r/bitcoinjs/bitcoinjs-lib) +[![Build Status](https://travis-ci.org/bitcoinjs/bitcoinjs-lib.png?branch=master)](https://travis-ci.org/bitcoinjs/bitcoinjs-lib) +[![Coverage Status](https://coveralls.io/repos/bitcoinjs/bitcoinjs-lib/badge.png)](https://coveralls.io/r/bitcoinjs/bitcoinjs-lib) +[![tip for next commit](http://tip4commit.com/projects/735.svg)](http://tip4commit.com/projects/735) [![NPM](https://nodei.co/npm/bitcoinjs-lib.png)](https://nodei.co/npm/bitcoinjs-lib/) -[![Browser Support](https://ci.testling.com/bitcoinjs/bitcoinjs-lib.png)](https://ci.testling.com/bitcoinjs/bitcoinjs-lib) - The pure JavaScript Bitcoin library for node.js and browsers. A continued implementation of the original `0.1.3` version used by over a million wallet users; the backbone for almost all Bitcoin web wallets in production today. @@ -26,6 +26,7 @@ A continued implementation of the original `0.1.3` version used by over a millio ## Should I use this in production? + If you are thinking of using the master branch of this library in production, *stop*. Master is not stable; it is our development branch, and only tagged releases may be classified as stable. @@ -43,99 +44,76 @@ If you are looking for the original, it is tagged as `0.1.3`. Unless you need it var bitcoin = require('bitcoinjs-lib') -From the repo: - - var bitcoin = require('./src/index.js') - ### Browser -From the repository: Compile `bitcoinjs-min.js` with the following command: - - $ npm run-script compile - -From NPM: - - $ npm -g install bitcoinjs-lib browserify uglify-js - $ browserify -r bitcoinjs-lib -s Bitcoin | uglifyjs > bitcoinjs.min.js - -After loading this file in your browser, you will be able to use the global `bitcoin` object. +If you're familiar with how to use browserify, ignore this and proceed normally. +These steps are advisory only and allow you to use the API to its full extent. +[Browserify](https://github.com/substack/node-browserify) is assumed to be installed for these steps. -## Usage +From your repository, create a `foobar.js` file -These examples assume you are running bitcoinjs-lib in the browser. - - -### Generating a Bitcoin address - -```javascript - -key = bitcoin.ECKey.makeRandom() - -// Print your private key (in WIF format) -console.log(key.toWIF()) -// => 8c112cf628362ecf4d482f68af2dbb50c8a2cb90d226215de925417aa9336a48 - -// Print your public key (toString defaults to a Bitcoin address) -console.log(key.pub.getAddress().toString()) -// => 14bZ7YWde4KdRb5YN7GYkToz3EHVCvRxkF +``` javascript +var foobar = { + base58: require('bs58'), + bitcoin: require('bitcoinjs-lib'), + ecurve: require('ecurve'), + BigInteger: require('bigi'), + Buffer: require('buffer') +} + +module.exports = foobar ``` -### Creating a Transaction +Each of these included packages are seperate to `bitcoinjs-lib`, and must be installed separately. +They are however used in the bitcoinjs-lib public API. -```javascript -tx = new bitcoin.Transaction() +Using browserify, compile `foobar.js` for use in the browser: -// Add the input (who is paying) of the form [previous transaction hash, index of the output to use] -tx.addInput("aa94ab02c182214f090e99a0d57021caffd0f195a81c24602b1028b130b63e31", 0) + $ browserify foobar.js -s foobar > foobar.js -// Add the output (who to pay to) of the form [payee's address, amount in satoshis] -tx.addOutput("1Gokm82v6DmtwKEB8AiVhm82hyFSsEvBDK", 15000) +You will then be able to load `foobar.js` into your browser, with each of the dependencies above accessible from the global `foobar` object. -// Initialize a private key using WIF -key = bitcoin.ECKey.fromWIF("L1uyy5qTuGrVXrmrsvHWHgVzW9kKdrp27wBC7Vs6nZDTF2BRUVwy") +**NOTE**: See our package.json for the currently supported version of browserify used by this repository. -// Sign the first input with the new key -tx.sign(0, key) -// Print transaction serialized as hex -console.log(tx.toHex()) -// => 0100000001313eb630b128102b60241ca895f1d0ffca2170d5a0990e094f2182c102ab94aa000000008a47304402200169f1f844936dc60df54e812345f5dd3e6681fea52e33c25154ad9cc23a330402204381ed8e73d74a95b15f312f33d5a0072c7a12dd6c3294df6e8efbe4aff27426014104e75628573696aed32d7656fb35e9c71ea08eb6492837e13d2662b9a36821d0fff992692fd14d74fdec20fae29128ba12653249cbeef521fc5eba84dde0689f27ffffffff01983a0000000000001976a914ad618cf4333b3b248f9744e8e81db2964d0ae39788ac00000000 +## Examples -// You could now push the transaction onto the Bitcoin network manually (see https://blockchain.info/pushtx) -``` - -### Creating a P2SH Multsig Address - -``` javascript -var bitcoin = require('bitcoinjs-lib') - -var privKeys = [bitcoin.ECKey.makeRandom(), bitcoin.ECKey.makeRandom(), bitcoin.ECKey.makeRandom()] -var pubKeys = privKeys.map(function(x) { return x.pub }) - -var redeemScript = bitcoin.scripts.multisigOutput(2, pubKeys) // 2 of 3 -var scriptPubKey = bitcoin.scripts.scriptHashOutput(redeemScript.getHash()) - -var multisigAddress = bitcoin.Address.fromOutputScript(scriptPubKey).toString() - -console.log("multisigP2SH:", multisigAddress) -``` +The below examples are implemented as integration tests, they should be very easy to understand. Otherwise, pull requests are appreciated. +- [Generate a random address](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/basic.js#L8) +- [Generate a address from a SHA256 hash](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/basic.js#L20) +- [Import an address via WIF](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/basic.js#L29) +- [Create a Transaction](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/basic.js#L36) +- [Sign a Bitcoin message](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/advanced.js#L9) +- [Verify a Bitcoin message](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/advanced.js#L17) +- [Create an OP RETURN transaction](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/advanced.js#L24) +- [Create a 2-of-3 multisig P2SH address](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/multisig.js#L8) +- [Spend from a 2-of-2 multisig P2SH address](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/multisig.js#L22) +- [Generate a single-key stealth address](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/crypto.js#L7) +- [Generate a dual-key stealth address](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/crypto.js#L42) +- [Recover a BIP32 parent private key from the parent public key and a derived non-hardened child private key](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/crypto.js#L44) +- [Recover a Private key from duplicate R values in a signature](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/crypto.js#L90) ## Projects utilizing BitcoinJS -- [Coinpunk](https://coinpunk.com) -- [Hive Wallet](https://www.hivewallet.com) -- [Justchain Exchange](https://justcoin.com) -- [Skyhook ATM](http://projectskyhook.com) - [BitAddress](https://www.bitaddress.org) - [Blockchain.info](https://blockchain.info/wallet) - [Brainwallet](https://brainwallet.github.io) +- [Coinkite](https://coinkite.com) +- [Coinpunk](https://coinpunk.com) - [Dark Wallet](https://darkwallet.unsystem.net) +- [DecentralBank](http://decentralbank.co) - [Dogechain Wallet](https://dogechain.info) - [GreenAddress](https://greenaddress.it) +- [Hive Wallet](https://www.hivewallet.com) +- [Justchain Exchange](https://justcoin.com) +- [QuickCoin](https://wallet.quickcoin.co) +- [Robocoin](https://wallet.robocoin.com) +- [Skyhook ATM](http://projectskyhook.com) + ## Contributors @@ -145,6 +123,7 @@ Since then, many people have contributed. [Click here](https://github.com/bitcoi Daniel Cousens, Wei Lu, JP Richardson and Kyle Drake lead the major refactor of the library from 0.1.3 to 1.0.0. + ## Contributing Join the ongoing IRC development channel at `#bitcoinjs-dev` on Freenode. @@ -161,9 +140,9 @@ Please make your best effort to adhere to these when contributing to save on tri ## Complementing Libraries -- [bip39](https://github.com/weilu/bip39) - Wei Lu's Mnemonic code generator +- [BIP39](https://github.com/weilu/bip39) - Mnemonic code for generating deterministic keys +- [BIP38](https://github.com/cryptocoinjs/bip38) - Passphrase-protected private keys - [BCoin](https://github.com/indutny/bcoin) - BIP37 / Bloom Filters / SPV client -- [scryptsy](https://github.com/cryptocoinjs/scryptsy) - Private key encryption (BIP38) - [insight](https://github.com/bitpay/insight) - A bitcoin blockchain API for web wallets. @@ -172,6 +151,7 @@ Please make your best effort to adhere to these when contributing to save on tri - [Bitcore](https://github.com/bitpay/bitcore) - [Cryptocoin](https://github.com/cryptocoinjs/cryptocoin) + ## License This library is free and open-source software released under the MIT license. @@ -179,5 +159,5 @@ This library is free and open-source software released under the MIT license. ## Copyright -BitcoinJS (c) 2011-2012 Stefan Thomas +BitcoinJS (c) 2011-2014 Bitcoinjs-lib contributors Released under MIT license diff --git a/package.json b/package.json index 6a5bb83df..b75fdd521 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "bitcoinjs-lib", - "version": "1.0.0", + "version": "1.4.5", "description": "Client-side Bitcoin JavaScript library", "main": "./src/index.js", "keywords": [ @@ -31,53 +31,35 @@ "url": "http://www.justmoon.net" } ], + "scripts": { + "compile": "browserify ./src/index.js -s bitcoin > bitcoin.js", + "coverage": "istanbul cover _mocha -- test/*.js", + "coveralls": "npm run-script coverage && coveralls < coverage/lcov.info", + "integration": "mocha --reporter list test/integration/*.js", + "jshint": "jshint --config jshint.json src/*.js ; true", + "test": "npm run-script unit", + "unit": "istanbul test mocha -- --reporter list test/*.js" + }, "repository": { "type": "git", "url": "https://github.com/bitcoinjs/bitcoinjs-lib.git" }, + "dependencies": { + "bigi": "^1.4.0", + "bs58check": "^1.0.4", + "ecurve": "^1.0.0", + "typeforce": "^0.1.0" + }, "devDependencies": { - "browserify": "4.1.11", - "coveralls": "~2.10.0", - "helloblock-js": "^0.2.1", - "istanbul": "0.1.30", - "jshint": "2.5.1", - "mocha": "1.18.2", + "async": "^0.9.0", + "browserify": "^9.0.3", + "bs58": "^2.0.1", + "cb-helloblock": "^0.4.10", + "coveralls": "^2.11.2", + "istanbul": "^0.3.5", + "jshint": "^2.5.11", + "mocha": "^2.1.0", "mocha-lcov-reporter": "0.0.1", - "sinon": "1.9.0", - "uglify-js": "2.4.13" - }, - "testling": { - "browsers": [ - "android-browser/4.2..latest", - "chrome/20..latest", - "firefox/21..latest", - "ipad/6..latest", - "iphone/6..latest", - "opera/15..latest", - "safari/latest" - ], - "harness": "mocha-bdd", - "files": "test/*.js" - }, - "scripts": { - "compile": "./node_modules/.bin/browserify ./src/index.js -s Bitcoin | ./node_modules/.bin/uglifyjs > bitcoinjs-min.js", - "coverage": "./node_modules/.bin/istanbul cover ./node_modules/.bin/_mocha -- --reporter list test/*.js", - "coveralls": "npm run-script coverage && node ./node_modules/.bin/coveralls < coverage/lcov.info", - "integration": "./node_modules/.bin/_mocha --reporter list test/integration/*.js", - "jshint": "./node_modules/.bin/jshint --config jshint.json src/*.js ; true", - "test": "npm run-script unit", - "unit": "./node_modules/.bin/istanbul test ./node_modules/.bin/_mocha -- --reporter list `find test -maxdepth 1 -not -type d`" - }, - "browser": { - "crypto": "crypto-browserify" - }, - "dependencies": { - "bigi": "1.1.0", - "bs58": "1.1.0", - "bs58check": "1.0.0", - "crypto-js": "3.1.2-3", - "crypto-browserify": "2.1.8", - "ecurve": "0.10.0", - "secure-random": "1.1.1" + "sinon": "^1.12.2" } } diff --git a/src/address.js b/src/address.js index fde2b2842..e87351598 100644 --- a/src/address.js +++ b/src/address.js @@ -1,5 +1,6 @@ var assert = require('assert') var base58check = require('bs58check') +var typeForce = require('typeforce') var networks = require('./networks') var scripts = require('./scripts') @@ -13,7 +14,8 @@ function findScriptTypeByVersion(version) { } function Address(hash, version) { - assert(Buffer.isBuffer(hash), 'Expected Buffer, got ' + hash) + typeForce('Buffer', hash) + assert.strictEqual(hash.length, 20, 'Invalid hash length') assert.strictEqual(version & 0xff, version, 'Invalid version byte') @@ -21,7 +23,6 @@ function Address(hash, version) { this.version = version } -// Import functions Address.fromBase58Check = function(string) { var payload = base58check.decode(string) var version = payload.readUInt8(0) @@ -33,15 +34,12 @@ Address.fromBase58Check = function(string) { Address.fromOutputScript = function(script, network) { network = network || networks.bitcoin - var type = scripts.classifyOutput(script) - - if (type === 'pubkeyhash') return new Address(script.chunks[2], network.pubKeyHash) - if (type === 'scripthash') return new Address(script.chunks[1], network.scriptHash) + if (scripts.isPubKeyHashOutput(script)) return new Address(script.chunks[2], network.pubKeyHash) + if (scripts.isScriptHashOutput(script)) return new Address(script.chunks[1], network.scriptHash) - assert(false, type + ' has no matching Address') + assert(false, script.toASM() + ' has no matching Address') } -// Export functions Address.prototype.toBase58Check = function () { var payload = new Buffer(21) payload.writeUInt8(this.version, 0) diff --git a/src/base58check.js b/src/base58check.js new file mode 100644 index 000000000..8108a0b05 --- /dev/null +++ b/src/base58check.js @@ -0,0 +1,18 @@ +var bs58check = require('bs58check') + +function decode() { + console.warn('bs58check will be removed in 2.0.0. require("bs58check") instead.'); + + return bs58check.decode.apply(undefined, arguments) +} + +function encode() { + console.warn('bs58check will be removed in 2.0.0. require("bs58check") instead.'); + + return bs58check.encode.apply(undefined, arguments) +} + +module.exports = { + decode: decode, + encode: encode +} diff --git a/src/block.js b/src/block.js new file mode 100644 index 000000000..3bef1655b --- /dev/null +++ b/src/block.js @@ -0,0 +1,121 @@ +var assert = require('assert') +var bufferutils = require('./bufferutils') +var crypto = require('./crypto') + +var Transaction = require('./transaction') +var Script = require('./script') + +function Block() { + this.version = 1 + this.prevHash = null + this.merkleRoot = null + this.timestamp = 0 + this.bits = 0 + this.nonce = 0 +} + +Block.fromBuffer = function(buffer) { + assert(buffer.length >= 80, 'Buffer too small (< 80 bytes)') + + var offset = 0 + function readSlice(n) { + offset += n + return buffer.slice(offset - n, offset) + } + + function readUInt32() { + var i = buffer.readUInt32LE(offset) + offset += 4 + return i + } + + var block = new Block() + block.version = readUInt32() + block.prevHash = readSlice(32) + block.merkleRoot = readSlice(32) + block.timestamp = readUInt32() + block.bits = readUInt32() + block.nonce = readUInt32() + + if (buffer.length === 80) return block + + function readVarInt() { + var vi = bufferutils.readVarInt(buffer, offset) + offset += vi.size + return vi.number + } + + // FIXME: poor performance + function readTransaction() { + var tx = Transaction.fromBuffer(buffer.slice(offset), true) + + offset += tx.toBuffer().length + return tx + } + + var nTransactions = readVarInt() + block.transactions = [] + + for (var i = 0; i < nTransactions; ++i) { + var tx = readTransaction() + block.transactions.push(tx) + } + + return block +} + +Block.fromHex = function(hex) { + return Block.fromBuffer(new Buffer(hex, 'hex')) +} + +Block.prototype.getHash = function() { + return crypto.hash256(this.toBuffer(true)) +} + +Block.prototype.getId = function() { + return bufferutils.reverse(this.getHash()).toString('hex') +} + +Block.prototype.getUTCDate = function() { + var date = new Date(0) // epoch + date.setUTCSeconds(this.timestamp) + + return date +} + +Block.prototype.toBuffer = function(headersOnly) { + var buffer = new Buffer(80) + + var offset = 0 + function writeSlice(slice) { + slice.copy(buffer, offset) + offset += slice.length + } + + function writeUInt32(i) { + buffer.writeUInt32LE(i, offset) + offset += 4 + } + + writeUInt32(this.version) + writeSlice(this.prevHash) + writeSlice(this.merkleRoot) + writeUInt32(this.timestamp) + writeUInt32(this.bits) + writeUInt32(this.nonce) + + if (headersOnly || !this.transactions) return buffer + + var txLenBuffer = bufferutils.varIntBuffer(this.transactions.length) + var txBuffers = this.transactions.map(function(tx) { + return tx.toBuffer() + }) + + return Buffer.concat([buffer, txLenBuffer].concat(txBuffers)) +} + +Block.prototype.toHex = function(headersOnly) { + return this.toBuffer(headersOnly).toString('hex') +} + +module.exports = Block diff --git a/src/bufferutils.js b/src/bufferutils.js index 05364e1f3..644db884c 100644 --- a/src/bufferutils.js +++ b/src/bufferutils.js @@ -159,11 +159,27 @@ function writeVarInt(buffer, number, offset) { return size } +function varIntBuffer(i) { + var size = varIntSize(i) + var buffer = new Buffer(size) + writeVarInt(buffer, i, 0) + + return buffer +} + +function reverse(buffer) { + var buffer2 = new Buffer(buffer) + Array.prototype.reverse.call(buffer2) + return buffer2 +} + module.exports = { pushDataSize: pushDataSize, readPushDataInt: readPushDataInt, readUInt64LE: readUInt64LE, readVarInt: readVarInt, + reverse: reverse, + varIntBuffer: varIntBuffer, varIntSize: varIntSize, writePushDataInt: writePushDataInt, writeUInt64LE: writeUInt64LE, diff --git a/src/convert.js b/src/convert.js deleted file mode 100644 index 85488660e..000000000 --- a/src/convert.js +++ /dev/null @@ -1,32 +0,0 @@ -var assert = require('assert') -var Crypto = require('crypto-js') -var WordArray = Crypto.lib.WordArray - -function bufferToWordArray(buffer) { - assert(Buffer.isBuffer(buffer), 'Expected Buffer, got', buffer) - - var words = [] - for (var i = 0, b = 0; i < buffer.length; i++, b += 8) { - words[b >>> 5] |= buffer[i] << (24 - b % 32) - } - - return new WordArray.init(words, buffer.length) -} - -function wordArrayToBuffer(wordArray) { - assert(Array.isArray(wordArray.words), 'Expected WordArray, got' + wordArray) - - var words = wordArray.words - var buffer = new Buffer(words.length * 4) - - words.forEach(function(value, i) { - buffer.writeInt32BE(value & -1, i * 4) - }) - - return buffer -} - -module.exports = { - bufferToWordArray: bufferToWordArray, - wordArrayToBuffer: wordArrayToBuffer -} diff --git a/src/crypto.js b/src/crypto.js index 1c8423b3b..e62e14b87 100644 --- a/src/crypto.js +++ b/src/crypto.js @@ -1,8 +1,4 @@ -// Crypto, crypto, where art thou crypto -var assert = require('assert') -var CryptoJS = require('crypto-js') var crypto = require('crypto') -var convert = require('./convert') function hash160(buffer) { return ripemd160(sha256(buffer)) @@ -26,19 +22,13 @@ function sha256(buffer) { // FIXME: Name not consistent with others function HmacSHA256(buffer, secret) { + console.warn('Hmac* functions are deprecated for removal in 2.0.0, use node crypto instead') return crypto.createHmac('sha256', secret).update(buffer).digest() } -function HmacSHA512(data, secret) { - assert(Buffer.isBuffer(data), 'Expected Buffer for data, got ' + data) - assert(Buffer.isBuffer(secret), 'Expected Buffer for secret, got ' + secret) - - var dataWords = convert.bufferToWordArray(data) - var secretWords = convert.bufferToWordArray(secret) - - var hash = CryptoJS.HmacSHA512(dataWords, secretWords) - - return convert.wordArrayToBuffer(hash) +function HmacSHA512(buffer, secret) { + console.warn('Hmac* functions are deprecated for removal in 2.0.0, use node crypto instead') + return crypto.createHmac('sha512', secret).update(buffer).digest() } module.exports = { diff --git a/src/ecdsa.js b/src/ecdsa.js index aeba1de4e..63c5ceffc 100644 --- a/src/ecdsa.js +++ b/src/ecdsa.js @@ -1,20 +1,54 @@ var assert = require('assert') -var crypto = require('./crypto') +var crypto = require('crypto') +var typeForce = require('typeforce') var BigInteger = require('bigi') var ECSignature = require('./ecsignature') -var Point = require('ecurve').Point + +var ZERO = new Buffer([0]) +var ONE = new Buffer([1]) // https://tools.ietf.org/html/rfc6979#section-3.2 -function deterministicGenerateK(curve, hash, d) { - assert(Buffer.isBuffer(hash), 'Hash must be a Buffer, not ' + hash) +function deterministicGenerateK(curve, hash, d, checkSig) { + typeForce('Buffer', hash) + typeForce('BigInteger', d) + + // FIXME: remove/uncomment for 2.0.0 +// typeForce('Function', checkSig) + + if (typeof checkSig !== 'function') { + console.warn('deterministicGenerateK requires a checkSig callback in 2.0.0, see #337 for more information') + + checkSig = function(k) { + var G = curve.G + var n = curve.n + var e = BigInteger.fromBuffer(hash) + + var Q = G.multiply(k) + + if (curve.isInfinity(Q)) + return false + + var r = Q.affineX.mod(n) + if (r.signum() === 0) + return false + + var s = k.modInverse(n).multiply(e.add(d.multiply(r))).mod(n) + if (s.signum() === 0) + return false + + return true + } + } + + // sanity check assert.equal(hash.length, 32, 'Hash must be 256 bit') - assert(d instanceof BigInteger, 'Private key must be a BigInteger') var x = d.toBuffer(32) var k = new Buffer(32) var v = new Buffer(32) + // Step A, ignored as hash already provided // Step B v.fill(1) @@ -22,28 +56,45 @@ function deterministicGenerateK(curve, hash, d) { k.fill(0) // Step D - k = crypto.HmacSHA256(Buffer.concat([v, new Buffer([0]), x, hash]), k) + k = crypto.createHmac('sha256', k) + .update(v) + .update(ZERO) + .update(x) + .update(hash) + .digest() // Step E - v = crypto.HmacSHA256(v, k) + v = crypto.createHmac('sha256', k).update(v).digest() // Step F - k = crypto.HmacSHA256(Buffer.concat([v, new Buffer([1]), x, hash]), k) + k = crypto.createHmac('sha256', k) + .update(v) + .update(ONE) + .update(x) + .update(hash) + .digest() // Step G - v = crypto.HmacSHA256(v, k) + v = crypto.createHmac('sha256', k).update(v).digest() // Step H1/H2a, ignored as tlen === qlen (256 bit) // Step H2b - v = crypto.HmacSHA256(v, k) + v = crypto.createHmac('sha256', k).update(v).digest() var T = BigInteger.fromBuffer(v) - // Step H3, repeat until T is within the interval [1, n - 1] - while ((T.signum() <= 0) || (T.compareTo(curve.n) >= 0)) { - k = crypto.HmacSHA256(Buffer.concat([v, new Buffer([0])]), k) - v = crypto.HmacSHA256(v, k) + // Step H3, repeat until T is within the interval [1, n - 1] and is suitable for ECDSA + while ((T.signum() <= 0) || (T.compareTo(curve.n) >= 0) || !checkSig(T)) { + k = crypto.createHmac('sha256', k) + .update(v) + .update(ZERO) + .digest() + + v = crypto.createHmac('sha256', k).update(v).digest() + // Step H1/H2a, again, ignored as tlen === qlen (256 bit) + // Step H2b again + v = crypto.createHmac('sha256', k).update(v).digest() T = BigInteger.fromBuffer(v) } @@ -51,18 +102,28 @@ function deterministicGenerateK(curve, hash, d) { } function sign(curve, hash, d) { - var k = deterministicGenerateK(curve, hash, d) + var r, s + var e = BigInteger.fromBuffer(hash) var n = curve.n var G = curve.G - var Q = G.multiply(k) - var e = BigInteger.fromBuffer(hash) - var r = Q.affineX.mod(n) - assert.notEqual(r.signum(), 0, 'Invalid R value') + deterministicGenerateK(curve, hash, d, function(k) { + var Q = G.multiply(k) - var s = k.modInverse(n).multiply(e.add(d.multiply(r))).mod(n) - assert.notEqual(s.signum(), 0, 'Invalid S value') + if (curve.isInfinity(Q)) + return false + + r = Q.affineX.mod(n) + if (r.signum() === 0) + return false + + s = k.modInverse(n).multiply(e.add(d.multiply(r))).mod(n) + if (s.signum() === 0) + return false + + return true + }) var N_OVER_TWO = n.shiftRight(1) @@ -74,12 +135,6 @@ function sign(curve, hash, d) { return new ECSignature(r, s) } -function verify(curve, hash, signature, Q) { - var e = BigInteger.fromBuffer(hash) - - return verifyRaw(curve, e, signature, Q) -} - function verifyRaw(curve, e, signature, Q) { var n = curve.n var G = curve.G @@ -87,20 +142,37 @@ function verifyRaw(curve, e, signature, Q) { var r = signature.r var s = signature.s - if (r.signum() === 0 || r.compareTo(n) >= 0) return false - if (s.signum() === 0 || s.compareTo(n) >= 0) return false + // 1.4.1 Enforce r and s are both integers in the interval [1, n − 1] + if (r.signum() <= 0 || r.compareTo(n) >= 0) return false + if (s.signum() <= 0 || s.compareTo(n) >= 0) return false + // c = s^-1 mod n var c = s.modInverse(n) + // 1.4.4 Compute u1 = es^−1 mod n + // u2 = rs^−1 mod n var u1 = e.multiply(c).mod(n) var u2 = r.multiply(c).mod(n) - var point = G.multiplyTwo(u1, Q, u2) - var v = point.affineX.mod(n) + // 1.4.5 Compute R = (xR, yR) = u1G + u2Q + var R = G.multiplyTwo(u1, Q, u2) + var v = R.affineX.mod(n) + // 1.4.5 (cont.) Enforce R is not at infinity + if (curve.isInfinity(R)) return false + + // 1.4.8 If v = r, output "valid", and if v != r, output "invalid" return v.equals(r) } +function verify(curve, hash, signature, Q) { + // 1.4.2 H = Hash(M), already done by the user + // 1.4.3 e = H + var e = BigInteger.fromBuffer(hash) + + return verifyRaw(curve, e, signature, Q) +} + /** * Recover a public key from a signature. * @@ -112,9 +184,15 @@ function verifyRaw(curve, e, signature, Q) { function recoverPubKey(curve, e, signature, i) { assert.strictEqual(i & 3, i, 'Recovery param is more than two bits') + var n = curve.n + var G = curve.G + var r = signature.r var s = signature.s + assert(r.signum() > 0 && r.compareTo(n) < 0, 'Invalid r value') + assert(s.signum() > 0 && s.compareTo(n) < 0, 'Invalid s value') + // A set LSB signifies that the y-coordinate is odd var isYOdd = i & 1 @@ -122,9 +200,6 @@ function recoverPubKey(curve, e, signature, i) { // first or second candidate key. var isSecondKey = i >> 1 - var n = curve.n - var G = curve.G - // 1.1 Let x = r + jn var x = isSecondKey ? r.add(n) : r var R = curve.pointFromX(isYOdd, x) diff --git a/src/eckey.js b/src/eckey.js index 87a93677a..74ba93c76 100644 --- a/src/eckey.js +++ b/src/eckey.js @@ -1,25 +1,29 @@ var assert = require('assert') var base58check = require('bs58check') +var crypto = require('crypto') var ecdsa = require('./ecdsa') +var typeForce = require('typeforce') var networks = require('./networks') -var secureRandom = require('secure-random') var BigInteger = require('bigi') var ECPubKey = require('./ecpubkey') var ecurve = require('ecurve') -var curve = ecurve.getCurveByName('secp256k1') +var secp256k1 = ecurve.getCurveByName('secp256k1') function ECKey(d, compressed) { assert(d.signum() > 0, 'Private key must be greater than 0') - assert(d.compareTo(curve.n) < 0, 'Private key must be less than the curve order') + assert(d.compareTo(ECKey.curve.n) < 0, 'Private key must be less than the curve order') - var Q = curve.G.multiply(d) + var Q = ECKey.curve.G.multiply(d) this.d = d this.pub = new ECPubKey(Q, compressed) } +// Constants +ECKey.curve = secp256k1 + // Static constructors ECKey.fromWIF = function(string) { var payload = base58check.decode(string) @@ -43,13 +47,14 @@ ECKey.fromWIF = function(string) { } ECKey.makeRandom = function(compressed, rng) { - rng = rng || secureRandom.randomBuffer + rng = rng || crypto.randomBytes var buffer = rng(32) - assert(Buffer.isBuffer(buffer), 'Expected Buffer, got ' + buffer) + typeForce('Buffer', buffer) + assert.equal(buffer.length, 32, 'Expected 256-bit Buffer from RNG') var d = BigInteger.fromBuffer(buffer) - d = d.mod(curve.n) + d = d.mod(ECKey.curve.n) return new ECKey(d, compressed) } @@ -73,7 +78,7 @@ ECKey.prototype.toWIF = function(network) { // Operations ECKey.prototype.sign = function(hash) { - return ecdsa.sign(curve, hash, this.d) + return ecdsa.sign(ECKey.curve, hash, this.d) } module.exports = ECKey diff --git a/src/ecpubkey.js b/src/ecpubkey.js index 37167297d..860e415a6 100644 --- a/src/ecpubkey.js +++ b/src/ecpubkey.js @@ -1,26 +1,29 @@ -var assert = require('assert') var crypto = require('./crypto') var ecdsa = require('./ecdsa') +var typeForce = require('typeforce') var networks = require('./networks') var Address = require('./address') var ecurve = require('ecurve') -var curve = ecurve.getCurveByName('secp256k1') +var secp256k1 = ecurve.getCurveByName('secp256k1') function ECPubKey(Q, compressed) { - assert(Q instanceof ecurve.Point, 'Expected Point, got ' + Q) + if (compressed === undefined) compressed = true - if (compressed == undefined) compressed = true - assert.strictEqual(typeof compressed, 'boolean', 'Expected boolean, got ' + compressed) + typeForce('Point', Q) + typeForce('Boolean', compressed) this.compressed = compressed this.Q = Q } +// Constants +ECPubKey.curve = secp256k1 + // Static constructors ECPubKey.fromBuffer = function(buffer) { - var Q = ecurve.Point.decodeFrom(curve, buffer) + var Q = ecurve.Point.decodeFrom(ECPubKey.curve, buffer) return new ECPubKey(Q, Q.compressed) } @@ -36,7 +39,7 @@ ECPubKey.prototype.getAddress = function(network) { } ECPubKey.prototype.verify = function(hash, signature) { - return ecdsa.verify(curve, hash, signature, this.Q) + return ecdsa.verify(ECPubKey.curve, hash, signature, this.Q) } // Export functions diff --git a/src/ecsignature.js b/src/ecsignature.js index bcd59d53f..11f2287c3 100644 --- a/src/ecsignature.js +++ b/src/ecsignature.js @@ -1,14 +1,16 @@ var assert = require('assert') +var typeForce = require('typeforce') + var BigInteger = require('bigi') function ECSignature(r, s) { - assert(r instanceof BigInteger, 'Expected BigInteger, got ' + r) - assert(s instanceof BigInteger, 'Expected BigInteger, got ' + s) + typeForce('BigInteger', r) + typeForce('BigInteger', s) + this.r = r this.s = s } -// Import operations ECSignature.parseCompact = function(buffer) { assert.equal(buffer.length, 65, 'Invalid signature length') var i = buffer.readUInt8(0) - 27 @@ -66,12 +68,12 @@ ECSignature.fromDER = function(buffer) { return new ECSignature(r, s) } -// FIXME: 0x00, 0x04, 0x80 are SIGHASH_* boundary constants, importing Transaction causes a circular dependency +// BIP62: 1 byte hashType flag (only 0x01, 0x02, 0x03, 0x81, 0x82 and 0x83 are allowed) ECSignature.parseScriptSignature = function(buffer) { var hashType = buffer.readUInt8(buffer.length - 1) var hashTypeMod = hashType & ~0x80 - assert(hashTypeMod > 0x00 && hashTypeMod < 0x04, 'Invalid hashType') + assert(hashTypeMod > 0x00 && hashTypeMod < 0x04, 'Invalid hashType ' + hashType) return { signature: ECSignature.fromDER(buffer.slice(0, -1)), @@ -79,7 +81,6 @@ ECSignature.parseScriptSignature = function(buffer) { } } -// Export operations ECSignature.prototype.toCompact = function(i, compressed) { if (compressed) i += 4 i += 27 @@ -114,6 +115,9 @@ ECSignature.prototype.toDER = function() { } ECSignature.prototype.toScriptSignature = function(hashType) { + var hashTypeMod = hashType & ~0x80 + assert(hashTypeMod > 0x00 && hashTypeMod < 0x04, 'Invalid hashType ' + hashType) + var hashTypeBuffer = new Buffer(1) hashTypeBuffer.writeUInt8(hashType, 0) diff --git a/src/hdnode.js b/src/hdnode.js index 627b67dcf..e81dc4ecb 100644 --- a/src/hdnode.js +++ b/src/hdnode.js @@ -1,6 +1,8 @@ var assert = require('assert') var base58check = require('bs58check') -var crypto = require('./crypto') +var bcrypto = require('./crypto') +var crypto = require('crypto') +var typeForce = require('typeforce') var networks = require('./networks') var BigInteger = require('bigi') @@ -10,32 +12,32 @@ var ECPubKey = require('./ecpubkey') var ecurve = require('ecurve') var curve = ecurve.getCurveByName('secp256k1') -function findBIP32ParamsByVersion(version) { +function findBIP32NetworkByVersion(version) { for (var name in networks) { var network = networks[name] - for (var type in network.bip32) { - if (version != network.bip32[type]) continue + if (version === network.bip32.private || + version === network.bip32.public) { - return { - isPrivate: (type === 'private'), - network: network - } + return network } } - assert(false, 'Could not find version ' + version.toString(16)) + assert(false, 'Could not find network for ' + version.toString(16)) } function HDNode(K, chainCode, network) { network = network || networks.bitcoin - assert(Buffer.isBuffer(chainCode), 'Expected Buffer, got ' + chainCode) + typeForce('Buffer', chainCode) + + assert.equal(chainCode.length, 32, 'Expected chainCode length of 32, got ' + chainCode.length) assert(network.bip32, 'Unknown BIP32 constants for network') this.chainCode = chainCode this.depth = 0 this.index = 0 + this.parentFingerprint = 0x00000000 this.network = network if (K instanceof BigInteger) { @@ -51,11 +53,12 @@ HDNode.HIGHEST_BIT = 0x80000000 HDNode.LENGTH = 78 HDNode.fromSeedBuffer = function(seed, network) { - assert(Buffer.isBuffer(seed), 'Expected Buffer, got' + seed) + typeForce('Buffer', seed) + assert(seed.length >= 16, 'Seed should be at least 128 bits') assert(seed.length <= 64, 'Seed should be at most 512 bits') - var I = crypto.HmacSHA512(seed, HDNode.MASTER_SECRET) + var I = crypto.createHmac('sha512', HDNode.MASTER_SECRET).update(seed).digest() var IL = I.slice(0, 32) var IR = I.slice(32) @@ -70,16 +73,28 @@ HDNode.fromSeedHex = function(hex, network) { return HDNode.fromSeedBuffer(new Buffer(hex, 'hex'), network) } -HDNode.fromBase58 = function(string) { - return HDNode.fromBuffer(base58check.decode(string)) +HDNode.fromBase58 = function(string, network) { + return HDNode.fromBuffer(base58check.decode(string), network, true) } -HDNode.fromBuffer = function(buffer) { +// FIXME: remove in 2.x.y +HDNode.fromBuffer = function(buffer, network, __ignoreDeprecation) { + if (!__ignoreDeprecation) { + console.warn('HDNode.fromBuffer() is deprecated for removal in 2.x.y, use fromBase58 instead') + } + assert.strictEqual(buffer.length, HDNode.LENGTH, 'Invalid buffer length') // 4 byte: version bytes var version = buffer.readUInt32BE(0) - var params = findBIP32ParamsByVersion(version) + + if (network) { + assert(version === network.bip32.private || version === network.bip32.public, 'Network doesn\'t match') + + // auto-detect + } else { + network = findBIP32NetworkByVersion(version) + } // 1 byte: depth: 0x00 for master nodes, 0x01 for level-1 descendants, ... var depth = buffer.readUInt8(4) @@ -97,18 +112,18 @@ HDNode.fromBuffer = function(buffer) { // 32 bytes: the chain code var chainCode = buffer.slice(13, 45) - var hd + var data, hd // 33 bytes: private key data (0x00 + k) - if (params.isPrivate) { + if (version === network.bip32.private) { assert.strictEqual(buffer.readUInt8(45), 0x00, 'Invalid private key') - var data = buffer.slice(46, 78) + data = buffer.slice(46, 78) var d = BigInteger.fromBuffer(data) - hd = new HDNode(d, chainCode, params.network) + hd = new HDNode(d, chainCode, network) // 33 bytes: public key data (0x02 + X or 0x03 + X) } else { - var data = buffer.slice(45, 78) + data = buffer.slice(45, 78) var Q = ecurve.Point.decodeFrom(curve, data) assert.equal(Q.compressed, true, 'Invalid public key') @@ -116,7 +131,7 @@ HDNode.fromBuffer = function(buffer) { // If not, the extended public key is invalid. curve.validate(Q) - hd = new HDNode(Q, chainCode, params.network) + hd = new HDNode(Q, chainCode, network) } hd.depth = depth @@ -126,12 +141,13 @@ HDNode.fromBuffer = function(buffer) { return hd } -HDNode.fromHex = function(hex) { - return HDNode.fromBuffer(new Buffer(hex, 'hex')) +// FIXME: remove in 2.x.y +HDNode.fromHex = function(hex, network) { + return HDNode.fromBuffer(new Buffer(hex, 'hex'), network) } HDNode.prototype.getIdentifier = function() { - return crypto.hash160(this.pubKey.toBuffer()) + return bcrypto.hash160(this.pubKey.toBuffer()) } HDNode.prototype.getFingerprint = function() { @@ -142,12 +158,32 @@ HDNode.prototype.getAddress = function() { return this.pubKey.getAddress(this.network) } +HDNode.prototype.neutered = function() { + var neutered = new HDNode(this.pubKey.Q, this.chainCode, this.network) + neutered.depth = this.depth + neutered.index = this.index + neutered.parentFingerprint = this.parentFingerprint + + return neutered +} + HDNode.prototype.toBase58 = function(isPrivate) { - return base58check.encode(this.toBuffer(isPrivate)) + return base58check.encode(this.toBuffer(isPrivate, true)) } -HDNode.prototype.toBuffer = function(isPrivate) { - if (isPrivate == undefined) isPrivate = !!this.privKey +// FIXME: remove in 2.x.y +HDNode.prototype.toBuffer = function(isPrivate, __ignoreDeprecation) { + if (isPrivate === undefined) { + isPrivate = !!this.privKey + + // FIXME: remove in 2.x.y + } else { + console.warn('isPrivate flag is deprecated, please use the .neutered() method instead') + } + + if (!__ignoreDeprecation) { + console.warn('HDNode.toBuffer() is deprecated for removal in 2.x.y, use toBase58 instead') + } // Version var version = isPrivate ? this.network.bip32.private : this.network.bip32.public @@ -161,8 +197,7 @@ HDNode.prototype.toBuffer = function(isPrivate) { buffer.writeUInt8(this.depth, 4) // 4 bytes: the fingerprint of the parent's key (0x00000000 if master key) - var fingerprint = (this.depth === 0) ? 0x00000000 : this.parentFingerprint - buffer.writeUInt32BE(fingerprint, 5) + buffer.writeUInt32BE(this.parentFingerprint, 5) // 4 bytes: child number. This is the number i in xi = xpar/i, with xi the key being serialized. // This is encoded in Big endian. (0x00000000 if master key) @@ -173,6 +208,7 @@ HDNode.prototype.toBuffer = function(isPrivate) { // 33 bytes: the public key or private key data if (isPrivate) { + // FIXME: remove in 2.x.y assert(this.privKey, 'Missing private key') // 0x00 + k for private keys @@ -187,6 +223,7 @@ HDNode.prototype.toBuffer = function(isPrivate) { return buffer } +// FIXME: remove in 2.x.y HDNode.prototype.toHex = function(isPrivate) { return this.toBuffer(isPrivate).toString('hex') } @@ -219,7 +256,7 @@ HDNode.prototype.derive = function(index) { ]) } - var I = crypto.HmacSHA512(data, this.chainCode) + var I = crypto.createHmac('sha512', this.chainCode).update(data).digest() var IL = I.slice(0, 32) var IR = I.slice(32) diff --git a/src/index.js b/src/index.js index 0b2897efd..5dc683f48 100644 --- a/src/index.js +++ b/src/index.js @@ -1,7 +1,8 @@ module.exports = { Address: require('./address'), + base58check: require('./base58check'), + Block: require('./block'), bufferutils: require('./bufferutils'), - convert: require('./convert'), crypto: require('./crypto'), ecdsa: require('./ecdsa'), ECKey: require('./eckey'), @@ -13,6 +14,7 @@ module.exports = { Script: require('./script'), scripts: require('./scripts'), Transaction: require('./transaction'), + TransactionBuilder: require('./transaction_builder'), networks: require('./networks'), Wallet: require('./wallet') } diff --git a/src/message.js b/src/message.js index 943d82ba9..6305a691c 100644 --- a/src/message.js +++ b/src/message.js @@ -1,12 +1,9 @@ -/// Implements Bitcoin's feature for signing arbitrary messages. -var Address = require('./address') -var BigInteger = require('bigi') var bufferutils = require('./bufferutils') var crypto = require('./crypto') var ecdsa = require('./ecdsa') var networks = require('./networks') -var Address = require('./address') +var BigInteger = require('bigi') var ECPubKey = require('./ecpubkey') var ECSignature = require('./ecsignature') @@ -16,8 +13,7 @@ var ecparams = ecurve.getCurveByName('secp256k1') function magicHash(message, network) { var magicPrefix = new Buffer(network.magicPrefix) var messageBuffer = new Buffer(message) - var lengthBuffer = new Buffer(bufferutils.varIntSize(messageBuffer.length)) - bufferutils.writeVarInt(lengthBuffer, messageBuffer.length, 0) + var lengthBuffer = bufferutils.varIntBuffer(messageBuffer.length) var buffer = Buffer.concat([magicPrefix, lengthBuffer, messageBuffer]) return crypto.hash256(buffer) @@ -35,19 +31,20 @@ function sign(privKey, message, network) { } // TODO: network could be implied from address -function verify(address, signatureBuffer, message, network) { - if (address instanceof Address) { - address = address.toString() +function verify(address, signature, message, network) { + if (!Buffer.isBuffer(signature)) { + signature = new Buffer(signature, 'base64') } + network = network || networks.bitcoin var hash = magicHash(message, network) - var parsed = ECSignature.parseCompact(signatureBuffer) + var parsed = ECSignature.parseCompact(signature) var e = BigInteger.fromBuffer(hash) var Q = ecdsa.recoverPubKey(ecparams, e, parsed.signature, parsed.i) var pubKey = new ECPubKey(Q, parsed.compressed) - return pubKey.getAddress(network).toString() === address + return pubKey.getAddress(network).toString() === address.toString() } module.exports = { diff --git a/src/networks.js b/src/networks.js index 60a321edb..db5aab6ee 100644 --- a/src/networks.js +++ b/src/networks.js @@ -15,19 +15,18 @@ var networks = { feePerKb: 10000, // https://github.com/bitcoin/bitcoin/blob/v0.9.2/src/main.cpp#L53 estimateFee: estimateFee('bitcoin') }, - dogecoin: { - magicPrefix: '\x19Dogecoin Signed Message:\n', + testnet: { + magicPrefix: '\x18Bitcoin Signed Message:\n', bip32: { - public: 0x02facafd, - private: 0x02fac398 + public: 0x043587cf, + private: 0x04358394 }, - pubKeyHash: 0x1e, - scriptHash: 0x16, - wif: 0x9e, - dustThreshold: 0, // https://github.com/dogecoin/dogecoin/blob/v1.7.1/src/core.h#L155-L160 - dustSoftThreshold: 100000000, // https://github.com/dogecoin/dogecoin/blob/v1.7.1/src/main.h#L62 - feePerKb: 100000000, // https://github.com/dogecoin/dogecoin/blob/v1.7.1/src/main.cpp#L58 - estimateFee: estimateFee('dogecoin') + pubKeyHash: 0x6f, + scriptHash: 0xc4, + wif: 0xef, + dustThreshold: 546, + feePerKb: 10000, + estimateFee: estimateFee('testnet') }, litecoin: { magicPrefix: '\x19Litecoin Signed Message:\n', @@ -43,18 +42,88 @@ var networks = { feePerKb: 100000, // https://github.com/litecoin-project/litecoin/blob/v0.8.7.2/src/main.cpp#L56 estimateFee: estimateFee('litecoin') }, - testnet: { - magicPrefix: '\x18Bitcoin Signed Message:\n', + dogecoin: { + magicPrefix: '\x19Dogecoin Signed Message:\n', + bip32: { + public: 0x02facafd, + private: 0x02fac398 + }, + pubKeyHash: 0x1e, + scriptHash: 0x16, + wif: 0x9e, + dustThreshold: 0, // https://github.com/dogecoin/dogecoin/blob/v1.7.1/src/core.h#L155-L160 + dustSoftThreshold: 100000000, // https://github.com/dogecoin/dogecoin/blob/v1.7.1/src/main.h#L62 + feePerKb: 100000000, // https://github.com/dogecoin/dogecoin/blob/v1.7.1/src/main.cpp#L58 + estimateFee: estimateFee('dogecoin') + }, + viacoin: { + magicPrefix: '\x18Viacoin Signed Message:\n', + bip32: { + public: 0x0488b21e, + private: 0x0488ade4 + }, + pubKeyHash: 0x47, + scriptHash: 0x21, + wif: 0xc7, + dustThreshold: 560, + dustSoftThreshold: 100000, + feePerKb: 100000, // + estimateFee: estimateFee('viacoin') + }, + viacointestnet: { + magicPrefix: '\x18Viacoin Signed Message:\n', bip32: { public: 0x043587cf, private: 0x04358394 }, - pubKeyHash: 0x6f, + pubKeyHash: 0x7f, scriptHash: 0xc4, - wif: 0xef, - dustThreshold: 546, + wif: 0xff, + dustThreshold: 560, + dustSoftThreshold: 100000, + feePerKb: 100000, + estimateFee: estimateFee('viacointestnet') + }, + gamerscoin: { + magicPrefix: '\x19Gamerscoin Signed Message:\n', + bip32: { + public: 0x019da462, + private: 0x019d9cfe + }, + pubKeyHash: 0x26, + scriptHash: 0x05, + wif: 0xA6, + dustThreshold: 0, // https://github.com/gamers-coin/gamers-coinv3/blob/master/src/main.cpp#L358-L363 + dustSoftThreshold: 100000, // https://github.com/gamers-coin/gamers-coinv3/blob/master/src/main.cpp#L51 + feePerKb: 100000, // https://github.com/gamers-coin/gamers-coinv3/blob/master/src/main.cpp#L54 + estimateFee: estimateFee('gamerscoin') + }, + jumbucks: { + magicPrefix: '\x19Jumbucks Signed Message:\n', + bip32: { + public: 0x037a689a, + private: 0x037a6460 + }, + pubKeyHash: 0x2b, + scriptHash: 0x05, + wif: 0xab, + dustThreshold: 0, + dustSoftThreshold: 10000, feePerKb: 10000, - estimateFee: estimateFee('testnet') + estimateFee: estimateFee('jumbucks') + }, + zetacoin: { + magicPrefix: '\x18Zetacoin Signed Message:\n', + bip32: { + public: 0x0488b21e, + private: 0x0488ade4 + }, + pubKeyHash: 0x50, + scriptHash: 0x09, + wif: 0xe0, + dustThreshold: 546, // https://github.com/zetacoin/zetacoin/blob/master/src/core.h#L159 + feePerKb: 10000, // https://github.com/zetacoin/zetacoin/blob/master/src/main.cpp#L54 + estimateFee: estimateFee('zetacoin') } } diff --git a/src/script.js b/src/script.js index 15565982b..812970e1a 100644 --- a/src/script.js +++ b/src/script.js @@ -1,17 +1,17 @@ var assert = require('assert') var bufferutils = require('./bufferutils') var crypto = require('./crypto') +var typeForce = require('typeforce') var opcodes = require('./opcodes') function Script(buffer, chunks) { - assert(Buffer.isBuffer(buffer), 'Expected Buffer, got ' + buffer) - assert(Array.isArray(chunks), 'Expected Array, got ' + chunks) + typeForce('Buffer', buffer) + typeForce('Array', chunks) this.buffer = buffer this.chunks = chunks } -// Import operations Script.fromASM = function(asm) { var strChunks = asm.split(' ') @@ -29,7 +29,6 @@ Script.fromASM = function(asm) { Script.fromBuffer = function(buffer) { var chunks = [] - var i = 0 while (i < buffer.length) { @@ -55,7 +54,7 @@ Script.fromBuffer = function(buffer) { } Script.fromChunks = function(chunks) { - assert(Array.isArray(chunks), 'Expected Array, got ' + chunks) + typeForce('Array', chunks) var bufferSize = chunks.reduce(function(accum, chunk) { if (Buffer.isBuffer(chunk)) { @@ -89,10 +88,8 @@ Script.fromHex = function(hex) { return Script.fromBuffer(new Buffer(hex, 'hex')) } -// Constants Script.EMPTY = Script.fromChunks([]) -// Operations Script.prototype.getHash = function() { return crypto.hash160(this.buffer) } @@ -104,7 +101,6 @@ Script.prototype.without = function(needle) { })) } -// Export operations var reverseOps = [] for (var op in opcodes) { var code = opcodes[op] diff --git a/src/scripts.js b/src/scripts.js index 661285ab1..4ad20a9fd 100644 --- a/src/scripts.js +++ b/src/scripts.js @@ -1,61 +1,17 @@ var assert = require('assert') -var opcodes = require('./opcodes') - -// FIXME: use ECPubKey, currently the circular dependency breaks everything. -// -// Solutions: -// * Remove ECPubKey.getAddress -// - Minimal change, but likely unpopular -// * Move all script related functionality out of Address -// - Means a lot of changes to Transaction/Wallet -// * Ignore it (existing solution) -// * Some form of hackery with commonjs -// +var ops = require('./opcodes') +var typeForce = require('typeforce') + var ecurve = require('ecurve') var curve = ecurve.getCurveByName('secp256k1') var ECSignature = require('./ecsignature') var Script = require('./script') -function classifyOutput(script) { - assert(script instanceof Script, 'Expected Script, got ', script) - - if (isPubKeyHashOutput.call(script)) { - return 'pubkeyhash' - } else if (isScriptHashOutput.call(script)) { - return 'scripthash' - } else if (isMultisigOutput.call(script)) { - return 'multisig' - } else if (isPubKeyOutput.call(script)) { - return 'pubkey' - } else if (isNulldataOutput.call(script)) { - return 'nulldata' - } else { - return 'nonstandard' - } -} - -function classifyInput(script) { - assert(script instanceof Script, 'Expected Script, got ', script) - - if (isPubKeyHashInput.call(script)) { - return 'pubkeyhash' - } else if (isScriptHashInput.call(script)) { - return 'scripthash' - } else if (isMultisigInput.call(script)) { - return 'multisig' - } else if (isPubKeyInput.call(script)) { - return 'pubkey' - } else { - return 'nonstandard' - } -} - function isCanonicalPubKey(buffer) { if (!Buffer.isBuffer(buffer)) return false try { - // FIXME: boo ecurve.Point.decodeFrom(curve, buffer) } catch (e) { if (!(e.message.match(/Invalid sequence (length|tag)/))) throw e @@ -80,84 +36,134 @@ function isCanonicalSignature(buffer) { return true } -function isPubKeyHashInput() { - return this.chunks.length === 2 && - isCanonicalSignature(this.chunks[0]) && - isCanonicalPubKey(this.chunks[1]) +function isPubKeyHashInput(script) { + return script.chunks.length === 2 && + isCanonicalSignature(script.chunks[0]) && + isCanonicalPubKey(script.chunks[1]) } -function isPubKeyHashOutput() { - return this.chunks.length === 5 && - this.chunks[0] === opcodes.OP_DUP && - this.chunks[1] === opcodes.OP_HASH160 && - Buffer.isBuffer(this.chunks[2]) && - this.chunks[2].length === 20 && - this.chunks[3] === opcodes.OP_EQUALVERIFY && - this.chunks[4] === opcodes.OP_CHECKSIG +function isPubKeyHashOutput(script) { + return script.chunks.length === 5 && + script.chunks[0] === ops.OP_DUP && + script.chunks[1] === ops.OP_HASH160 && + Buffer.isBuffer(script.chunks[2]) && + script.chunks[2].length === 20 && + script.chunks[3] === ops.OP_EQUALVERIFY && + script.chunks[4] === ops.OP_CHECKSIG } -function isPubKeyInput() { - return this.chunks.length === 1 && - isCanonicalSignature(this.chunks[0]) +function isPubKeyInput(script) { + return script.chunks.length === 1 && + isCanonicalSignature(script.chunks[0]) } -function isPubKeyOutput() { - return this.chunks.length === 2 && - isCanonicalPubKey(this.chunks[0]) && - this.chunks[1] === opcodes.OP_CHECKSIG +function isPubKeyOutput(script) { + return script.chunks.length === 2 && + isCanonicalPubKey(script.chunks[0]) && + script.chunks[1] === ops.OP_CHECKSIG } -function isScriptHashInput() { - if (this.chunks.length < 2) return false - var lastChunk = this.chunks[this.chunks.length - 1] +function isScriptHashInput(script, allowIncomplete) { + if (script.chunks.length < 2) return false + var lastChunk = script.chunks[script.chunks.length - 1] if (!Buffer.isBuffer(lastChunk)) return false - var scriptSig = Script.fromChunks(this.chunks.slice(0, -1)) - var scriptPubKey = Script.fromBuffer(lastChunk) + var scriptSig = Script.fromChunks(script.chunks.slice(0, -1)) + var scriptPubKey + + try { + scriptPubKey = Script.fromBuffer(lastChunk) + } catch (e) { + return false + } - return classifyInput(scriptSig) === classifyOutput(scriptPubKey) + return classifyInput(scriptSig, allowIncomplete) === classifyOutput(scriptPubKey) } -function isScriptHashOutput() { - return this.chunks.length === 3 && - this.chunks[0] === opcodes.OP_HASH160 && - Buffer.isBuffer(this.chunks[1]) && - this.chunks[1].length === 20 && - this.chunks[2] === opcodes.OP_EQUAL +function isScriptHashOutput(script) { + return script.chunks.length === 3 && + script.chunks[0] === ops.OP_HASH160 && + Buffer.isBuffer(script.chunks[1]) && + script.chunks[1].length === 20 && + script.chunks[2] === ops.OP_EQUAL } -function isMultisigInput() { - return this.chunks[0] === opcodes.OP_0 && - this.chunks.slice(1).every(isCanonicalSignature) +// allowIncomplete is to account for combining signatures +// See https://github.com/bitcoin/bitcoin/blob/f425050546644a36b0b8e0eb2f6934a3e0f6f80f/src/script/sign.cpp#L195-L197 +function isMultisigInput(script, allowIncomplete) { + if (script.chunks.length < 2) return false + if (script.chunks[0] !== ops.OP_0) return false + + if (allowIncomplete) { + return script.chunks.slice(1).every(function(chunk) { + return chunk === ops.OP_0 || isCanonicalSignature(chunk) + }) + } + + return script.chunks.slice(1).every(isCanonicalSignature) } -function isMultisigOutput() { - if (this.chunks < 4) return false - if (this.chunks[this.chunks.length - 1] !== opcodes.OP_CHECKMULTISIG) return false +function isMultisigOutput(script) { + if (script.chunks.length < 4) return false + if (script.chunks[script.chunks.length - 1] !== ops.OP_CHECKMULTISIG) return false - var mOp = this.chunks[0] - if (mOp === opcodes.OP_0) return false - if (mOp < opcodes.OP_1) return false - if (mOp > opcodes.OP_16) return false + var mOp = script.chunks[0] + if (mOp === ops.OP_0) return false + if (mOp < ops.OP_1) return false + if (mOp > ops.OP_16) return false - var nOp = this.chunks[this.chunks.length - 2] - if (nOp === opcodes.OP_0) return false - if (nOp < opcodes.OP_1) return false - if (nOp > opcodes.OP_16) return false + var nOp = script.chunks[script.chunks.length - 2] + if (nOp === ops.OP_0) return false + if (nOp < ops.OP_1) return false + if (nOp > ops.OP_16) return false - var m = mOp - (opcodes.OP_1 - 1) - var n = nOp - (opcodes.OP_1 - 1) + var m = mOp - (ops.OP_1 - 1) + var n = nOp - (ops.OP_1 - 1) if (n < m) return false - var pubKeys = this.chunks.slice(1, -2) + var pubKeys = script.chunks.slice(1, -2) if (n < pubKeys.length) return false return pubKeys.every(isCanonicalPubKey) } -function isNulldataOutput() { - return this.chunks[0] === opcodes.OP_RETURN +function isNullDataOutput(script) { + return script.chunks[0] === ops.OP_RETURN +} + +function classifyOutput(script) { + typeForce('Script', script) + + if (isPubKeyHashOutput(script)) { + return 'pubkeyhash' + } else if (isScriptHashOutput(script)) { + return 'scripthash' + } else if (isMultisigOutput(script)) { + return 'multisig' + } else if (isPubKeyOutput(script)) { + return 'pubkey' + } else if (isNullDataOutput(script)) { + return 'nulldata' + } + + return 'nonstandard' +} + +function classifyInput(script, allowIncomplete) { + typeForce('Script', script) + + if (isPubKeyHashInput(script)) { + return 'pubkeyhash' + } else if (isMultisigInput(script, allowIncomplete)) { + return 'multisig' + } else if (isScriptHashInput(script, allowIncomplete)) { + return 'scripthash' + } else if (isPubKeyInput(script)) { + return 'pubkey' + } + + return 'nonstandard' } // Standard Script Templates @@ -165,37 +171,38 @@ function isNulldataOutput() { function pubKeyOutput(pubKey) { return Script.fromChunks([ pubKey.toBuffer(), - opcodes.OP_CHECKSIG + ops.OP_CHECKSIG ]) } // OP_DUP OP_HASH160 {pubKeyHash} OP_EQUALVERIFY OP_CHECKSIG function pubKeyHashOutput(hash) { - assert(Buffer.isBuffer(hash), 'Expected Buffer, got ' + hash) + typeForce('Buffer', hash) return Script.fromChunks([ - opcodes.OP_DUP, - opcodes.OP_HASH160, + ops.OP_DUP, + ops.OP_HASH160, hash, - opcodes.OP_EQUALVERIFY, - opcodes.OP_CHECKSIG + ops.OP_EQUALVERIFY, + ops.OP_CHECKSIG ]) } // OP_HASH160 {scriptHash} OP_EQUAL function scriptHashOutput(hash) { - assert(Buffer.isBuffer(hash), 'Expected Buffer, got ' + hash) + typeForce('Buffer', hash) return Script.fromChunks([ - opcodes.OP_HASH160, + ops.OP_HASH160, hash, - opcodes.OP_EQUAL + ops.OP_EQUAL ]) } // m [pubKeys ...] n OP_CHECKMULTISIG function multisigOutput(m, pubKeys) { - assert(Array.isArray(pubKeys), 'Expected Array, got ' + pubKeys) + typeForce(['ECPubKey'], pubKeys) + assert(pubKeys.length >= m, 'Not enough pubKeys provided') var pubKeyBuffers = pubKeys.map(function(pubKey) { @@ -204,23 +211,23 @@ function multisigOutput(m, pubKeys) { var n = pubKeys.length return Script.fromChunks([].concat( - (opcodes.OP_1 - 1) + m, + (ops.OP_1 - 1) + m, pubKeyBuffers, - (opcodes.OP_1 - 1) + n, - opcodes.OP_CHECKMULTISIG + (ops.OP_1 - 1) + n, + ops.OP_CHECKMULTISIG )) } // {signature} function pubKeyInput(signature) { - assert(Buffer.isBuffer(signature), 'Expected Buffer, got ' + signature) + typeForce('Buffer', signature) return Script.fromChunks([signature]) } // {signature} {pubKey} function pubKeyHashInput(signature, pubKey) { - assert(Buffer.isBuffer(signature), 'Expected Buffer, got ' + signature) + typeForce('Buffer', signature) return Script.fromChunks([signature, pubKey.toBuffer()]) } @@ -236,25 +243,54 @@ function scriptHashInput(scriptSig, scriptPubKey) { // OP_0 [signatures ...] function multisigInput(signatures, scriptPubKey) { if (scriptPubKey) { - assert(isMultisigOutput.call(scriptPubKey)) + assert(isMultisigOutput(scriptPubKey)) + + var mOp = scriptPubKey.chunks[0] + var nOp = scriptPubKey.chunks[scriptPubKey.chunks.length - 2] + var m = mOp - (ops.OP_1 - 1) + var n = nOp - (ops.OP_1 - 1) - var m = scriptPubKey.chunks[0] - var k = m - (opcodes.OP_1 - 1) - assert(k <= signatures.length, 'Not enough signatures provided') + var count = 0 + signatures.forEach(function(signature) { + count += (signature !== ops.OP_0) + }) + + assert(count >= m, 'Not enough signatures provided') + assert(count <= n, 'Too many signatures provided') } - return Script.fromChunks([].concat(opcodes.OP_0, signatures)) + return Script.fromChunks([].concat(ops.OP_0, signatures)) +} + +function nullDataOutput(data) { + return Script.fromChunks([ops.OP_RETURN, data]) } module.exports = { - classifyInput: classifyInput, + isCanonicalPubKey: isCanonicalPubKey, + isCanonicalSignature: isCanonicalSignature, + isPubKeyHashInput: isPubKeyHashInput, + isPubKeyHashOutput: isPubKeyHashOutput, + isPubKeyInput: isPubKeyInput, + isPubKeyOutput: isPubKeyOutput, + isScriptHashInput: isScriptHashInput, + isScriptHashOutput: isScriptHashOutput, + isMultisigInput: isMultisigInput, + isMultisigOutput: isMultisigOutput, + isNullDataOutput: isNullDataOutput, classifyOutput: classifyOutput, - multisigInput: multisigInput, - multisigOutput: multisigOutput, - pubKeyHashInput: pubKeyHashInput, + classifyInput: classifyInput, + pubKeyOutput: pubKeyOutput, pubKeyHashOutput: pubKeyHashOutput, + scriptHashOutput: scriptHashOutput, + multisigOutput: multisigOutput, pubKeyInput: pubKeyInput, - pubKeyOutput: pubKeyOutput, + pubKeyHashInput: pubKeyHashInput, scriptHashInput: scriptHashInput, - scriptHashOutput: scriptHashOutput + multisigInput: multisigInput, + dataOutput: function(data) { + console.warn('dataOutput is deprecated, use nullDataOutput by 2.0.0') + return nullDataOutput(data) + }, + nullDataOutput: nullDataOutput } diff --git a/src/transaction.js b/src/transaction.js index 80393b974..ad6179d7a 100644 --- a/src/transaction.js +++ b/src/transaction.js @@ -1,29 +1,114 @@ var assert = require('assert') var bufferutils = require('./bufferutils') var crypto = require('./crypto') +var typeForce = require('typeforce') var opcodes = require('./opcodes') var scripts = require('./scripts') var Address = require('./address') -var ECKey = require('./eckey') var ECSignature = require('./ecsignature') var Script = require('./script') +function Transaction() { + this.version = 1 + this.locktime = 0 + this.ins = [] + this.outs = [] +} + Transaction.DEFAULT_SEQUENCE = 0xffffffff Transaction.SIGHASH_ALL = 0x01 Transaction.SIGHASH_NONE = 0x02 Transaction.SIGHASH_SINGLE = 0x03 Transaction.SIGHASH_ANYONECANPAY = 0x80 -function Transaction() { - this.version = 1 - this.locktime = 0 - this.ins = [] - this.outs = [] +Transaction.fromBuffer = function(buffer, __disableAssert) { + var offset = 0 + function readSlice(n) { + offset += n + return buffer.slice(offset - n, offset) + } + + function readUInt32() { + var i = buffer.readUInt32LE(offset) + offset += 4 + return i + } + + function readUInt64() { + var i = bufferutils.readUInt64LE(buffer, offset) + offset += 8 + return i + } + + function readVarInt() { + var vi = bufferutils.readVarInt(buffer, offset) + offset += vi.size + return vi.number + } + + function readScript() { + return Script.fromBuffer(readSlice(readVarInt())) + } + + function readGenerationScript() { + return new Script(readSlice(readVarInt()), []) + } + + var tx = new Transaction() + tx.version = readUInt32() + + var vinLen = readVarInt() + for (var i = 0; i < vinLen; ++i) { + var hash = readSlice(32) + + if (Transaction.isCoinbaseHash(hash)) { + tx.ins.push({ + hash: hash, + index: readUInt32(), + script: readGenerationScript(), + sequence: readUInt32() + }) + + } else { + tx.ins.push({ + hash: hash, + index: readUInt32(), + script: readScript(), + sequence: readUInt32() + }) + } + } + + var voutLen = readVarInt() + for (i = 0; i < voutLen; ++i) { + tx.outs.push({ + value: readUInt64(), + script: readScript() + }) + } + + tx.locktime = readUInt32() + + if (!__disableAssert) { + assert.equal(offset, buffer.length, 'Transaction has unexpected data') + } + + return tx +} + +Transaction.fromHex = function(hex) { + return Transaction.fromBuffer(new Buffer(hex, 'hex')) +} + +Transaction.isCoinbaseHash = function(buffer) { + return Array.prototype.every.call(buffer, function(x) { + return x === 0 + }) } /** - * Create a new txin. + * Create a new txIn. * * Can be called with any of: * @@ -32,38 +117,40 @@ function Transaction() { * * Note that this method does not sign the created input. */ -Transaction.prototype.addInput = function(tx, index, sequence) { - if (sequence == undefined) sequence = Transaction.DEFAULT_SEQUENCE - - var hash +Transaction.prototype.addInput = function(hash, index, sequence, script) { + if (sequence === undefined || sequence === null) { + sequence = Transaction.DEFAULT_SEQUENCE + } - if (typeof tx === 'string') { - hash = new Buffer(tx, 'hex') + script = script || Script.EMPTY + if (typeof hash === 'string') { // TxId hex is big-endian, we need little-endian - Array.prototype.reverse.call(hash) + hash = bufferutils.reverse(new Buffer(hash, 'hex')) - } else if (tx instanceof Transaction) { - hash = tx.getHash() + } else if (hash instanceof Transaction) { + hash = hash.getHash() - } else { - hash = tx } - assert(Buffer.isBuffer(hash), 'Expected Transaction, txId or txHash, got ' + tx) + typeForce('Buffer', hash) + typeForce('Number', index) + typeForce('Number', sequence) + typeForce('Script', script) + assert.equal(hash.length, 32, 'Expected hash length of 32, got ' + hash.length) - assert.equal(typeof index, 'number', 'Expected number index, got ' + index) + // Add the input and return the input's index return (this.ins.push({ hash: hash, index: index, - script: Script.EMPTY, + script: script, sequence: sequence }) - 1) } /** - * Create a new txout. + * Create a new txOut. * * Can be called with: * @@ -79,79 +166,41 @@ Transaction.prototype.addOutput = function(scriptPubKey, value) { // Attempt to get a valid script if it's an Address object if (scriptPubKey instanceof Address) { - var address = scriptPubKey - - scriptPubKey = address.toOutputScript() + scriptPubKey = scriptPubKey.toOutputScript() } - assert(scriptPubKey instanceof Script, 'Expected Address or Script, got ' + scriptPubKey) + typeForce('Script', scriptPubKey) + typeForce('Number', value) + // Add the output and return the output's index return (this.outs.push({ script: scriptPubKey, value: value }) - 1) } -Transaction.prototype.toBuffer = function () { - var txInSize = this.ins.reduce(function(a, x) { - return a + (40 + bufferutils.varIntSize(x.script.buffer.length) + x.script.buffer.length) - }, 0) - - var txOutSize = this.outs.reduce(function(a, x) { - return a + (8 + bufferutils.varIntSize(x.script.buffer.length) + x.script.buffer.length) - }, 0) - - var buffer = new Buffer( - 8 + - bufferutils.varIntSize(this.ins.length) + - bufferutils.varIntSize(this.outs.length) + - txInSize + - txOutSize - ) - - var offset = 0 - function writeSlice(slice) { - slice.copy(buffer, offset) - offset += slice.length - } - function writeUInt32(i) { - buffer.writeUInt32LE(i, offset) - offset += 4 - } - function writeUInt64(i) { - bufferutils.writeUInt64LE(buffer, i, offset) - offset += 8 - } - function writeVarInt(i) { - var n = bufferutils.writeVarInt(buffer, i, offset) - offset += n - } - - writeUInt32(this.version) - writeVarInt(this.ins.length) +Transaction.prototype.clone = function () { + var newTx = new Transaction() + newTx.version = this.version + newTx.locktime = this.locktime - this.ins.forEach(function(txin) { - writeSlice(txin.hash) - writeUInt32(txin.index) - writeVarInt(txin.script.buffer.length) - writeSlice(txin.script.buffer) - writeUInt32(txin.sequence) + newTx.ins = this.ins.map(function(txIn) { + return { + hash: txIn.hash, + index: txIn.index, + script: txIn.script, + sequence: txIn.sequence + } }) - writeVarInt(this.outs.length) - this.outs.forEach(function(txout) { - writeUInt64(txout.value) - writeVarInt(txout.script.buffer.length) - writeSlice(txout.script.buffer) + newTx.outs = this.outs.map(function(txOut) { + return { + script: txOut.script, + value: txOut.value + } }) - writeUInt32(this.locktime) - - return buffer -} - -Transaction.prototype.toHex = function() { - return this.toBuffer().toString('hex') + return newTx } /** @@ -162,17 +211,30 @@ Transaction.prototype.toHex = function() { * hashType, serializes and finally hashes the result. This hash can then be * used to sign the transaction input in question. */ -Transaction.prototype.hashForSignature = function(prevOutScript, inIndex, hashType) { +Transaction.prototype.hashForSignature = function(inIndex, prevOutScript, hashType) { + // FIXME: remove in 2.x.y + if (arguments[0] instanceof Script) { + console.warn('hashForSignature(prevOutScript, inIndex, ...) has been deprecated. Use hashForSignature(inIndex, prevOutScript, ...)') + + // swap the arguments (must be stored in tmp, arguments is special) + var tmp = arguments[0] + inIndex = arguments[1] + prevOutScript = tmp + } + + typeForce('Number', inIndex) + typeForce('Script', prevOutScript) + typeForce('Number', hashType) + assert(inIndex >= 0, 'Invalid vin index') assert(inIndex < this.ins.length, 'Invalid vin index') - assert(prevOutScript instanceof Script, 'Invalid Script object') var txTmp = this.clone() var hashScript = prevOutScript.without(opcodes.OP_CODESEPARATOR) // Blank out other inputs' signatures - txTmp.ins.forEach(function(txin) { - txin.script = Script.EMPTY + txTmp.ins.forEach(function(txIn) { + txIn.script = Script.EMPTY }) txTmp.ins[inIndex].script = hashScript @@ -201,130 +263,109 @@ Transaction.prototype.getHash = function () { } Transaction.prototype.getId = function () { - var buffer = this.getHash() - - // Big-endian is used for TxHash - Array.prototype.reverse.call(buffer) - - return buffer.toString('hex') + // TxHash is little-endian, we need big-endian + return bufferutils.reverse(this.getHash()).toString('hex') } -Transaction.prototype.clone = function () { - var newTx = new Transaction() - newTx.version = this.version - newTx.locktime = this.locktime - - newTx.ins = this.ins.map(function(txin) { - return { - hash: txin.hash, - index: txin.index, - script: txin.script, - sequence: txin.sequence - } - }) +Transaction.prototype.toBuffer = function () { + function scriptSize(script) { + var length = script.buffer.length - newTx.outs = this.outs.map(function(txout) { - return { - script: txout.script, - value: txout.value - } - }) + return bufferutils.varIntSize(length) + length + } - return newTx -} + var buffer = new Buffer( + 8 + + bufferutils.varIntSize(this.ins.length) + + bufferutils.varIntSize(this.outs.length) + + this.ins.reduce(function(sum, input) { return sum + 40 + scriptSize(input.script) }, 0) + + this.outs.reduce(function(sum, output) { return sum + 8 + scriptSize(output.script) }, 0) + ) -Transaction.fromBuffer = function(buffer) { var offset = 0 - function readSlice(n) { - offset += n - return buffer.slice(offset - n, offset) + function writeSlice(slice) { + slice.copy(buffer, offset) + offset += slice.length } - function readUInt32() { - var i = buffer.readUInt32LE(offset) + + function writeUInt32(i) { + buffer.writeUInt32LE(i, offset) offset += 4 - return i } - function readUInt64() { - var i = bufferutils.readUInt64LE(buffer, offset) + + function writeUInt64(i) { + bufferutils.writeUInt64LE(buffer, i, offset) offset += 8 - return i } - function readVarInt() { - var vi = bufferutils.readVarInt(buffer, offset) - offset += vi.size - return vi.number + + function writeVarInt(i) { + var n = bufferutils.writeVarInt(buffer, i, offset) + offset += n } - var tx = new Transaction() - tx.version = readUInt32() + writeUInt32(this.version) + writeVarInt(this.ins.length) - var vinLen = readVarInt() - for (var i = 0; i < vinLen; ++i) { - var hash = readSlice(32) - var vout = readUInt32() - var scriptLen = readVarInt() - var script = readSlice(scriptLen) - var sequence = readUInt32() - - tx.ins.push({ - hash: hash, - index: vout, - script: Script.fromBuffer(script), - sequence: sequence - }) - } + this.ins.forEach(function(txIn) { + writeSlice(txIn.hash) + writeUInt32(txIn.index) + writeVarInt(txIn.script.buffer.length) + writeSlice(txIn.script.buffer) + writeUInt32(txIn.sequence) + }) - var voutLen = readVarInt() - for (i = 0; i < voutLen; ++i) { - var value = readUInt64() - var scriptLen = readVarInt() - var script = readSlice(scriptLen) + writeVarInt(this.outs.length) + this.outs.forEach(function(txOut) { + writeUInt64(txOut.value) + writeVarInt(txOut.script.buffer.length) + writeSlice(txOut.script.buffer) + }) - tx.outs.push({ - value: value, - script: Script.fromBuffer(script) - }) - } + writeUInt32(this.locktime) - tx.locktime = readUInt32() - assert.equal(offset, buffer.length, 'Transaction has unexpected data') + return buffer +} - return tx +Transaction.prototype.toHex = function() { + return this.toBuffer().toString('hex') } -Transaction.fromHex = function(hex) { - return Transaction.fromBuffer(new Buffer(hex, 'hex')) +Transaction.prototype.setInputScript = function(index, script) { + typeForce('Number', index) + typeForce('Script', script) + + this.ins[index].script = script } -/** - * Signs a pubKeyHash output at some index with the given key - */ +// FIXME: remove in 2.x.y Transaction.prototype.sign = function(index, privKey, hashType) { + console.warn("Transaction.prototype.sign is deprecated. Use TransactionBuilder instead.") + var prevOutScript = privKey.pub.getAddress().toOutputScript() var signature = this.signInput(index, prevOutScript, privKey, hashType) - // FIXME: Assumed prior TX was pay-to-pubkey-hash var scriptSig = scripts.pubKeyHashInput(signature, privKey.pub) this.setInputScript(index, scriptSig) } +// FIXME: remove in 2.x.y Transaction.prototype.signInput = function(index, prevOutScript, privKey, hashType) { + console.warn("Transaction.prototype.signInput is deprecated. Use TransactionBuilder instead.") + hashType = hashType || Transaction.SIGHASH_ALL - var hash = this.hashForSignature(prevOutScript, index, hashType) + var hash = this.hashForSignature(index, prevOutScript, hashType) var signature = privKey.sign(hash) return signature.toScriptSignature(hashType) } -Transaction.prototype.setInputScript = function(index, script) { - this.ins[index].script = script -} - -// FIXME: could be validateInput(index, prevTxOut, pub) +// FIXME: remove in 2.x.y Transaction.prototype.validateInput = function(index, prevOutScript, pubKey, buffer) { + console.warn("Transaction.prototype.validateInput is deprecated. Use TransactionBuilder instead.") + var parsed = ECSignature.parseScriptSignature(buffer) - var hash = this.hashForSignature(prevOutScript, index, parsed.hashType) + var hash = this.hashForSignature(index, prevOutScript, parsed.hashType) return pubKey.verify(hash, parsed.signature) } diff --git a/src/transaction_builder.js b/src/transaction_builder.js new file mode 100644 index 000000000..a45b06b88 --- /dev/null +++ b/src/transaction_builder.js @@ -0,0 +1,358 @@ +var assert = require('assert') +var ops = require('./opcodes') +var scripts = require('./scripts') + +var ECPubKey = require('./ecpubkey') +var ECSignature = require('./ecsignature') +var Script = require('./script') +var Transaction = require('./transaction') + +function extractInput(txIn) { + var redeemScript + var scriptSig = txIn.script + var prevOutScript + var prevOutType = scripts.classifyInput(scriptSig, true) + var scriptType + + // Re-classify if scriptHash + if (prevOutType === 'scripthash') { + redeemScript = Script.fromBuffer(scriptSig.chunks.slice(-1)[0]) + prevOutScript = scripts.scriptHashOutput(redeemScript.getHash()) + + scriptSig = Script.fromChunks(scriptSig.chunks.slice(0, -1)) + scriptType = scripts.classifyInput(scriptSig, true) + + } else { + scriptType = prevOutType + } + + // Extract hashType, pubKeys and signatures + var hashType, parsed, pubKeys, signatures + + switch (scriptType) { + case 'pubkeyhash': { + parsed = ECSignature.parseScriptSignature(scriptSig.chunks[0]) + hashType = parsed.hashType + pubKeys = [ECPubKey.fromBuffer(scriptSig.chunks[1])] + signatures = [parsed.signature] + prevOutScript = pubKeys[0].getAddress().toOutputScript() + + break + } + + case 'pubkey': { + parsed = ECSignature.parseScriptSignature(scriptSig.chunks[0]) + hashType = parsed.hashType + signatures = [parsed.signature] + + if (redeemScript) { + pubKeys = [ECPubKey.fromBuffer(redeemScript.chunks[0])] + } + + break + } + + case 'multisig': { + signatures = scriptSig.chunks.slice(1).map(function(chunk) { + if (chunk === ops.OP_0) return chunk + + var parsed = ECSignature.parseScriptSignature(chunk) + hashType = parsed.hashType + + return parsed.signature + }) + + if (redeemScript) { + pubKeys = redeemScript.chunks.slice(1, -2).map(ECPubKey.fromBuffer) + } + + break + } + } + + return { + hashType: hashType, + prevOutScript: prevOutScript, + prevOutType: prevOutType, + pubKeys: pubKeys, + redeemScript: redeemScript, + scriptType: scriptType, + signatures: signatures + } +} + +function TransactionBuilder() { + this.prevTxMap = {} + this.prevOutScripts = {} + this.prevOutTypes = {} + + this.inputs = [] + this.tx = new Transaction() +} + +TransactionBuilder.fromTransaction = function(transaction) { + var txb = new TransactionBuilder() + + // Copy other transaction fields + txb.tx.version = transaction.version + txb.tx.locktime = transaction.locktime + + // Extract/add inputs + transaction.ins.forEach(function(txIn) { + txb.addInput(txIn.hash, txIn.index, txIn.sequence) + }) + + // Extract/add outputs + transaction.outs.forEach(function(txOut) { + txb.addOutput(txOut.script, txOut.value) + }) + + // Extract/add signatures + txb.inputs = transaction.ins.map(function(txIn) { + // TODO: remove me after testcase added + assert(!Transaction.isCoinbaseHash(txIn.hash), 'coinbase inputs not supported') + + // Ignore empty scripts + if (txIn.script.buffer.length === 0) return + + return extractInput(txIn) + }) + + return txb +} + +TransactionBuilder.prototype.addInput = function(prevTx, index, sequence, prevOutScript) { + var prevOutHash + + if (typeof prevTx === 'string') { + prevOutHash = new Buffer(prevTx, 'hex') + + // TxId hex is big-endian, we want little-endian hash + Array.prototype.reverse.call(prevOutHash) + + } else if (prevTx instanceof Transaction) { + prevOutHash = prevTx.getHash() + prevOutScript = prevTx.outs[index].script + + } else { + prevOutHash = prevTx + + } + + var input = {} + if (prevOutScript) { + var prevOutType = scripts.classifyOutput(prevOutScript) + + // if we can, extract pubKey information + switch (prevOutType) { + case 'multisig': + input.pubKeys = prevOutScript.chunks.slice(1, -2).map(ECPubKey.fromBuffer) + break + + case 'pubkey': + input.pubKeys = prevOutScript.chunks.slice(0, 1).map(ECPubKey.fromBuffer) + break + } + + if (prevOutType !== 'scripthash') { + input.scriptType = prevOutType + } + + input.prevOutScript = prevOutScript + input.prevOutType = prevOutType + } + + assert(this.inputs.every(function(input2) { + if (input2.hashType === undefined) return true + + return input2.hashType & Transaction.SIGHASH_ANYONECANPAY + }), 'No, this would invalidate signatures') + + var prevOut = prevOutHash.toString('hex') + ':' + index + assert(!(prevOut in this.prevTxMap), 'Transaction is already an input') + + var vin = this.tx.addInput(prevOutHash, index, sequence) + this.inputs[vin] = input + this.prevTxMap[prevOut] = vin + + return vin +} + +TransactionBuilder.prototype.addOutput = function(scriptPubKey, value) { + assert(this.inputs.every(function(input) { + if (input.hashType === undefined) return true + + return (input.hashType & 0x1f) === Transaction.SIGHASH_SINGLE + }), 'No, this would invalidate signatures') + + return this.tx.addOutput(scriptPubKey, value) +} + +TransactionBuilder.prototype.build = function() { return this.__build(false) } +TransactionBuilder.prototype.buildIncomplete = function() { return this.__build(true) } + +var canSignTypes = { 'pubkeyhash': true, 'multisig': true, 'pubkey': true } + +TransactionBuilder.prototype.__build = function(allowIncomplete) { + if (!allowIncomplete) { + assert(this.tx.ins.length > 0, 'Transaction has no inputs') + assert(this.tx.outs.length > 0, 'Transaction has no outputs') + } + + var tx = this.tx.clone() + + // Create script signatures from signature meta-data + this.inputs.forEach(function(input, index) { + var scriptType = input.scriptType + var scriptSig + + if (!allowIncomplete) { + assert(!!scriptType, 'Transaction is not complete') + assert(scriptType in canSignTypes, scriptType + ' not supported') + assert(input.signatures, 'Transaction is missing signatures') + } + + if (input.signatures) { + switch (scriptType) { + case 'pubkeyhash': + var pkhSignature = input.signatures[0].toScriptSignature(input.hashType) + scriptSig = scripts.pubKeyHashInput(pkhSignature, input.pubKeys[0]) + break + + case 'multisig': + // Array.prototype.map is sparse-compatible + var msSignatures = input.signatures.map(function(signature) { + return signature.toScriptSignature(input.hashType) + }) + + // fill in blanks with OP_0 + for (var i = 0; i < msSignatures.length; ++i) { + if (msSignatures[i]) continue + + msSignatures[i] = ops.OP_0 + } + + var redeemScript = allowIncomplete ? undefined : input.redeemScript + scriptSig = scripts.multisigInput(msSignatures, redeemScript) + break + + case 'pubkey': + var pkSignature = input.signatures[0].toScriptSignature(input.hashType) + scriptSig = scripts.pubKeyInput(pkSignature) + break + } + } + + // did we build a scriptSig? + if (scriptSig) { + // wrap as scriptHash if necessary + if (input.prevOutType === 'scripthash') { + scriptSig = scripts.scriptHashInput(scriptSig, input.redeemScript) + } + + tx.setInputScript(index, scriptSig) + } + }) + + return tx +} + +TransactionBuilder.prototype.sign = function(index, privKey, redeemScript, hashType) { + assert(index in this.inputs, 'No input at index: ' + index) + hashType = hashType || Transaction.SIGHASH_ALL + + var input = this.inputs[index] + var canSign = input.hashType && + input.prevOutScript && + input.prevOutType && + input.pubKeys && + input.scriptType && + input.signatures + + // are we almost ready to sign? + if (canSign) { + // if redeemScript was provided, enforce consistency + if (redeemScript) { + assert.deepEqual(input.redeemScript, redeemScript, 'Inconsistent redeemScript') + } + + assert.equal(input.hashType, hashType, 'Inconsistent hashType') + + // no? prepare + } else { + if (redeemScript) { + // if we have a prevOutScript, enforce scriptHash equality to the redeemScript + if (input.prevOutScript) { + assert.equal(input.prevOutType, 'scripthash', 'PrevOutScript must be P2SH') + + var scriptHash = input.prevOutScript.chunks[1] + assert.deepEqual(scriptHash, redeemScript.getHash(), 'RedeemScript does not match ' + scriptHash.toString('hex')) + } + + var scriptType = scripts.classifyOutput(redeemScript) + assert(scriptType in canSignTypes, 'RedeemScript not supported (' + scriptType + ')') + + var pubKeys = [] + switch (scriptType) { + case 'multisig': + pubKeys = redeemScript.chunks.slice(1, -2).map(ECPubKey.fromBuffer) + break + + case 'pubkeyhash': + var pkh1 = redeemScript.chunks[2] + var pkh2 = privKey.pub.getAddress().hash + + assert.deepEqual(pkh1, pkh2, 'privateKey cannot sign for this input') + pubKeys = [privKey.pub] + break + + case 'pubkey': + pubKeys = redeemScript.chunks.slice(0, 1).map(ECPubKey.fromBuffer) + break + } + + if (!input.prevOutScript) { + input.prevOutScript = scripts.scriptHashOutput(redeemScript.getHash()) + input.prevOutType = 'scripthash' + } + + input.pubKeys = pubKeys + input.redeemScript = redeemScript + input.scriptType = scriptType + + } else { + assert.notEqual(input.prevOutType, 'scripthash', 'PrevOutScript is P2SH, missing redeemScript') + + // can we sign this? + if (input.scriptType) { + assert(input.pubKeys, input.scriptType + ' not supported') + + // we know nothin' Jon Snow, assume pubKeyHash + } else { + input.prevOutScript = privKey.pub.getAddress().toOutputScript() + input.prevOutType = 'pubkeyhash' + input.pubKeys = [privKey.pub] + input.scriptType = input.prevOutType + + } + } + + input.hashType = hashType + input.signatures = input.signatures || [] + } + + // enforce in order signing of public keys + assert(input.pubKeys.some(function(pubKey, i) { + if (!privKey.pub.Q.equals(pubKey.Q)) return false + + assert(!input.signatures[i], 'Signature already exists') + var signatureScript = input.redeemScript || input.prevOutScript + var signatureHash = this.tx.hashForSignature(index, signatureScript, hashType) + var signature = privKey.sign(signatureHash) + input.signatures[i] = signature + + return true + }, this), 'privateKey cannot sign for this input') +} + +module.exports = TransactionBuilder diff --git a/src/wallet.js b/src/wallet.js index da1408002..3e3ab7799 100644 --- a/src/wallet.js +++ b/src/wallet.js @@ -1,304 +1,361 @@ var assert = require('assert') +var bufferutils = require('./bufferutils') +var crypto = require('crypto') +var typeForce = require('typeforce') var networks = require('./networks') -var rng = require('secure-random') var Address = require('./address') var HDNode = require('./hdnode') -var Transaction = require('./transaction') +var TransactionBuilder = require('./transaction_builder') +var Script = require('./script') function Wallet(seed, network) { + console.warn('Wallet is deprecated and will be removed in 2.0.0, see #296') + + seed = seed || crypto.randomBytes(32) network = network || networks.bitcoin // Stored in a closure to make accidental serialization less likely - var masterkey = null - var me = this - var accountZero = null - var internalAccount = null - var externalAccount = null + var masterKey = HDNode.fromSeedBuffer(seed, network) + + // HD first-level child derivation method should be hardened + // See https://bitcointalk.org/index.php?topic=405179.msg4415254#msg4415254 + var accountZero = masterKey.deriveHardened(0) + var externalAccount = accountZero.derive(0) + var internalAccount = accountZero.derive(1) - // Addresses this.addresses = [] this.changeAddresses = [] + this.network = network + this.unspents = [] - // Transaction output data - this.outputs = {} + // FIXME: remove in 2.0.0 + this.unspentMap = {} - // Make a new master key + // FIXME: remove in 2.0.0 + var me = this this.newMasterKey = function(seed) { - seed = seed || new Buffer(rng(32)) - masterkey = HDNode.fromSeedBuffer(seed, network) + console.warn('newMasterKey is deprecated, please make a new Wallet instance instead') + + seed = seed || crypto.randomBytes(32) + masterKey = HDNode.fromSeedBuffer(seed, network) - // HD first-level child derivation method should be hardened - // See https://bitcointalk.org/index.php?topic=405179.msg4415254#msg4415254 - accountZero = masterkey.deriveHardened(0) + accountZero = masterKey.deriveHardened(0) externalAccount = accountZero.derive(0) internalAccount = accountZero.derive(1) me.addresses = [] me.changeAddresses = [] - me.outputs = {} + me.unspents = [] + me.unspentMap = {} } - this.newMasterKey(seed) + this.getMasterKey = function() { return masterKey } + this.getAccountZero = function() { return accountZero } + this.getExternalAccount = function() { return externalAccount } + this.getInternalAccount = function() { return internalAccount } +} - this.generateAddress = function() { - var key = externalAccount.derive(this.addresses.length) - this.addresses.push(key.getAddress().toString()) - return this.addresses[this.addresses.length - 1] - } +Wallet.prototype.createTransaction = function(to, value, options) { + // FIXME: remove in 2.0.0 + if (typeof options !== 'object') { + if (options !== undefined) { + console.warn('Non options object parameters are deprecated, use options object instead') - this.generateChangeAddress = function() { - var key = internalAccount.derive(this.changeAddresses.length) - this.changeAddresses.push(key.getAddress().toString()) - return this.changeAddresses[this.changeAddresses.length - 1] + options = { + fixedFee: arguments[2], + changeAddress: arguments[3] + } + } } - this.getBalance = function() { - return this.getUnspentOutputs().reduce(function(memo, output){ - return memo + output.value - }, 0) - } + options = options || {} - this.getUnspentOutputs = function() { - var utxo = [] + assert(value > this.network.dustThreshold, value + ' must be above dust threshold (' + this.network.dustThreshold + ' Satoshis)') - for(var key in this.outputs){ - var output = this.outputs[key] - if(!output.to) utxo.push(outputToUnspentOutput(output)) - } + var changeAddress = options.changeAddress + var fixedFee = options.fixedFee + var minConf = options.minConf === undefined ? 0 : options.minConf // FIXME: change minConf:1 by default in 2.0.0 - return utxo - } + // filter by minConf, then pending and sort by descending value + var unspents = this.unspents.filter(function(unspent) { + return unspent.confirmations >= minConf + }).filter(function(unspent) { + return !unspent.pending + }).sort(function(o1, o2) { + return o2.value - o1.value + }) - this.setUnspentOutputs = function(utxo) { - var outputs = {} + var accum = 0 + var addresses = [] + var subTotal = value - utxo.forEach(function(uo){ - validateUnspentOutput(uo) - var o = unspentOutputToOutput(uo) - outputs[o.from] = o - }) + var txb = new TransactionBuilder() + txb.addOutput(to, value) - this.outputs = outputs - } + for (var i = 0; i < unspents.length; ++i) { + var unspent = unspents[i] + addresses.push(unspent.address) - function outputToUnspentOutput(output){ - var hashAndIndex = output.from.split(":") + txb.addInput(unspent.txHash, unspent.index) - return { - hash: hashAndIndex[0], - outputIndex: parseInt(hashAndIndex[1]), - address: output.address, - value: output.value, - pending: output.pending - } - } + var fee = fixedFee === undefined ? estimatePaddedFee(txb.buildIncomplete(), this.network) : fixedFee - function unspentOutputToOutput(o) { - var hash = o.hash - var key = hash + ":" + o.outputIndex - return { - from: key, - address: o.address, - value: o.value, - pending: o.pending + accum += unspent.value + subTotal = value + fee + + if (accum >= subTotal) { + var change = accum - subTotal + + if (change > this.network.dustThreshold) { + txb.addOutput(changeAddress || this.getChangeAddress(), change) + } + + break } } - function validateUnspentOutput(uo) { - var missingField + assert(accum >= subTotal, 'Not enough funds (incl. fee): ' + accum + ' < ' + subTotal) + + return this.signWith(txb, addresses).build() +} + +// FIXME: remove in 2.0.0 +Wallet.prototype.processPendingTx = function(tx){ + this.__processTx(tx, true) +} + +// FIXME: remove in 2.0.0 +Wallet.prototype.processConfirmedTx = function(tx){ + this.__processTx(tx, false) +} + +// FIXME: remove in 2.0.0 +Wallet.prototype.__processTx = function(tx, isPending) { + console.warn('processTransaction is considered harmful, see issue #260 for more information') + + var txId = tx.getId() + var txHash = tx.getHash() - if (isNullOrUndefined(uo.hash)) { - missingField = "hash" + tx.outs.forEach(function(txOut, i) { + var address + + try { + address = Address.fromOutputScript(txOut.script, this.network).toString() + } catch(e) { + if (!(e.message.match(/has no matching Address/))) throw e } - var requiredKeys = ['outputIndex', 'address', 'value'] - requiredKeys.forEach(function (key) { - if (isNullOrUndefined(uo[key])){ - missingField = key + var myAddresses = this.addresses.concat(this.changeAddresses) + if (myAddresses.indexOf(address) > -1) { + var lookup = txId + ':' + i + if (lookup in this.unspentMap) return + + // its unique, add it + var unspent = { + address: address, + confirmations: 0, // no way to determine this without more information + index: i, + txHash: txHash, + txId: txId, + value: txOut.value, + pending: isPending } - }) - - if (missingField) { - var message = [ - 'Invalid unspent output: key', missingField, 'is missing.', - 'A valid unspent output must contain' - ] - message.push(requiredKeys.join(', ')) - message.push("and hash") - throw new Error(message.join(' ')) + + this.unspentMap[lookup] = unspent + this.unspents.push(unspent) } - } + }, this) - function isNullOrUndefined(value) { - return value == undefined - } + tx.ins.forEach(function(txIn, i) { + // copy and convert to big-endian hex + var txInId = bufferutils.reverse(txIn.hash).toString('hex') - this.processPendingTx = function(tx){ - processTx(tx, true) - } + var lookup = txInId + ':' + txIn.index + if (!(lookup in this.unspentMap)) return - this.processConfirmedTx = function(tx){ - processTx(tx, false) - } + var unspent = this.unspentMap[lookup] - function processTx(tx, isPending) { - var txid = tx.getId() + if (isPending) { + unspent.pending = true + unspent.spent = true - tx.outs.forEach(function(txOut, i) { - var address + } else { + delete this.unspentMap[lookup] - try { - address = Address.fromOutputScript(txOut.script, network).toString() - } catch(e) { - if (!(e.message.match(/has no matching Address/))) throw e - } + this.unspents = this.unspents.filter(function(unspent2) { + return unspent !== unspent2 + }) + } + }, this) +} - if (isMyAddress(address)) { - var output = txid + ':' + i +Wallet.prototype.generateAddress = function() { + var k = this.addresses.length + var address = this.getExternalAccount().derive(k).getAddress() - me.outputs[output] = { - from: output, - value: txOut.value, - address: address, - pending: isPending - } - } - }) + this.addresses.push(address.toString()) - tx.ins.forEach(function(txIn, i) { - // copy and convert to big-endian hex - var txinId = new Buffer(txIn.hash) - Array.prototype.reverse.call(txinId) - txinId = txinId.toString('hex') + return this.getReceiveAddress() +} - var output = txinId + ':' + txIn.index +Wallet.prototype.generateChangeAddress = function() { + var k = this.changeAddresses.length + var address = this.getInternalAccount().derive(k).getAddress() - if (!(output in me.outputs)) return + this.changeAddresses.push(address.toString()) - if (isPending) { - me.outputs[output].to = txid + ':' + i - me.outputs[output].pending = true - } else { - delete me.outputs[output] - } - }) + return this.getChangeAddress() +} + +Wallet.prototype.getAddress = function() { + if (this.addresses.length === 0) { + this.generateAddress() } - this.createTx = function(to, value, fixedFee, changeAddress) { - assert(value > network.dustThreshold, value + ' must be above dust threshold (' + network.dustThreshold + ' Satoshis)') + return this.addresses[this.addresses.length - 1] +} - var utxos = getCandidateOutputs(value) - var accum = 0 - var subTotal = value - var addresses = [] +Wallet.prototype.getBalance = function(minConf) { + minConf = minConf || 0 - var tx = new Transaction() - tx.addOutput(to, value) + return this.unspents.filter(function(unspent) { + return unspent.confirmations >= minConf - for (var i = 0; i < utxos.length; ++i) { - var utxo = utxos[i] - addresses.push(utxo.address) + // FIXME: remove spent filter in 2.0.0 + }).filter(function(unspent) { + return !unspent.spent + }).reduce(function(accum, unspent) { + return accum + unspent.value + }, 0) +} - var outpoint = utxo.from.split(':') - tx.addInput(outpoint[0], parseInt(outpoint[1])) +Wallet.prototype.getChangeAddress = function() { + if (this.changeAddresses.length === 0) { + this.generateChangeAddress() + } - var fee = fixedFee == undefined ? estimateFeePadChangeOutput(tx) : fixedFee + return this.changeAddresses[this.changeAddresses.length - 1] +} - accum += utxo.value - subTotal = value + fee - if (accum >= subTotal) { - var change = accum - subTotal +Wallet.prototype.getInternalPrivateKey = function(index) { + return this.getInternalAccount().derive(index).privKey +} - if (change > network.dustThreshold) { - tx.addOutput(changeAddress || getChangeAddress(), change) - } +Wallet.prototype.getPrivateKey = function(index) { + return this.getExternalAccount().derive(index).privKey +} - break - } - } +Wallet.prototype.getPrivateKeyForAddress = function(address) { + var index - assert(accum >= subTotal, 'Not enough funds (incl. fee): ' + accum + ' < ' + subTotal) + if ((index = this.addresses.indexOf(address)) > -1) { + return this.getPrivateKey(index) + } - this.signWith(tx, addresses) - return tx + if ((index = this.changeAddresses.indexOf(address)) > -1) { + return this.getInternalPrivateKey(index) } - function getCandidateOutputs() { - var unspent = [] + assert(false, 'Unknown address. Make sure the address is from the keychain and has been generated') +} + +Wallet.prototype.getUnspentOutputs = function(minConf) { + minConf = minConf || 0 - for (var key in me.outputs) { - var output = me.outputs[key] - if (!output.pending) unspent.push(output) + return this.unspents.filter(function(unspent) { + return unspent.confirmations >= minConf + + // FIXME: remove spent filter in 2.0.0 + }).filter(function(unspent) { + return !unspent.spent + }).map(function(unspent) { + return { + address: unspent.address, + confirmations: unspent.confirmations, + index: unspent.index, + txId: unspent.txId, + value: unspent.value, + + // FIXME: remove in 2.0.0 + hash: unspent.txId, + pending: unspent.pending } + }) +} - var sortByValueDesc = unspent.sort(function(o1, o2){ - return o2.value - o1.value - }) +Wallet.prototype.setUnspentOutputs = function(unspents) { + this.unspentMap = {} + this.unspents = unspents.map(function(unspent) { + // FIXME: remove unspent.hash in 2.0.0 + var txId = unspent.txId || unspent.hash + var index = unspent.index - return sortByValueDesc - } + // FIXME: remove in 2.0.0 + if (unspent.hash !== undefined) { + console.warn('unspent.hash is deprecated, use unspent.txId instead') + } - function estimateFeePadChangeOutput(tx) { - var tmpTx = tx.clone() - tmpTx.addOutput(getChangeAddress(), network.dustSoftThreshold || 0) + // FIXME: remove in 2.0.0 + if (index === undefined) { + console.warn('unspent.outputIndex is deprecated, use unspent.index instead') + index = unspent.outputIndex + } - return network.estimateFee(tmpTx) - } + typeForce('String', txId) + typeForce('Number', index) + typeForce('Number', unspent.value) - function getChangeAddress() { - if(me.changeAddresses.length === 0) me.generateChangeAddress(); - return me.changeAddresses[me.changeAddresses.length - 1] - } + assert.equal(txId.length, 64, 'Expected valid txId, got ' + txId) + assert.doesNotThrow(function() { Address.fromBase58Check(unspent.address) }, 'Expected Base58 Address, got ' + unspent.address) + assert(isFinite(index), 'Expected finite index, got ' + index) - this.signWith = function(tx, addresses) { - assert.equal(tx.ins.length, addresses.length, 'Number of addresses must match number of transaction inputs') + // FIXME: remove branch in 2.0.0 + if (unspent.confirmations !== undefined) { + typeForce('Number', unspent.confirmations) + } - addresses.forEach(function(address, i) { - var key = me.getPrivateKeyForAddress(address) + var txHash = bufferutils.reverse(new Buffer(txId, 'hex')) - tx.sign(i, key) - }) + unspent = { + address: unspent.address, + confirmations: unspent.confirmations || 0, + index: index, + txHash: txHash, + txId: txId, + value: unspent.value, - return tx - } + // FIXME: remove in 2.0.0 + pending: unspent.pending || false + } - this.getMasterKey = function() { return masterkey } - this.getAccountZero = function() { return accountZero } - this.getInternalAccount = function() { return internalAccount } - this.getExternalAccount = function() { return externalAccount } + // FIXME: remove in 2.0.0 + this.unspentMap[txId + ':' + index] = unspent - this.getPrivateKey = function(index) { - return externalAccount.derive(index).privKey - } + return unspent + }, this) +} - this.getInternalPrivateKey = function(index) { - return internalAccount.derive(index).privKey - } +Wallet.prototype.signWith = function(tx, addresses) { + addresses.forEach(function(address, i) { + var privKey = this.getPrivateKeyForAddress(address) - this.getPrivateKeyForAddress = function(address) { - var index - if((index = this.addresses.indexOf(address)) > -1) { - return this.getPrivateKey(index) - } else if((index = this.changeAddresses.indexOf(address)) > -1) { - return this.getInternalPrivateKey(index) - } else { - throw new Error('Unknown address. Make sure the address is from the keychain and has been generated.') - } - } + tx.sign(i, privKey) + }, this) - function isReceiveAddress(address){ - return me.addresses.indexOf(address) > -1 - } + return tx +} - function isChangeAddress(address){ - return me.changeAddresses.indexOf(address) > -1 - } +function estimatePaddedFee(tx, network) { + var tmpTx = tx.clone() + tmpTx.addOutput(Script.EMPTY, network.dustSoftThreshold || 0) - function isMyAddress(address) { - return isReceiveAddress(address) || isChangeAddress(address) - } + return network.estimateFee(tmpTx) } +// FIXME: 1.0.0 shims, remove in 2.0.0 +Wallet.prototype.getReceiveAddress = Wallet.prototype.getAddress +Wallet.prototype.createTx = Wallet.prototype.createTransaction + module.exports = Wallet diff --git a/test/address.js b/test/address.js index 50ff895af..0e29f0d93 100644 --- a/test/address.js +++ b/test/address.js @@ -10,22 +10,22 @@ describe('Address', function() { describe('Constructor', function() { it('does not mutate the input', function() { fixtures.valid.forEach(function(f) { - var hash = new Buffer(f.hex, 'hex') + var hash = new Buffer(f.hash, 'hex') var addr = new Address(hash, f.version) assert.equal(addr.version, f.version) - assert.equal(addr.hash.toString('hex'), f.hex) + assert.equal(addr.hash.toString('hex'), f.hash) }) }) }) describe('fromBase58Check', function() { fixtures.valid.forEach(function(f) { - it('imports ' + f.description + '(' + f.network + ') correctly', function() { + it('imports ' + f.script + ' (' + f.network + ') correctly', function() { var addr = Address.fromBase58Check(f.base58check) assert.equal(addr.version, f.version) - assert.equal(addr.hash.toString('hex'), f.hex) + assert.equal(addr.hash.toString('hex'), f.hash) }) }) @@ -40,18 +40,18 @@ describe('Address', function() { describe('fromOutputScript', function() { fixtures.valid.forEach(function(f) { - it('imports ' + f.description + '(' + f.network + ') correctly', function() { - var script = Script.fromHex(f.script) + it('imports ' + f.script + ' (' + f.network + ') correctly', function() { + var script = Script.fromASM(f.script) var addr = Address.fromOutputScript(script, networks[f.network]) assert.equal(addr.version, f.version) - assert.equal(addr.hash.toString('hex'), f.hex) + assert.equal(addr.hash.toString('hex'), f.hash) }) }) fixtures.invalid.fromOutputScript.forEach(function(f) { it('throws when ' + f.description, function() { - var script = Script.fromHex(f.hex) + var script = Script.fromASM(f.script) assert.throws(function() { Address.fromOutputScript(script) @@ -62,7 +62,7 @@ describe('Address', function() { describe('toBase58Check', function() { fixtures.valid.forEach(function(f) { - it('exports ' + f.description + '(' + f.network + ') correctly', function() { + it('exports ' + f.script + ' (' + f.network + ') correctly', function() { var addr = Address.fromBase58Check(f.base58check) var result = addr.toBase58Check() @@ -73,17 +73,17 @@ describe('Address', function() { describe('toOutputScript', function() { fixtures.valid.forEach(function(f) { - it('imports ' + f.description + '(' + f.network + ') correctly', function() { + it('imports ' + f.script + ' (' + f.network + ') correctly', function() { var addr = Address.fromBase58Check(f.base58check) var script = addr.toOutputScript() - assert.equal(script.toHex(), f.script) + assert.equal(script.toASM(), f.script) }) }) fixtures.invalid.toOutputScript.forEach(function(f) { it('throws when ' + f.description, function() { - var addr = new Address(new Buffer(f.hex, 'hex'), f.version) + var addr = new Address(new Buffer(f.hash, 'hex'), f.version) assert.throws(function() { addr.toOutputScript() diff --git a/test/base58check.js b/test/base58check.js new file mode 100644 index 000000000..030fb09c0 --- /dev/null +++ b/test/base58check.js @@ -0,0 +1,28 @@ +var assert = require('assert') +var base58check = require('../src/base58check') +var bs58check = require('bs58check') +var sinon = require('sinon') + +describe('base58check', function() { + var param + + beforeEach(function() { + param = {} + }) + + it('wraps bs58check.decode', sinon.test(function() { + var expectation = this.mock(bs58check).expects('decode') + expectation.once().calledWith(param) + expectation.onCall(0).returns('foo') + + assert.equal(base58check.decode(param), 'foo') + })) + + it('wraps bs58check.encode', sinon.test(function() { + var expectation = this.mock(bs58check).expects('encode') + expectation.once().calledWith(param) + expectation.onCall(0).returns('foo') + + assert.equal(base58check.encode(param), 'foo') + })) +}) diff --git a/test/bitcoin.core.js b/test/bitcoin.core.js index 71bd7e6f9..eadb07641 100644 --- a/test/bitcoin.core.js +++ b/test/bitcoin.core.js @@ -1,14 +1,16 @@ var assert = require('assert') + var base58 = require('bs58') -var base58check = require('bs58check') -var networks = require('../src/networks') +//var base58check = require('bs58check') -var Address = require('../src/address') -var BigInteger = require('bigi') -var ECKey = require('../src/eckey') -var ECSignature = require('../src/ecsignature') -var Transaction = require('../src/transaction') -var Script = require('../src/script') +var Bitcoin = require('../') +var Address = Bitcoin.Address +var base58check = Bitcoin.base58check +var networks = Bitcoin.networks +var ECKey = Bitcoin.ECKey +var ECSignature = Bitcoin.ECSignature +var Transaction = Bitcoin.Transaction +var Script = Bitcoin.Script var base58_encode_decode = require("./fixtures/core/base58_encode_decode.json") var base58_keys_invalid = require("./fixtures/core/base58_keys_invalid.json") @@ -27,7 +29,7 @@ describe('Bitcoin-core', function() { it('can decode ' + fb58, function() { var buffer = base58.decode(fb58) - var actual = buffer.toString('hex') + var actual = new Buffer(buffer).toString('hex') assert.equal(actual, fhex) }) @@ -94,17 +96,19 @@ describe('Bitcoin-core', function() { var string = f[0] var hex = f[1] var params = f[2] - var network = networks.bitcoin + var network = params.isTestnet ? networks.testnet : networks.bitcoin if (!params.isPrivkey) return - if (params.isTestnet) network = networks.testnet + var privKey = ECKey.fromWIF(string) it('imports ' + string + ' correctly', function() { - var privKey = ECKey.fromWIF(string) - assert.equal(privKey.d.toHex(), hex) assert.equal(privKey.pub.compressed, params.isCompressed) }) + + it('exports ' + hex + ' to ' + string, function() { + assert.equal(privKey.toWIF(network), string) + }) }) }) @@ -120,8 +124,8 @@ describe('Bitcoin-core', function() { it('throws on ' + string, function() { assert.throws(function() { - var privKey = ECKey.fromWIF(string) - var version = base58check.decode(string).version + ECKey.fromWIF(string) + var version = base58check.decode(string).readUInt8(0) assert.notEqual(allowedNetworks.indexOf(version), -1, 'Invalid network') }, /Invalid (checksum|compression flag|network|WIF payload)/) @@ -183,13 +187,13 @@ describe('Bitcoin-core', function() { var actualHash try { - actualHash = transaction.hashForSignature(script, inIndex, hashType) + actualHash = transaction.hashForSignature(inIndex, script, hashType) } catch (e) { // don't fail if we don't support it yet, TODO if (!e.message.match(/not yet supported/)) throw e } - if (actualHash != undefined) { + if (actualHash !== undefined) { // Test data is big-endian Array.prototype.reverse.call(actualHash) diff --git a/test/block.js b/test/block.js new file mode 100644 index 000000000..c5099804a --- /dev/null +++ b/test/block.js @@ -0,0 +1,88 @@ +var assert = require('assert') + +var Block = require('../src/block') + +var fixtures = require('./fixtures/block') + +describe('Block', function() { + describe('fromBuffer/fromHex', function() { + fixtures.valid.forEach(function(f) { + it('imports the block: ' + f.description + ' correctly', function() { + var block = Block.fromHex(f.hex) + + assert.equal(block.version, f.version) + assert.equal(block.prevHash.toString('hex'), f.prevHash) + assert.equal(block.merkleRoot.toString('hex'), f.merkleRoot) + assert.equal(block.timestamp, f.timestamp) + assert.equal(block.bits, f.bits) + assert.equal(block.nonce, f.nonce) + }) + }) + + fixtures.invalid.forEach(function(f) { + it('throws on ' + f.exception, function() { + assert.throws(function() { + Block.fromHex(f.hex) + }, new RegExp(f.exception)) + }) + }) + }) + + describe('toBuffer/toHex', function() { + fixtures.valid.forEach(function(f) { + var block + + beforeEach(function() { + block = Block.fromHex(f.hex) + }) + + it('exports the block: ' + f.description + ' correctly', function() { + assert.equal(block.toHex(), f.hex) + }) + }) + }) + + describe('getHash', function() { + fixtures.valid.forEach(function(f) { + var block + + beforeEach(function() { + block = Block.fromHex(f.hex) + }) + + it('calculates ' + f.hash + ' for the block: ' + f.description, function() { + assert.equal(block.getHash().toString('hex'), f.hash) + }) + }) + }) + + describe('getId', function() { + fixtures.valid.forEach(function(f) { + var block + + beforeEach(function() { + block = Block.fromHex(f.hex) + }) + + it('calculates ' + f.id + ' for the block: ' + f.description, function() { + assert.equal(block.getId(), f.id) + }) + }) + }) + + describe('getUTCDate', function() { + fixtures.valid.forEach(function(f) { + var block + + beforeEach(function() { + block = Block.fromHex(f.hex) + }) + + it('returns UTC date of ' + f.id, function() { + var utcDate = block.getUTCDate().getTime() + + assert.equal(utcDate, f.timestamp * 1e3) + }) + }) + }) +}) diff --git a/test/bufferutils.js b/test/bufferutils.js index d7dbfa33f..8fc054cb0 100644 --- a/test/bufferutils.js +++ b/test/bufferutils.js @@ -75,6 +75,29 @@ describe('bufferutils', function() { }) }) + describe('reverse', function() { + fixtures.valid.forEach(function(f) { + it('reverses ' + f.hex64 + ' correctly', function() { + var buffer = new Buffer(f.hex64, 'hex') + var buffer2 = bufferutils.reverse(buffer) + + Array.prototype.reverse.call(buffer) + + assert.deepEqual(buffer, buffer2) + }) + }) + }) + + describe('varIntBuffer', function() { + fixtures.valid.forEach(function(f) { + it('encodes ' + f.dec + ' correctly', function() { + var buffer = bufferutils.varIntBuffer(f.dec) + + assert.equal(buffer.toString('hex'), f.hexVI) + }) + }) + }) + describe('varIntSize', function() { fixtures.valid.forEach(function(f) { it('determines the varIntSize of ' + f.dec + ' correctly', function() { @@ -86,7 +109,7 @@ describe('bufferutils', function() { }) describe('writePushDataInt', function() { - fixtures.valid.forEach(function(f, i) { + fixtures.valid.forEach(function(f) { if (!f.hexPD) return it('encodes ' + f.dec + ' correctly', function() { diff --git a/test/convert.js b/test/convert.js deleted file mode 100644 index 0b06e4429..000000000 --- a/test/convert.js +++ /dev/null @@ -1,27 +0,0 @@ -var assert = require('assert') -var convert = require('../src/convert') - -var fixtures = require('./fixtures/convert') - -describe('convert', function() { - describe('bufferToWordArray', function() { - fixtures.valid.forEach(function(f) { - it('converts ' + f.hex + ' correctly', function() { - var buffer = new Buffer(f.hex, 'hex') - var result = convert.bufferToWordArray(buffer) - - assert.deepEqual(result, f.wordArray) - }) - }) - }) - - describe('wordArrayToBuffer', function() { - fixtures.valid.forEach(function(f) { - it('converts to ' + f.hex + ' correctly', function() { - var resultHex = convert.wordArrayToBuffer(f.wordArray).toString('hex') - - assert.deepEqual(resultHex, f.hex) - }) - }) - }) -}) diff --git a/test/ecdsa.js b/test/ecdsa.js index 84c0b44a3..b95397775 100644 --- a/test/ecdsa.js +++ b/test/ecdsa.js @@ -15,12 +15,25 @@ var fixtures = require('./fixtures/ecdsa.json') describe('ecdsa', function() { describe('deterministicGenerateK', function() { - fixtures.valid.forEach(function(f) { + function checkSig() { return true } + + fixtures.valid.ecdsa.forEach(function(f) { it('for \"' + f.message + '\"', function() { var d = BigInteger.fromHex(f.d) var h1 = crypto.sha256(f.message) - var k = ecdsa.deterministicGenerateK(curve, h1, d) + var k = ecdsa.deterministicGenerateK(curve, h1, d, checkSig) + assert.equal(k.toHex(), f.k) + }) + }) + + // FIXME: remove in 2.0.0 + fixtures.valid.ecdsa.forEach(function(f) { + it('(deprecated) for \"' + f.message + '\"', function() { + var d = BigInteger.fromHex(f.d) + var h1 = crypto.sha256(f.message) + + var k = ecdsa.deterministicGenerateK(curve, h1, d) // default checkSig assert.equal(k.toHex(), f.k) }) }) @@ -28,21 +41,58 @@ describe('ecdsa', function() { it('loops until an appropriate k value is found', sinon.test(function() { this.mock(BigInteger).expects('fromBuffer') .exactly(3) - .onCall(0).returns(new BigInteger('0')) - .onCall(1).returns(curve.n) - .onCall(2).returns(new BigInteger('42')) + .onCall(0).returns(new BigInteger('0')) // < 1 + .onCall(1).returns(curve.n) // > n-1 + .onCall(2).returns(new BigInteger('42')) // valid var d = new BigInteger('1') var h1 = new Buffer(32) - - var k = ecdsa.deterministicGenerateK(curve, h1, d) + var k = ecdsa.deterministicGenerateK(curve, h1, d, checkSig) assert.equal(k.toString(), '42') })) + + it('loops until a suitable signature is found', sinon.test(function() { + this.mock(BigInteger).expects('fromBuffer') + .exactly(4) + .onCall(0).returns(new BigInteger('0')) // < 1 + .onCall(1).returns(curve.n) // > n-1 + .onCall(2).returns(new BigInteger('42')) // valid, but 'bad' signature + .onCall(3).returns(new BigInteger('53')) // valid, good signature + + var checkSig = this.mock() + checkSig.exactly(2) + checkSig.onCall(0).returns(false) // bad signature + checkSig.onCall(1).returns(true) // good signature + + var d = new BigInteger('1') + var h1 = new Buffer(32) + var k = ecdsa.deterministicGenerateK(curve, h1, d, checkSig) + + assert.equal(k.toString(), '53') + })) + + fixtures.valid.rfc6979.forEach(function(f) { + it('produces the expected k values for ' + f.message + ' if k wasn\'t suitable', function() { + var d = BigInteger.fromHex(f.d) + var h1 = crypto.sha256(f.message) + + var results = [] + ecdsa.deterministicGenerateK(curve, h1, d, function(k) { + results.push(k) + + return results.length === 16 + }) + + assert.equal(results[0].toHex(), f.k0) + assert.equal(results[1].toHex(), f.k1) + assert.equal(results[15].toHex(), f.k15) + }) + }) }) describe('recoverPubKey', function() { - fixtures.valid.forEach(function(f) { + fixtures.valid.ecdsa.forEach(function(f) { it('recovers the pubKey for ' + f.d, function() { var d = BigInteger.fromHex(f.d) var Q = curve.G.multiply(d) @@ -94,7 +144,7 @@ describe('ecdsa', function() { }) describe('sign', function() { - fixtures.valid.forEach(function(f) { + fixtures.valid.ecdsa.forEach(function(f) { it('produces a deterministic signature for \"' + f.message + '\"', function() { var d = BigInteger.fromHex(f.d) var hash = crypto.sha256(f.message) @@ -115,31 +165,35 @@ describe('ecdsa', function() { }) }) - describe('verifyRaw', function() { - fixtures.valid.forEach(function(f) { + describe('verify/verifyRaw', function() { + fixtures.valid.ecdsa.forEach(function(f) { it('verifies a valid signature for \"' + f.message + '\"', function() { var d = BigInteger.fromHex(f.d) - var e = BigInteger.fromBuffer(crypto.sha256(f.message)) + var H = crypto.sha256(f.message) + var e = BigInteger.fromBuffer(H) var signature = new ECSignature( new BigInteger(f.signature.r), new BigInteger(f.signature.s) ) var Q = curve.G.multiply(d) + assert(ecdsa.verify(curve, H, signature, Q)) assert(ecdsa.verifyRaw(curve, e, signature, Q)) }) }) fixtures.invalid.verifyRaw.forEach(function(f) { it('fails to verify with ' + f.description, function() { + var H = crypto.sha256(f.message) + var e = BigInteger.fromBuffer(H) var d = BigInteger.fromHex(f.d) - var e = BigInteger.fromHex(f.e) var signature = new ECSignature( new BigInteger(f.signature.r), new BigInteger(f.signature.s) ) var Q = curve.G.multiply(d) + assert.equal(ecdsa.verify(curve, H, signature, Q), false) assert.equal(ecdsa.verifyRaw(curve, e, signature, Q), false) }) }) diff --git a/test/eckey.js b/test/eckey.js index 7694a1bff..271e5f75a 100644 --- a/test/eckey.js +++ b/test/eckey.js @@ -1,8 +1,7 @@ var assert = require('assert') -var crypto = require('../src/crypto') +var crypto = require('crypto') +var ecurve = require('ecurve') var networks = require('../src/networks') - -var secureRandom = require('secure-random') var sinon = require('sinon') var BigInteger = require('bigi') @@ -44,6 +43,21 @@ describe('ECKey', function() { }) }) + it('uses the secp256k1 curve by default', function() { + var secp256k1 = ecurve.getCurveByName('secp256k1') + + for (var property in secp256k1) { + // FIXME: circular structures in ecurve + if (property === 'G') continue + if (property === 'infinity') continue + + var actual = ECKey.curve[property] + var expected = secp256k1[property] + + assert.deepEqual(actual, expected) + } + }) + describe('fromWIF', function() { fixtures.valid.forEach(function(f) { f.WIFs.forEach(function(wif) { @@ -84,13 +98,13 @@ describe('ECKey', function() { var exPrivKey = ECKey.fromWIF(exWIF) var exBuffer = exPrivKey.d.toBuffer(32) - describe('using default RNG', function() { + describe('uses default crypto RNG', function() { beforeEach(function() { - sinon.stub(secureRandom, 'randomBuffer').returns(exBuffer) + sinon.stub(crypto, 'randomBytes').returns(exBuffer) }) afterEach(function() { - secureRandom.randomBuffer.restore() + crypto.randomBytes.restore() }) it('generates a ECKey', function() { @@ -116,7 +130,7 @@ describe('ECKey', function() { }) describe('signing', function() { - var hash = crypto.sha256('Vires in numeris') + var hash = crypto.randomBytes(32) var priv = ECKey.makeRandom() var signature = priv.sign(hash) diff --git a/test/ecpubkey.js b/test/ecpubkey.js index 62609ef96..bf4ac5a52 100644 --- a/test/ecpubkey.js +++ b/test/ecpubkey.js @@ -35,6 +35,21 @@ describe('ECPubKey', function() { }) }) + it('uses the secp256k1 curve by default', function() { + var secp256k1 = ecurve.getCurveByName('secp256k1') + + for (var property in secp256k1) { + // FIXME: circular structures in ecurve + if (property === 'G') continue + if (property === 'infinity') continue + + var actual = ECPubKey.curve[property] + var expected = secp256k1[property] + + assert.deepEqual(actual, expected) + } + }) + describe('fromHex/toHex', function() { it('supports compressed points', function() { var pubKey = ECPubKey.fromHex(fixtures.compressed.hex) @@ -91,13 +106,13 @@ describe('ECPubKey', function() { it('verifies a valid signature', function() { var hash = crypto.sha256(fixtures.message) - assert.ok(pubKey.verify(hash, signature)) + assert(pubKey.verify(hash, signature)) }) it('doesn\'t verify the wrong signature', function() { var hash = crypto.sha256('mushrooms') - assert.ok(!pubKey.verify(hash, signature)) + assert(!pubKey.verify(hash, signature)) }) }) }) diff --git a/test/ecsignature.js b/test/ecsignature.js index dac7fb2e4..1866ac24e 100644 --- a/test/ecsignature.js +++ b/test/ecsignature.js @@ -92,6 +92,19 @@ describe('ECSignature', function() { assert.equal(scriptSignature.toString('hex'), f.scriptSignature.hex) }) }) + + fixtures.invalid.scriptSignature.forEach(function(f) { + it('throws ' + f.exception, function() { + var signature = new ECSignature( + new BigInteger(f.signature.r), + new BigInteger(f.signature.s) + ) + + assert.throws(function() { + signature.toScriptSignature(f.hashType) + }, new RegExp(f.exception)) + }) + }) }) describe('parseScriptSignature', function() { @@ -106,9 +119,9 @@ describe('ECSignature', function() { }) }) - fixtures.invalid.DER.forEach(function(f) { + fixtures.invalid.scriptSignature.forEach(function(f) { it('throws on ' + f.hex, function() { - var buffer = new Buffer(f.hex + '01', 'hex') + var buffer = new Buffer(f.hex, 'hex') assert.throws(function() { ECSignature.parseScriptSignature(buffer) diff --git a/test/fixtures/address.json b/test/fixtures/address.json index cedd17d1f..fff8d9a59 100644 --- a/test/fixtures/address.json +++ b/test/fixtures/address.json @@ -1,36 +1,32 @@ { "valid": [ { - "description": "pubKeyHash", "network": "bitcoin", "version": 0, - "hex": "751e76e8199196d454941c45d1b3a323f1433bd6", + "hash": "751e76e8199196d454941c45d1b3a323f1433bd6", "base58check": "1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH", - "script": "76a914751e76e8199196d454941c45d1b3a323f1433bd688ac" + "script": "OP_DUP OP_HASH160 751e76e8199196d454941c45d1b3a323f1433bd6 OP_EQUALVERIFY OP_CHECKSIG" }, { - "description": "scriptHash", "network": "bitcoin", "version": 5, - "hex": "cd7b44d0b03f2d026d1e586d7ae18903b0d385f6", + "hash": "cd7b44d0b03f2d026d1e586d7ae18903b0d385f6", "base58check": "3LRW7jeCvQCRdPF8S3yUCfRAx4eqXFmdcr", - "script": "a914cd7b44d0b03f2d026d1e586d7ae18903b0d385f687" + "script": "OP_HASH160 cd7b44d0b03f2d026d1e586d7ae18903b0d385f6 OP_EQUAL" }, { - "description": "pubKeyHash", "network": "testnet", "version": 111, - "hex": "751e76e8199196d454941c45d1b3a323f1433bd6", + "hash": "751e76e8199196d454941c45d1b3a323f1433bd6", "base58check": "mrCDrCybB6J1vRfbwM5hemdJz73FwDBC8r", - "script": "76a914751e76e8199196d454941c45d1b3a323f1433bd688ac" + "script": "OP_DUP OP_HASH160 751e76e8199196d454941c45d1b3a323f1433bd6 OP_EQUALVERIFY OP_CHECKSIG" }, { - "description": "scriptHash", "network": "testnet", "version": 196, - "hex": "cd7b44d0b03f2d026d1e586d7ae18903b0d385f6", + "hash": "cd7b44d0b03f2d026d1e586d7ae18903b0d385f6", "base58check": "2NByiBUaEXrhmqAsg7BbLpcQSAQs1EDwt5w", - "script": "a914cd7b44d0b03f2d026d1e586d7ae18903b0d385f687" + "script": "OP_HASH160 cd7b44d0b03f2d026d1e586d7ae18903b0d385f6 OP_EQUAL" } ], "invalid": { @@ -48,22 +44,22 @@ ], "fromOutputScript": [ { - "description": "pubkey has no matching Address", - "hex": "21031f1e68f82112b373f0fe980b3a89d212d2b5c01fb51eb25acb8b4c4b4299ce95ac" + "description": "has no matching Address", + "script": "031f1e68f82112b373f0fe980b3a89d212d2b5c01fb51eb25acb8b4c4b4299ce95 OP_CHECKSIG" }, { - "description": "multisig has no matching Address", - "hex": "5121032487c2a32f7c8d57d2a93906a6457afd00697925b0e6e145d89af6d3bca330162102308673d16987eaa010e540901cc6fe3695e758c19f46ce604e174dac315e685a52ae" + "description": "has no matching Address", + "script": "OP_TRUE 032487c2a32f7c8d57d2a93906a6457afd00697925b0e6e145d89af6d3bca33016 02308673d16987eaa010e540901cc6fe3695e758c19f46ce604e174dac315e685a OP_2 OP_CHECKMULTISIG" }, { - "description": "nulldata has no matching Address", - "hex": "6a2606deadbeef03f895a2ad89fb6d696497af486cb7c644a27aa568c7a18dd06113401115185474" + "description": "has no matching Address", + "script": "OP_RETURN 06deadbeef03f895a2ad89fb6d696497af486cb7c644a27aa568c7a18dd06113401115185474" } ], "toOutputScript": [ { "description": "24kPZCmVgzfkpGdXExy56234MRHrsqQxNWE has no matching Script", - "hex": "751e76e8199196d454941c45d1b3a323f1433bd6", + "hash": "751e76e8199196d454941c45d1b3a323f1433bd6", "version": 153 } ] diff --git a/test/fixtures/base58.json b/test/fixtures/base58.json deleted file mode 100644 index a9b3705df..000000000 --- a/test/fixtures/base58.json +++ /dev/null @@ -1,74 +0,0 @@ -{ - "valid": [ - { - "hex": "", - "string": "" - }, - { - "hex": "61", - "string": "2g" - }, - { - "hex": "626262", - "string": "a3gV" - }, - { - "hex": "636363", - "string": "aPEr" - }, - { - "hex": "73696d706c792061206c6f6e6720737472696e67", - "string": "2cFupjhnEsSn59qHXstmK2ffpLv2" - }, - { - "hex": "00eb15231dfceb60925886b67d065299925915aeb172c06647", - "string": "1NS17iag9jJgTHD1VXjvLCEnZuQ3rJDE9L" - }, - { - "hex": "516b6fcd0f", - "string": "ABnLTmg" - }, - { - "hex": "bf4f89001e670274dd", - "string": "3SEo3LWLoPntC" - }, - { - "hex": "572e4794", - "string": "3EFU7m" - }, - { - "hex": "ecac89cad93923c02321", - "string": "EJDM8drfXA6uyA" - }, - { - "hex": "10c8511e", - "string": "Rt5zm" - }, - { - "hex": "00000000000000000000", - "string": "1111111111" - } - ], - "invalid": [ - { - "description": "non-base58 string", - "string": "invalid" - }, - { - "description": "non-base58 alphabet", - "string": "c2F0b3NoaQo=" - }, - { - "description": "leading whitespace", - "string": " 1111111111" - }, - { - "description": "trailing whitespace", - "string": "1111111111 " - }, - { - "description": "unexpected character after whitespace", - "string": " \t\n\u000b\f\r skip \r\f\u000b\n\t a" - } - ] -} diff --git a/test/fixtures/block.json b/test/fixtures/block.json new file mode 100644 index 000000000..147a7ca2d --- /dev/null +++ b/test/fixtures/block.json @@ -0,0 +1,58 @@ +{ + "valid": [ + { + "description": "Coinbase only - Headers only", + "hash": "55388f8f9b326bd0b8e50fbe44c1903d4be14febcfad4dffa50c846c00000000", + "id": "000000006c840ca5ff4dadcfeb4fe14b3d90c144be0fe5b8d06b329b8f8f3855", + "version": 2, + "prevHash": "3385c4b2a3499669987f5d04fa4127b59dbf2ee625694fa0bf08000000000000", + "merkleRoot": "cf52f0ed6571367818a801a169e64030d8cab1a9f17e27170a6924127e19dbb8", + "timestamp": 1413391595, + "bits": 486604799, + "nonce": 3760981266, + "hex": "020000003385c4b2a3499669987f5d04fa4127b59dbf2ee625694fa0bf08000000000000cf52f0ed6571367818a801a169e64030d8cab1a9f17e27170a6924127e19dbb8eba43e54ffff001d12052ce0" + }, + { + "description": "Coinbase only", + "hash": "55388f8f9b326bd0b8e50fbe44c1903d4be14febcfad4dffa50c846c00000000", + "id": "000000006c840ca5ff4dadcfeb4fe14b3d90c144be0fe5b8d06b329b8f8f3855", + "version": 2, + "prevHash": "3385c4b2a3499669987f5d04fa4127b59dbf2ee625694fa0bf08000000000000", + "merkleRoot": "cf52f0ed6571367818a801a169e64030d8cab1a9f17e27170a6924127e19dbb8", + "timestamp": 1413391595, + "bits": 486604799, + "nonce": 3760981266, + "hex": "020000003385c4b2a3499669987f5d04fa4127b59dbf2ee625694fa0bf08000000000000cf52f0ed6571367818a801a169e64030d8cab1a9f17e27170a6924127e19dbb8eba43e54ffff001d12052ce00101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff2403089904174b6e434d696e65724251521defe5cdcf04ad543ea4eb0101000000165e0000ffffffff0100f90295000000001976a9149e8985f82bc4e0f753d0492aa8d11cc39925774088ac00000000" + }, + { + "description": "Low number of transactions", + "hash": "f0ca57cf84cc953194cd87de4bd9142720a056dc6f27484767f3e85e00000000", + "id": "000000005ee8f3674748276fdc56a0202714d94bde87cd943195cc84cf57caf0", + "version": 2, + "prevHash": "0cccf0b884a20113ea2c53a381dacc92a68ae9db1cf86525eb259f0c00000000", + "merkleRoot": "0ebdaf5341d911e69ab53928e3f9f46e5ece27b950f3b43eae521a602bde41d3", + "timestamp": 1413393997, + "bits": 486604799, + "nonce": 3126400832, + "hex": "020000000cccf0b884a20113ea2c53a381dacc92a68ae9db1cf86525eb259f0c000000000ebdaf5341d911e69ab53928e3f9f46e5ece27b950f3b43eae521a602bde41d34dae3e54ffff001d401759ba0a01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0e030a9904026309062f503253482fffffffff01700a049500000000232103b441d74dccfe4d9a3b343149557107a68feacbe95b5ea3af63f4259b4f278b24ac0000000001000000014c959784efb5b2e8d19e2aaca588f7591c10d8f9833dfaa70db9fcaaca123ce6010000008b48304502200d4e79a0aaa162413b812aa9a5a9af18933bb9ee8adabbbe745785ecd0a41d7d022100909f15d27633127727b02a36c2986c37fda5df7ac686002dc9d0b892678b42b40141040cfa3dfb357bdff37c8748c7771e173453da5d7caa32972ab2f5c888fff5bbaeb5fc812b473bf808206930fade81ef4e373e60039886b51022ce68902d96ef70ffffffff02a0860100000000001976a914ff6d383fc1eb0560b5bf63bac7988763098ee7b688acb10abba80a0000001976a91461b469ada61f37c620010912a9d5d56646015f1688ac0000000001000000012568651e80bd1f2c08fde0b10ef507c19ca95e0f7249d6d8b473eada36963866010000006b4830450221008be9aff4a081f7c734e2b679ca3483e205711fad2733c5f5afb8c0bb5213930a02206322d9eb1808e5633da787bf56966418169fcb0a2b964b9a35053c4859cfd35f012103bb318b00de944086fad67ab78a832eb1bf26916053ecd3b14a3f48f9fbe0821fffffffff02a8610000000000001976a91452bc36b0497a027d1c5637d8df7389a48b34245a88ac101aff03000000001976a9148e8c1d4adef86c11154fd04b5012306715fd4baf88ac00000000010000000103642c8bfac609738fb1b17f270ba2baf5df8e87108fc795070c8bf2e415210c010000006b4830450221009714a186283b0d97ae5b24f538618f96e0cca42269e69c389c9c5211bb095bcf02200e3992501e90f44a8b03b26cf9c3f2b16d73d150ddefcae607fc1e7c2dcc02c40121027ccca0e0a9c86180431340aa14038f22428b196a7a97f4c63b4afe142afb55e5ffffffff03781e0000000000001976a914a4547646725696134124fdf5b465438b940d43aa88ac781e000000000000475121027ccca0e0a9c86180431340aa14038f22428b196a7a97f4c63b4afe142afb55e5211c434e5452505254590000000000f0a6c9ae7ec80100000000000000030000000052ae035cad01000000001976a91421cd98f4b804dc07f992559cb551158a078472f688ac00000000010000000183f2c7c9ccba010eeb72a53f83a41970c2cf2e2f2deac895e917fc1a8da11aec000000006a4730440220068e182f5528c1752414bb8db48637b8e329b700018d02056d4ed01721daef9f02202bb66702d586e4ece02e70ebac251b5e5e7af0914ceda2f5efd1ed0e7fbe51030121031765feada1e5d93645b514bced2ac7135bb4d14c41c5e1f9f973803537299c20ffffffff02a0860100000000001976a91436e875fe44ff4a4b13838d73ed32b5c62722096588acb09a9600000000001976a9141d6131f1ffad58a7d091f1fab29ee36b27b091c288ac000000000100000001387daacc7861110fc52535e8a9733d40fb9c56b281708993e1678c79ed927677000000006a47304402205b082eea0f426dc753b7489df44f2b979d1cccd3b6f0ec9bb2ca002cae4cf78a02203733f049ac70354ffa8eb290c7c470352802339ddcb940659340d15fac89c880012103e1a574d02cd9d0231cf53cb2ea5bba86d570f0edc9b5cdff613a46d381103fb0ffffffff01e054e111000000001976a9140744f9fd5a3b73a656694171412182a32f2971b788ac000000000100000001be200e8543914878d90b147f5a43bfc3828af510a043a4e0512293df5ea4ad60010000006b483045022025f5128d9d2c66a32e8fb80c733054a3ab74fcba6499a84f55918e52551e8d01022100a11f183fb8da767fb16b71fbe737f062983793509fc6b8286a6a2c84f6553a690121037beaee4dcb1fdf673665256608d3ad0beacde433b117ea8f0a8d23b8a0ee8307ffffffff01e092f505000000001976a9140744f9fd5a3b73a656694171412182a32f2971b788ac0000000001000000010f5ed77b8816242642a5ba39464e48fcd1cacce6dfc27862d922407b760fadaf010000008b483045022019737d8bebf06ae5588888dcf403b456484cd8a1eef73e5890f980dc9ef88aa002210088e8d47f7c719482ae74ba6bea8a70a76bbfd72f751bb78e49a9499e931608c50141040cfa3dfb357bdff37c8748c7771e173453da5d7caa32972ab2f5c888fff5bbaeb5fc812b473bf808206930fade81ef4e373e60039886b51022ce68902d96ef70ffffffff02a0860100000000001976a9145bd3695ed80d96ad480bcc3e252030e30b1c2b1c88ac015db9a80a0000001976a91461b469ada61f37c620010912a9d5d56646015f1688ac0000000001000000016f367591e290ecc8d36a27e00d03a789df873dda395a1c5dfc6e52ecd6d8630f020000006c493046022100e1a59d90352d278499ed252fe5318bef60aa4e6c12aea5b6308014db01370983022100e583dad2651f1cc4769e533d5f398fc49856f1d8500b75bb1970dc893231ceba0121027ccca0e0a9c86180431340aa14038f22428b196a7a97f4c63b4afe142afb55e5ffffffff0338570900000000001976a9141b0247a0ec998ce4774193e6629708a5112f3cee88ac10980200000000001976a914a4547646725696134124fdf5b465438b940d43aa88acab45a101000000001976a91421cd98f4b804dc07f992559cb551158a078472f688ac00000000010000000187bbd96b486f78f8f80a6c19d60c50637c3c56a08d96d61e941fdf7b30aa9a9c010000006b483045022100aaf951927b28c66f32e26c1ba29563fe04ba245cd5f30af7887b3881c30914320220692f588b0a31f730a70e99cf143ef09b29b2216268f15f71d7f0255842a4fd65012103d850746fc0287d8550dfba1ec81c340d3c192d10fa9c918908fc913910667f01ffffffff0280969800000000001976a9140bccecf71c0b232d9f89dabc3f2b5c4ee7170e7b88ac00000000000000001976a91418751b7839491fb642b370760c5887037d30aefc88ac00000000" + }, + { + "description": "Medium number of transactions", + "hash": "0cccf0b884a20113ea2c53a381dacc92a68ae9db1cf86525eb259f0c00000000", + "id": "000000000c9f25eb2565f81cdbe98aa692ccda81a3532cea1301a284b8f0cc0c", + "version": 2, + "prevHash": "55388f8f9b326bd0b8e50fbe44c1903d4be14febcfad4dffa50c846c00000000", + "merkleRoot": "0c40f497466fe67a94dd9dd6851844097ec0e30656959ccc26efde12e119f770", + "timestamp": 1413392796, + "bits": 486604799, + "nonce": 1810450624, + "hex": "0200000055388f8f9b326bd0b8e50fbe44c1903d4be14febcfad4dffa50c846c000000000c40f497466fe67a94dd9dd6851844097ec0e30656959ccc26efde12e119f7709ca93e54ffff001dc048e96b6701000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0e0309990402db00062f503253482fffffffff0110801f9500000000232103a552615caaf1a5f824a47fffede99bc845d182d66daf5eba55a881bb596c6530ac000000000100000001d0fe6ff5a3cdd1ebaafec5489e7cffd5e15269fc06492a1b948bc3d503725621010000006b4830450221008dbd58f7d83223f7354e6c6c4201bf0723a107b2472294c51c0e8633df026ae502201a625322146f421562e139735ad960baf084335e7df445105838ac2d4b591af2012103c1d0ed4e6a4ed6a60e87021a98bbeb838cad9474fbc3bb45df1b2152f1ecc79dffffffff0200ac23fc060000001976a914b6c4f76334fa9c78ab90a136495366d4dfd6a8bb88ac40e179ca0d0000001976a9145001e1cceab6a9a5d29360c9e38e30010638437688ac000000000100000001e4dacf5f5d9856e5cc5503639f03030cb1325e5957d45fa018de00892379f680010000006a473044022075fe3a93ee65b18b1f2c7b32051a54b55e4497e8413da313b79f134bd562c5f4022002cfd7e72c81e7e7a4d7cc20e7a357ee9bf1ea837820968126d8880b98027bb8012103f5ae0228e424648dea53ed5e913ce2627d5b920ad4154816b0778a23dec0eb74ffffffff03781e0000000000001976a9149864a73e6d46d4a0ca10c693b450a868cd72a2a188ac781e00000000000047512103f5ae0228e424648dea53ed5e913ce2627d5b920ad4154816b0778a23dec0eb74211c434e5452505254590000000000000000000000010000002650170ae70000000052ae6821052a010000001976a914460e23da200cbaab6c3ced3f4e28bfc98a63be8288ac00000000010000000c9538dede63932e2fe2982c545ea6998718d1b5605090dd58519616b453db7e5e020000006b483045022100bbd78f28b3865fc2e4f9cd803a25d479fd0d9327a4ad96a99d1d47ca3f15094e022054173cba9e535f413552dacba5c1a50c52ac48466ae01638437716ae49d2ef960121037af2e06061b54cdfe3657bbc8496d69000b822e2db0c86ccbe376346a700b833ffffffffb59e4f1a7ebdbdff556b31302dd8a91a33d77b3957ae6ca72b489748afdbe9b2010000006b4830450221009c28408dd0164c3bd5c1b79540c916e851625826de9086fb8450e7bd439715b602207f90e0e898bd4c83c78cb4c1c9374e5257ace1ec854ffe8ac40198f2581bf74d0121037af2e06061b54cdfe3657bbc8496d69000b822e2db0c86ccbe376346a700b833ffffffff91dd9ba2209e55d744ddee54086dc30593841c27f1103afbbecc27fd39e0fa36010000006b483045022062dae6848b1a56a90f4ef78f820685c4502bf265db2df71217f9473115f56fd8022100eba07d1665f5d51a9aafeffe2f6fc8a14088d638480718bbfaafed724b6c4e050121037af2e06061b54cdfe3657bbc8496d69000b822e2db0c86ccbe376346a700b833ffffffff805e418a77068a845f6f9fc1b70e786cd69fb7ba0a377b732fac7468ac5f38fe010000006c493046022100a4bf26f55c6ab6ed7a93d2b2ea7d61fffef97240d2aa898bc1e66a23ee4e9c420221009b4d8e4105d7d7d0e518b8588f241c3768406747a16238d19ca3ac9be91a6e790121037af2e06061b54cdfe3657bbc8496d69000b822e2db0c86ccbe376346a700b833ffffffff352e5fee82e0bc6602f384cf70955516a7704408c0ffe1e9cf8653f83834df0a000000006b483045022009bf389f60989afc68ce3c95bbbb101cc459e8563560726a2ad96fb3d78c28e6022100ca37cb90c653202004ec55df1719714ecc2d93ac4a79e1d01ce6e28499edada00121037af2e06061b54cdfe3657bbc8496d69000b822e2db0c86ccbe376346a700b833ffffffff7dd3e25567c59fb11ff5d51e1df5721e791ce45177ffe1b03bde5f6f381530a6000000006c493046022100c1c4cd0032c8b492f8cb1cee933119ffe3af098ac9a83af6dff8beddb9f025770221009f397a74a31e2864b49ab52ba05df6a91434a4796aee8615854f5fab38645e330121037af2e06061b54cdfe3657bbc8496d69000b822e2db0c86ccbe376346a700b833ffffffff4f1dccb3840dc9b283f40bcedde013952b5f63de3cd55637bdb107cddde18e90010000006c493046022100acca38436760d65b1255001c8e28e4b56a465ae0ced00339dc4624a9ace4ad8e022100d41a6a2176b0448ac0a42bd1c9c66b1b1d2fffbe27aeb78815f3ebe66fd40bc60121037af2e06061b54cdfe3657bbc8496d69000b822e2db0c86ccbe376346a700b833ffffffff55f0f7040e86dc63abb338fe084aef5eb6212a94eee96511dd1e7dc1a0276e25000000006a4730440220249ba7396f8c00bde4c1175b6d23ddd6b0b316472a09a7c257d692305de7c97502206f8700e708155ea0ea7f21666b849c8c458555d6eba1f2efa33402619bef61a00121037af2e06061b54cdfe3657bbc8496d69000b822e2db0c86ccbe376346a700b833ffffffffad66cade8d08afb220204659a195c8b5d0ee3835d943b5f1e696ddbca24e6a46000000006c493046022100e7c30711e1824bbac03e4797fb989b1de1097868714933734fcbb5bf64861b5f022100892eddbb854a0415628a1b03f0adb45cb47b397cba03ff00536cab86c3f4c6e90121037af2e06061b54cdfe3657bbc8496d69000b822e2db0c86ccbe376346a700b833ffffffff6c2fd410949b573b0f9ecf05f93c1e5bd23333d25a219dcf59e9422049d04369000000006b483045022100f38eb4802ae149ca498b6d0f9398e1724d659f75f6ce953868d3761b0b1bc4c50220067a29a68bc1ac0c85489949e069ed336d33b0b5c437bfb85733dcf971362e0a0121037af2e06061b54cdfe3657bbc8496d69000b822e2db0c86ccbe376346a700b833ffffffff3d78262970277034a0d6fe72724be0901875e5a6998d52e9d086d4548adbb194000000006b483045022100d0f74329ebbc2377707143ac5f6a9c88e99cbd610fdd60a5d79229a69fadfaff022033c07ab93c23a41b9fc88a330e9c5a7b904f80085400ee21bdd217675de68cdb0121037af2e06061b54cdfe3657bbc8496d69000b822e2db0c86ccbe376346a700b833ffffffffa5ab972cc8ca1f10df69bc3ca1b05f68b23414b6f9efc486d8732a1ede6da29c000000006a47304402206d3d945226f41fee7f266c57089c96e1d75aa5c03675357617cd6781e38e5c3b022064f3f32221c26a9b4e33d3d78625210942c508d679c999e385d0d58fefa330350121037af2e06061b54cdfe3657bbc8496d69000b822e2db0c86ccbe376346a700b833ffffffff02009ce4a6000000001976a9149864a73e6d46d4a0ca10c693b450a868cd72a2a188ac20114e00000000001976a9146c39ee7c8f3a5ffa6121b0304a7a0de9d3d9a15288ac000000000100000009f09b59178c8494f17e829c5c06295492024cfc97cd5286435bf3ffc8c0420809020000006c493046022100f0cacaeded00fb8f4c84f999f2052b6c4ab6ec5b41e5ef6223beb7fb33adfc1a02210084a5ca4e74ee0ff13d1e6840f6cc8d210e6d2411cec6b336a968ab62bc6ebe04012103a4ab022465616cca59be2fabce9601fd8226b681b05d62cfd61a1653da0cb59dffffffff46b64a680c290c0eeddabc01d4de0dc736400b615324262348bfa0ba9fba249d000000006c4930460221008e6d830527e941d79a6fafc832a58173e7de92614275263b2746753f605a9d69022100baf17b1a870e0ee617aeffe726fe02a45c8fe19c09840b01851c64be2363fcd1012103a4ab022465616cca59be2fabce9601fd8226b681b05d62cfd61a1653da0cb59dfffffffffe5db21f92158086e8a062f5d458c8652895815b0b550f8847e1c544b59a205f000000006a473044022017e1890c25400567aedc52fd0c87eb1411bd19b47e44411438eef2c7cfffb6bb022077f93849ed0fad655b48ddbab1c0b0dc0c704184000bcef7b22b6303956a9571012103a4ab022465616cca59be2fabce9601fd8226b681b05d62cfd61a1653da0cb59dffffffff794c2f1b57dd6f54fe53bdb7220ec59b57f4114786e820355ecdb16f6d66dfbf000000006b483045022100b3cdad56c0451610957f474f1e21f30440cfbc4e8da8bf1e0e6b2df7df268f13022000f17c04a04d0b5f1daf4a11e78d581387d8371b690a435deafd9e5d425a57e4012103a4ab022465616cca59be2fabce9601fd8226b681b05d62cfd61a1653da0cb59dffffffff09b1e86b31fd61037056116b96fd7e09edf83c414808d50975cf307cf1859c53000000006a473044022012887712278e9ec9fe936bdab5b46fbe4aa7232a37201e6f4871949ac32b47470220427b8ca31f60d45dbe892d5dad571c3e9844827894d0816520ff59e15ff47e03012103a4ab022465616cca59be2fabce9601fd8226b681b05d62cfd61a1653da0cb59dffffffff559bda6c35d2bea892e02d1bd1a8ceb6cfb8ec6da4c5e64095aa6ece16da9fa2000000006b4830450220376f947fd4f6a34a6962af5f6a6b90aae828ff68a2312f0ed4abbdbb10311455022100ac3d53e42df05c62cf1874744a70d9f9a88a20cbb7fa9e3bc2f008fc0cf747a8012103a4ab022465616cca59be2fabce9601fd8226b681b05d62cfd61a1653da0cb59dffffffff3bb1a45cb905a09a7fa335455e4a2403bdd3e2e07aa9de8e858f2df282e841ae000000006b4830450220058a3cda301167c4030eb8793870f2fc8b62b17ccbf04e35e733e2db1d9a33410221009163811a84710f73d043e6c72b932d257f566760752d13abeddcda878f4bffc1012103a4ab022465616cca59be2fabce9601fd8226b681b05d62cfd61a1653da0cb59dffffffff73e038fd3ca63a87cb5f754e87151d5d4690005907d1fec8a585ac01269ed3ec000000006a47304402207c0f6cbce98127749da512ee7368dcb8973c5b5fe254a2b667a08636acda487f02202be9c33b4d49e6c3615116a0633cbd6fcfb8606dab768b66baae83069a6a5f79012103a4ab022465616cca59be2fabce9601fd8226b681b05d62cfd61a1653da0cb59dffffffff9538dede63932e2fe2982c545ea6998718d1b5605090dd58519616b453db7e5e000000006b483045022100e2212e01176bced817130de9031785a25e41fb8f40449ea4b8ff74ef59badaa102207458caeafa0d38b6e9f5d13c8bf95b16c7a075a2b0e6225b025954a2e71a72e2012103a4ab022465616cca59be2fabce9601fd8226b681b05d62cfd61a1653da0cb59dffffffff01d8defd09000000001976a9147f6394009ab59c0ff5f5cda27ddf2fddfae4167988ac000000000100000004cbaef29b3dadd2016ef2e3e646dc75ef97438bbd22ca1ccc775172519d22962a010000006a4730440220329e18e737bc060d43596d2326edb43ed445b89e7ecb19682a2a59f4c50589ef022005fef3ad6601070ccaff7bae890259b6a7754284a54d00598c2d46e0c0c4a1730121037dcd7819ce02547f96a93deaf7ea5c6244d79f13c21a855be672eb28af964c16ffffffffa634945cbb12e719a9997387202aa589a4318f8416804aac1674d14ee5ad1e1c000000006b483045022100ed0d21ee6bf1a288525f8beb39596f844201bc2287aa1993bc3d6a67d246d33602206130710bf1ef50655c9dfa1713f28eba4e03d3084e2d317802e1a15347fa647b0121037dcd7819ce02547f96a93deaf7ea5c6244d79f13c21a855be672eb28af964c16ffffffff1fe00780bc3e272f758f6892b7eebc58798e2c253685720e944c484657b045db000000006a47304402200df0e0c3bab3e8d9c784237fb81ffd7fffe1dd56b4d026e1f423a0658732836b0220209c70ef792bb0912b4cad295ea53075f224f8121d0204fce5e3ed16b40b39c10121039e3480be13ca86714045e1d28da159e0b3647d4bfb51dcebd8b33985a47a51c9ffffffff06d9254a072fb767bf04fbc22f76332826f5b63532b1d11daa731605ef7a54ed000000006a4730440220636fc2e4e2ab68dbfacce1bc0741d6f2106a581b65077a2e2e08e3536a0436a302201a2a1967b4e990832fc03d5647e6f2512d12dc99fdf6a0a5fc125787a5305e490121037dcd7819ce02547f96a93deaf7ea5c6244d79f13c21a855be672eb28af964c16ffffffff01e84a867d000000001976a9140744f9fd5a3b73a656694171412182a32f2971b788ac000000000100000001b57bd3ac69dcbc285633cb4cf12c765c019e51f910c631fc1c74ad660dc5e7b201000000da0048304502204aa9b763abccd2409d63c618381c87b844dd7e7db093e14e37c8301272cd0be9022100fb59b59c39c2c4314ff2f08d1ab3fed224caf141e29605973b45c24a2540ccd50147304402202bc760c7f98446731f6aa2bbc0e3913229724e228bf8acc7f42f16107be158290220336e90a92a5aa6cd0d386895f4cdf8ec9ad313d9a458b7ca9d9193fee92abf210147522102632178d046673c9729d828cfee388e121f497707f810c131e0d3fc0fe0bd66d62103a0951ec7d3a9da9de171617026442fcd30f34d66100fab539853b43f508787d452aeffffffff0240420f000000000017a9145bee13302ba496c3b2d3ccc89202fd76831228dc8768a4a6280100000017a9148ce5408cfeaddb7ccb2545ded41ef478109454848700000000010000000149c6ab2ace8003a6a1b5bf67e6e0fa897e1d2d2608172255f45bfb324532a09e01000000da004730440220214317cad717a1cd6baa040c52e697d4cd2b17ac4c85e18c6f258e412778bdb202205707884f09b4afb60063520004e8af694c6dbc7f6e111cf40ec0e0079041cb8601483045022100e2c9b18f26892a97b0008b8176b1d68f464994640d0208761e321390c4ce47b0022016e9d87c32d36e2add90a89abaa585eb164a756ded0136937e93fef3982ec5a70147522102632178d046673c9729d828cfee388e121f497707f810c131e0d3fc0fe0bd66d62103a0951ec7d3a9da9de171617026442fcd30f34d66100fab539853b43f508787d452aeffffffff0240420f000000000017a914c4c9057cfe2b9083b6533f51f747ae3c77c5c3da87405e97280100000017a9148ce5408cfeaddb7ccb2545ded41ef47810945484870000000001000000017a22d9a45dc865bee7642eb16c60ec0a6717d8554772e0640a705c12e549018501000000dc00483045022100839f66f053917dd03a84a949b4bb539f8fc06c95ac0aafc9e78f6cc040ab419f022001eac1a450ca2613d6ce47db4a548565bb70886dcc555b495b77ed2a7140fd5901493046022100d35e6c8f71eed0114f8787d078a17310483a36100eb6cd7c9a0fb7eefce4e0b5022100d7640cdf302734a1a8e2ea81f3bb6319652134234859725a4f16ba780495f4e00147522102632178d046673c9729d828cfee388e121f497707f810c131e0d3fc0fe0bd66d62103a0951ec7d3a9da9de171617026442fcd30f34d66100fab539853b43f508787d452aeffffffff0240420f000000000017a914d1e450d11ce446fc45d57001351842aa3b810cde8768a4a6280100000017a9148ce5408cfeaddb7ccb2545ded41ef4781094548487000000000100000001ac7c2b917cf245c06be4d59250bd433f367387c727b46616ad2594de8396105601000000dc00493046022100dc62b5a197b6bf7cc90d5d0f87fa97499e3bd456a7f8bdfef40df55d6a41b6d802210081db312d90ef3353aefdab24c58391c2337412b889a0160982e6312a9430c77001483045022100b5e89e670ad5aaa51ab412fbaa6718586fdb1b298fb3920ea90b4732c38063050220307721a03a54b4362abfdfc4d994b020e42a5b869c85fc089b9498d9d5ddc7550147522102632178d046673c9729d828cfee388e121f497707f810c131e0d3fc0fe0bd66d62103a0951ec7d3a9da9de171617026442fcd30f34d66100fab539853b43f508787d452aeffffffff0240420f000000000017a914a49d822d44e0ef8d20022843032de6bc420b2f5e8768a4a6280100000017a9148ce5408cfeaddb7ccb2545ded41ef47810945484870000000001000000018f30d8ad625b6605299ed2f66a6b28e2912b4391937f7329ce2424ed178d7a3101000000dc00483045022005bcbe24a3511f401be2f94f5ec9414050d7b5d4c8a9c262d6a438a080559c09022100f6c5f4422bd702ba4fdc403c342c0796a6c4b34b236f15362827636448f02a1c01493046022100aef5108bafd191e519bd633460d334bc3ce662f513fecaf3a8d48ee93c3c2c04022100df30ec6fecf165cc187194b82c534b102d62c66e97c21291d8192b5b00a3b2aa0147522102632178d046673c9729d828cfee388e121f497707f810c131e0d3fc0fe0bd66d62103a0951ec7d3a9da9de171617026442fcd30f34d66100fab539853b43f508787d452aeffffffff0240420f000000000017a9141ce06aa7fb344bfffa8252abb5100777a8baa5068768a4a6280100000017a9148ce5408cfeaddb7ccb2545ded41ef4781094548487000000000100000001054b9393e69e0d71bfd194558ec2182e38167e0a7256ceb0869ccacc3a5acf8d01000000dc0048304502207d21bee5933283111d4af205e3366e0ec8497e77553570737de9631c9554ef7f022100d284bd8ec48c58b879ac1e7f39ddc9e62ceae440636e9179602a666b6bd1a36d01493046022100af4c6387b30b0050af7ee32d7b900ba6e6051aaa41c876d54fbab95bde62d583022100c3893f9bea39253ec1997717ed50f4797522b41ca5732364d506be31342a82e40147522102632178d046673c9729d828cfee388e121f497707f810c131e0d3fc0fe0bd66d62103a0951ec7d3a9da9de171617026442fcd30f34d66100fab539853b43f508787d452aeffffffff0240420f000000000017a914edb50dd706eba79f59fed2980843190799abc74187405e97280100000017a9148ce5408cfeaddb7ccb2545ded41ef4781094548487000000000100000001dfbc1c58a49e3a41a6d3ac9891124f49631f8a590a8abf788e971a59a155352c01000000dc00493046022100e04d51aed1ceee5b3dd16977f32fcb0ecca486b15483b792ccbf72fd444e93ea022100c6c1ef150716597a57d5efb3859c3a519b6099f8fcd76fd88dee51d9de6deefd01483045022100a0adc0d0ee2f333a0377ed97f233944b5017be97869f1ad3c0a4f63b7583bff0022019f9033576e8a1557e8c341290e04378ce8c11b59451aa1c99b36875f04b4a760147522102632178d046673c9729d828cfee388e121f497707f810c131e0d3fc0fe0bd66d62103a0951ec7d3a9da9de171617026442fcd30f34d66100fab539853b43f508787d452aeffffffff0240420f000000000017a9148821c06dd300e0c5e715a7c4764bcff9725447778768a4a6280100000017a9148ce5408cfeaddb7ccb2545ded41ef47810945484870000000001000000016fd309077d56f72e8e479c41c20181edabef98b64a6b67412edbf24025d5a53a01000000db00483045022067574c4d4ab1a0c8ec6a8af7ece7ea7239612f447e06c7332049692694dc3bd0022100a04b4c0d3159c6b32087971d6ec50818d74136472cd671df9b3991ab72a127e101483045022011357745d9e0516b8d11f38959e1d16fb6135f43c52235c541bd50f0ab4af3e5022100a2b34b41173d7c2de383559264aaf67fdf4cb3a59de1774e809a9c362da72f2e0147522102632178d046673c9729d828cfee388e121f497707f810c131e0d3fc0fe0bd66d62103a0951ec7d3a9da9de171617026442fcd30f34d66100fab539853b43f508787d452aeffffffff0240420f000000000017a914765d0d258d3d0e6a59bfee9bfbb20596518b95d08768a4a6280100000017a9148ce5408cfeaddb7ccb2545ded41ef478109454848700000000010000000284f9f2e4e397cce58c844dcf5ec258dcae775d5bb6c8e1bb9bd0f53f127f7cea010000006b483045022100a6e72fc5e8a0e07853b9015874d8e40e6861185ba440fa4d79ffe925206133d5022006fb2812553d16e46559ba2260536e5809c9e8e420ea98033b85d91f3726a3ae012103e5481d0ff7e3d8ce9190647281f5475724b349444ada4bbe8faacebf92ee94f7fffffffff93a9a177906a0ef7ef0232ccc9a2933a99b3369b996c39f8afdff5a0b95188d000000006b483045022100dca4ecdf7c3449c434fa0cf32193afe1aced32c7491e9cdaac592a61bb3ad8180220143cfc0cf38b68531699bbb8a6723205376cecdd254e33711b73c7b5fac9db0d012103e5481d0ff7e3d8ce9190647281f5475724b349444ada4bbe8faacebf92ee94f7ffffffff020065cd1d000000001976a91472b06fb7c4eadb3c789cf719f7799baeca7e760788acbf874711000000001976a9140744f9fd5a3b73a656694171412182a32f2971b788ac00000000010000000240a16cc443545b1ae809148704511609154d4a9d8dbe31dcd54bdee791eecf7f010000006a47304402207efad34302f84d50c02836b684670f4a94fd13983e23351de3b29441234204d40220242f0522da0602877f7d25f71b8a261db67eee197bd991961714eed2a63121f4012103e5481d0ff7e3d8ce9190647281f5475724b349444ada4bbe8faacebf92ee94f7ffffffff12aacb63b4b92d7ed823e660c33ca21e68322a2c6e7aebbcf7b1213f545baca3010000006b483045022100ab60c956a9ba34c406833c9311ab09ef7970ffafcb06ffa8d8a92f31a74a983d02200eaebf14c4b5ba4c8f48a4771dfd8e03ec2d14ae11d8ad3d9d487a738946344a012103e5481d0ff7e3d8ce9190647281f5475724b349444ada4bbe8faacebf92ee94f7ffffffff020065cd1d000000001976a914ee2af5db7aa20a3ba7626a94cdb2da48c0701b4188acd753ee35000000001976a9140744f9fd5a3b73a656694171412182a32f2971b788ac0000000001000000015adb3c14dbd5a07721af2b787c85a95c4cb133dd17f9063dc129ac849067813b01000000dc00483045022100c8fad8eece220e207e2dbdda58b8952a1dbc704d65665af4de527fb01b3734d60220085531162e2448ac58c769c60187a013aef46d26288dd97113e71e0b888ebd3401493046022100a55e1af65f9cdcfccf05c15e536c3d491831a3eda3fba686c7eeaec276f1d3cd022100ca4e818f811d3635710226623c892c0b31cd7740b8e74d4a0d7a59dfc1a76c0b0147522102632178d046673c9729d828cfee388e121f497707f810c131e0d3fc0fe0bd66d62103a0951ec7d3a9da9de171617026442fcd30f34d66100fab539853b43f508787d452aeffffffff0240420f000000000017a914c07e34dff7613b421fd17ae714ac2915cdcc54de8780a5e2ce0000000017a9148ce5408cfeaddb7ccb2545ded41ef47810945484870000000001000000010b121a3f08fbdf2e8ba32c5f60953eecf80dd67e121fb14d4209ffa0c4c2ec0f010000008c493046022100875bf2629f2cf6acdc2302f657ba7aca44eddd5fe37dd754b8a6fba8c47f4fd7022100bfcf0ed84925391c96c75caa5a914f786430bdc29ded3634e043aff339fa08ee0141040cfa3dfb357bdff37c8748c7771e173453da5d7caa32972ab2f5c888fff5bbaeb5fc812b473bf808206930fade81ef4e373e60039886b51022ce68902d96ef70ffffffff02a0860100000000001976a914db2045e8f7c6327732dd242c865e0e418910644188ac9162c9a80a0000001976a91461b469ada61f37c620010912a9d5d56646015f1688ac0000000001000000017419cb2cdb61340f43882c2e034bcc919fcc742444ec41406e72f7897ce64aa0000000006a47304402205144fb84c7af6981c727ee6866ddc9a37a5ed49557dcd1042721ec2d78b7c71f02207968261f681fc3a0531bebf366cb92c88e454325a4a2c98cf3dd66dacf0363920121037af2e06061b54cdfe3657bbc8496d69000b822e2db0c86ccbe376346a700b833ffffffff03781e0000000000001976a9149864a73e6d46d4a0ca10c693b450a868cd72a2a188ac781e000000000000475121037af2e06061b54cdfe3657bbc8496d69000b822e2db0c86ccbe376346a700b833211c434e545250525459000000000000000000000001000000dd0aff99340000000052ae80329800000000001976a9146c39ee7c8f3a5ffa6121b0304a7a0de9d3d9a15288ac000000000100000001b4702c4f28336cff691596e14170507ed9d64b732cf519a61287acb2edd773c2010000006a4730440220050fc133e55a1bd033b84931e1b62569f98737f099593c784609e03bacf231a1022029afb388fbe52e7819d9dac0d73cd0ed2035d9f917bae304ff224ce144198f40012103e86cf84b12237867a327cfa3f34cee931db055a06312b4bef003bb784b4ca74fffffffff0200688909000000001976a91449eee8f4d96e60d8bbfcc9f82747c4496ce33b9888ac406c026c030000001976a91443dd90e9647903ea668e4ea3f5feb8469630d5a188ac000000000100000001d22f69c205c88c4f52f7e148ca579f5cb3a93cc5e52689594e0f33306ac8694b000000006a473044022021fe708e2fc6d0dd2bbf7d022860f5e8c68e6b1fd6306fc3079360a48afbbd6d02203fd0d67f89e3cced810028ee8b48c47b9078438e99b0bfb4386f57afe9d3d23e012102ddda164bba2b1435b04a8205ca5eefb5115270e6d19f6daf8960211b17df086cffffffff0100e1f505000000001976a914eba91c185a67b207f461c7661ff36890fd43e38888ac0000000001000000017aaef574e423163a2fffa556544bd9483ba415e023d56bb40a9e3a9f805e934e000000006b4830450221008db13e8a38e3c10b41f740eff8b5f5789c1c134d68d97cf4b3ab1fd72d3c8a76022061972edc83147ac6cce1d03272a0afbcceec3fe840b43170e6acd427d6423653012103c117f27c03a87e09ae1b0c6f49d2ab79c1c199580a1d0ff2fd682e227a60c427ffffffff0100e1f505000000001976a914ebf067bf6053b3834f6154fecdf31ba28bcb0e7e88ac000000000100000001f0ba13339fb4d5f722a02bb31383427014d967cca259fc51ba6b1209129d09eb000000006a47304402201382d8ed4c3948a33da4131910d060b79e74afd2d890a9f99bcd5358bb6d5f6802204720beb06bb6f1a51528941e587d547e2efbe0b3f96c8d19edf71ca09dddff0c01210222e624e056d0de15bb97e28c7fb8c149dee686f3027f17a032617152c7676d09ffffffff0100e1f505000000001976a9146d2276d2ff9bdc3e3da97c2761bdac3d3104241388ac0000000001000000010df8c6a9c06786b269b6db1dcff3b766fbb13532352d48ae175245594178f949000000006a47304402204715e1ba8c869d7574cd4f4ad4ffe7eda44b3e4c11640e03dbcdf538afcb2bc80220614d5a17c60e84e1bf38e0d2d2a4c7fef7a91a2a42466bc8cbfcf0b48a650761012102c9bf3691bbd97c2a6bcb8610f547cac3ee169f5d59e947069d85f2f35df1a6beffffffff0100e1f505000000001976a9140693c924d0650fd0cbe51d87de977faf13c6a29088ac000000000100000003bd698ace3161ce9e9bd493c24dd43e78fb61f079c4aa047932244d8c9948c2b8010000006b48304502206e82747317df2734e5f2c74514c575ddb7d79ec928649fd1734ae11d53703c4402210094f7f303c2ead5b0fb33b9d7663e4a0bc2711390ac5b8d742bf0bc747b16cf100121025c25273e23fc94e9bdef68279ce4b2eda49dbf09635af064b8bc89061c2d334cffffffff06d9254a072fb767bf04fbc22f76332826f5b63532b1d11daa731605ef7a54ed010000006c493046022100a67df7840a93b3f19d8fbad4383557585017fcff853b488292c626ed155e3b350221009b3aecea5f29b7420ec53b0d119996a569e421a752b1fe30efdebb9f158b498d0121022ee4f8d242ad07274e4deccf0523dcbf6da2e55cdac0f92e8a79e40c721a7350ffffffffcbaef29b3dadd2016ef2e3e646dc75ef97438bbd22ca1ccc775172519d22962a000000006b483045022045df4b9c83159061ba910a07886a5eb312fd244faa963cddf6dd5af20b685bf3022100dc29ca5952f74934d589b838bf785042e78b638756fc92e71d3ef8e4bec8aa8c0121039b1a29bea54d0482295ebef1d023c686dc156722bba16bf317b38636bb1717b2ffffffff01a834f20d000000001976a9140744f9fd5a3b73a656694171412182a32f2971b788ac00000000010000000136e0ee46b394e3e665ad264d6b0e813f510ebd49c9cf4ee9fea66394a3cdd516000000006a4730440220547205d8dc30ae833b82cf0acc2c76079e9344ceea410bef3dba06b96ae2f0fc02206a3b175b1229079b64892d99bccffbe8cccf9ce634a11e6cdccb2d740a89c938012102c6052392f63433a8874a85e632960df44f901bbcb81e75e91c502eafe8437b88ffffffff0239660902000000001976a914e31b53d30d08216f5b7e8b8fb153f424f0cf2c2d88ac7493f901000000001976a914474a84925c770102eaa92c509e95812507f3f7d188ac0000000001000000015559373139aeb2fa33fcf78ba3e571663b20486cfb7d896528f97091975c5605000000006b4830450221009112b1958627956e05b5468f60931317b83dccdb9c91a195f49ceac2fd42bba7022005b57df3119060ffe82d478d834dc3d6b4c743787aee2801dc3cc504578ef3af0121020f4f9616ef316d44aed4f0fa971cf32e455b8f82e90a7a9c54dfe37d5c3ec135ffffffff02c9658b01000000001976a914066d59ecda788373e3206282d5255777b92a893188ac69256002000000001976a91418db620c4ec4593d76fdc6d4a30a531d08ca959a88ac00000000010000000262ee525e057cd877f7ceca8b3d3dcbd977fcfc993e6c1edb8577e707b4b5f61f000000006a4730440220292a063728cadfadd7cba2599cf5cd0460e9295fac92f50fe73da9c1c949979d022079794d70e84787a720b535cb1122cdf8c76486c7466ea6d12032784cd2776e230121030fdb4faabae84b19a7b192ed101ea5c8bbe5067acef3caf3553d23d1328fe74bffffffff2eae185b608e4598ee77e575f2ed39cd1cea745549aaf0288bc4e9cf696abeaf000000006b483045022100d338cf96525cf590a9f67046bb669f0636614b2ed9b85f07ae4db15c8d03ad8b022001ccf28b1733b03a218a5a6f1aeba5e35f424e39578730e50d927a574a6835c2012103612de64d269ed8a44f4fd3b5584ae7e5620909b5991d20b534525ed1b54ebc1cffffffff0220c15500000000001976a914f091a6cb3afa861e0dea0139dd26c60ea21cb8f388ac50690f00000000001976a914e8255ec318662f530c71ecb9ec1bfdb98ae7e45088ac0000000001000000016ad4fa5b8f91a88aed9fe60c2954b839d0d3f8345deb5f31ca6abb66e0213d3e000000006b48304502210093612fd6c14ff3f01f88e1f96d579b20af951c38fc3f96334c7f55a9f2df2d2b02201279f695bcd933bc2bd89a6849111a9987058a24cdc11b2838e0ebfdd702c9a3012103d850746fc0287d8550dfba1ec81c340d3c192d10fa9c918908fc913910667f01ffffffff0280969800000000001976a91421620cd6d05d98cd15d2c7957d3c07bcf9731f8d88ac80969800000000001976a91418751b7839491fb642b370760c5887037d30aefc88ac00000000010000000214b9b3fd8986001e9dffafcbd08b5cf96d893b326eca48e1a6e600875ba7c1d6000000006a4730440220771ac11af98c9f3356c72c57e6d7b61d0ec6b1fd472c879e72f3b65adef3bd9c022006184aadbd78eb46494771b7a8636273c4a43b2369e57a921fe1966a4f684fd9012103e4b47eb68ccad5cf770836ba84d7450767432eda7ad62aea7c593db7a133cea8fffffffff53570346a73aa9ceed9ccc525985b06f412babf2d7441e52db9db6cc83551d2610000006b483045022100a67e312945d4b56a559352c743e4a981aba2856469697dc629db85f0e4f2e54302206ed43173e5e65af5df9e55303ac9689d63f458d9554e4fd37035aa8547736ef501210253244eaa74d1b79c722e062a98a7ca2be46e7a26edd948b5b5b141c5f1d15475ffffffff0296ef0800000000001976a91414da5dde04bc5bf7358984bcb227ed431419ef4d88acaa520600000000001976a91491859a64a061038dee95e6be293c7a9849db22ff88ac000000000100000002a186000a255c2cf7b6ccb4ec2774365629a6687c55643387e89ada264c5fd65c000000006a47304402203e1d65edfa06295a66899d5f3c5494fbe109c473afdb699ecda484e70f77e1e0022063a0c24b65b5c54aaaaac006161ac77bb4cfc402fe955654e993c1bb39e82fc0012102a7fc839c0427d33a1158f5b1d60d6d88fe6a790a838c6255cdb15f1a417af601ffffffffe0f8c952e4dfe597864519f34b292bb4b0094978b8f7bcbf0066d55b5198e934570000006a473044022008d5f4cca8362a1735a62e6ca1c95626fc9e9add2bbd24ec19d3eadca1791c9f02205ca558bd7cc3131ca6a4f89a86f78e83298dc98bc7a3ce1b109d020a398dffa1012103f9b986f00ef7410d06f50eaf324deb4513f83079ab7a42acdf470c12bb078adaffffffff02908e0800000000001976a9147605eab76b8020350291bd3d22d8999d991a757688acb0b30600000000001976a91414da5dde04bc5bf7358984bcb227ed431419ef4d88ac0000000001000000010bb8f053cdf88a3048c9dc92f014cecf514e1841c5cbc6eb21218ffe60b4706c010000006a473044022071eb972e731146779d208e378a521e098bd063143ddd3b10271f1d1a94e934ef022068a48334b2a92c52e97de4c1a41acf777ad68fa76fd2917cbe32eefcd057ae2a012103f29b04e608b2e2247969d0488dfec01a2ad4b83ba7bbf3c5ba4460c5782acf41ffffffff0210270000000000001976a91417a149e8da478715292c372a8dee19453f72293888ac605af405000000001976a914dae4b5b55ef3219a9a79cd0143379e73ad3185e288ac00000000010000000195fb54d349c2d732001dd6f3b373cd3880e28c9e421af34801f1a6d2b88a41a8010000006b483045022100cab333e2c20a2003b410a2f013800b3107fdf0d6c42bd3235369beabae73ed6102205ce3c234ff199a2d742e83a00420a3f071d68f6732a6dfc141e2b86f8b988afe012103f54d81de4eec9215803c88d4097bd6990523e5a69ca6c20aff080f94d8925f39ffffffff023074f105000000001976a91400a469197c35da3fa892ff221c22840c2d15ce8788ac10270000000000001976a914f1c5145f86267d64199a22e2c420d022eb56575d88ac00000000010000000136e0ee46b394e3e665ad264d6b0e813f510ebd49c9cf4ee9fea66394a3cdd516010000006b4830450221008eddddd407132af0fdebbdd756db68f3440a3d41047bce5fd30a08a6fe5adbb502200825ca373a426ac1471c44eeebd29f10f1459d949a3cea09c7b95d1ff269cf3e012103c70fe3608fd1a6cfb79a66552a2ead99a3d20716ac20b8988ca87636f9c422a8ffffffff02a0860100000000001976a9140172c2761cef7a82f63cd1bd9921306d66fbb93d88ac9312f101000000001976a91424eb20998407878b5d704e54a8c0ace7b1be0eb888ac0000000001000000014fb19d79fb214b86b0fb0a6577c4250b2cba5358a14d0afd7acfdd7b2b055bfa010000006a47304402200692f6d951f2cd66c7a0d9aaa530fd6a07689c65cb5cd5b6d04faaddf75408bb02201e9c0e192ea7d59e1ac9ec2be449f3706fb0f9227c743defed62b3ba5b47f3880121021225b15301c1facaacac0104d6bd05a33046b2ba4731edaa565da41d7b4c2715ffffffff02f0490200000000001976a9142591497f043482f83f30aeeac043291dffebbd0188acfd03ea01000000001976a91457207e70a8aabb9479c5b4716fdbdf0c4238a9b088ac000000000100000001600f3a2aef32b8275f72e356af3cb94ac371c2d40464f899cef86390282f16f1000000006a4730440220521bda4b96e98e37a734ea9ae79c22f2da9b2ba4b1888d1af544b1172ba13b670220609121425bc54c61390bc9a73c92ef4c8e041c5da5d01de72d56d95340845e20012102f543b68df48df748df773d7c6359cf1ff90f81552c513572602733408c3f27eeffffffff0290f60e00000000001976a914b00e35e1b56764175f33e7caa14ccf14f94b5f3088accb85dc01000000001976a91478b58ed969d5df656b5a2bc42c8355656dd354ed88ac000000000100000001287a6ba0a3a61b4132b374c62ebf576790b80c1679accd0b8a655fe2daf9f282010000006a4730440220325c6e862d0c076cefdbe9bc768be8c417a63289aa12c85218e574a979577b3f022042288b09b904764e16019ce51cd3ee9bfbf9e20349f458f27319ae4df67d22b5012103c8fad558718b44e388695b2969d17cf7daaa1fb872ba072d0dd4b97fa1c8b594ffffffff0253c6a501000000001976a914aa2a1b4b7cb551c663c91280e83b11ef25bf488b88ac40420f00000000001976a914fde75662f6e3aaec042bf3670345e070286b6e5b88ac00000000010000000189f749d3a7c6ef70b549a3f7502bdeadc3ab0c98bf80b716d961bf3830150012010000006b483045022100fe8ed68b63c84e3a251f8717caeae551117f52187cf6d591f1d448ce68a57fa202206182a016acaa14438bb25a87345b55340098f4ed6d7833b51f1b2364ae2b328e012103cc3e7ded16d0d2a09b40faf7ba90da7ee77f71ed8086b6bb8d5a97ed287740a9ffffffff026e3b9401000000001976a914a4a2e8d23729124e04dd84b6abfeb53f1a3e22d588ac20a10700000000001976a914b5fdc18f8d82cf96dfea700f7a4dfc434a07fd1788ac00000000010000000129160bfe0aad2936e867fb84f49b484ce6d0c8831cfa88d214641acbbb0ed3a2000000006b483045022100fe49582a0906dc7ae9a167a67249ab22e54b91c4e08335e2924724afafb415df02201f557f540f20ecae35a06f40a1bea9ee7563bfb5f9738fe56bb2673e11690af5012102fab3570778a8b802b52d2fdb583f4d800bc0321b707a3b5b2e78cb4167da4e11ffffffff029df58001000000001976a914cfb467700301f9406e82047b49043907e9b9036b88ac20a10700000000001976a914b5fdc18f8d82cf96dfea700f7a4dfc434a07fd1788ac0000000001000000010fcf7953a6e8f88c430c24b630a61d5a49181afe2e767ce76119ade9fe07c231000000006a47304402201bdc04089297f83336c6e75db11eda95e417d816566f8b89a692f032127d771c02204cfd078b03f01916b5104c44e33067c38338c15e7f243c45e0c8204553f096c10121039b790ac2e936f461b9aa4c93ad40aaf715c800e35c7280f8f71ad222c4aec018ffffffff0253607101000000001976a9145ffd1ab701aaf92210c9125a3e8631505db4ce2088ac20a10700000000001976a914a54a4680732fb4ab0a5893690f8606cbe24fbc5588ac000000000100000001918ca179557f97ca12aadbfb9ba1c18dee32afac64e5ea292790b468c7edabde000000006a47304402206ecdde1cb4c048911c1f271e60edd7d2892181fe262b50b12cf55971e288ee3202205cdf0b06ed964700860c6c266004a9ee5199d80835145c6f153e78056996a1bf012102c28e3e4e6e25d5d7590f4a7ce318c32c0a98c380aab803873a9628c7e0d5c8f4ffffffff025b227000000000001976a9142baef9004079d373df20c22b0dfe40017433db0c88ac154d2800000000001976a914ec0e8c940a0d3c9a786268e2dcae2e3d641b632288ac000000000100000001d9fc92b02970e558021a15e5f6597ed3075a3972705db1990e6eb22d8f21d8fc010000006b483045022100828a4eac23959ec1574f060a7207161e49e4267807bc9744bcc190b5d8caf39a022007c8eb27f14b7f5670f5611a9e6f30a8a206763eccee0b7a5331d0ad552befb001210317f6cc241d8f0a0123378475e52267b6f6870f134b66e9135ecdbde79df4dce5ffffffff0240cc5705000000001976a914b29f68300e3e489fd301a7a8e01d25ea6f1320f588ac10270000000000001976a914f1c5145f86267d64199a22e2c420d022eb56575d88ac000000000100000002d04ddebd5d7076fb60a39b977208d67fefa431a91f47628304f913605c25a68c000000006a473044022050fd6cde2008b312900692a3928d4bcf1b7d150c22fc77598752a75b94d5dbc0022045715e024c59b5060e8246fbd0b9e86ff2408a6cf4b79695a457d131543afa87012103b5ebafa7223a8b4dcebf89d14fba39f5393ecd839e02e09c691180d29974795cffffffffe0f8c952e4dfe597864519f34b292bb4b0094978b8f7bcbf0066d55b5198e934520000006b483045022100a2e0690726c6a8f45a3d4685676c81a43deae953e0a3069e8045cdebafa1fbfa02206327b98b5da80b494830b5654f6844878a7ea06161279fa879cab1d1b337f01d012103f9b986f00ef7410d06f50eaf324deb4513f83079ab7a42acdf470c12bb078adaffffffff02b5a20100000000001976a914665f2d69bd3aeff8581cf66e2764b849a63ab81f88accb770400000000001976a91414da5dde04bc5bf7358984bcb227ed431419ef4d88ac000000000100000001048f282287120e94e0a87bcb4c4c4c07c8c2f8f6e09ced38d861e0331789f14b000000006b48304502202fd11730a933bf595803f10b8c634aea441263bff45200fe29982ee663c89acd022100f51c76447481326553925c1becd5eb86c0fae7ec2fd2ebf7b93ab6ad86a9ce5501210234eece87d535b56c12df83f70e479fc409957df7929b0c4b08eabaacde1b0a82ffffffff0240420f00000000001976a914153a6b7f423b4e51c69039635260eedfe2b584e088ac307eae02000000001976a9145481ed9fdde635476f602b9c9816fe2ce6d5c28588ac000000000100000002e33ed9cab58586d309378728d5644c1913b919c7a1b8675a603f48d2df256631010000006a4730440220015a7513ef719c26f32c97ecde8d570ab3d2196add683430d64a8302dfec8bc002200d9a65d86419813df213a95070731412b6fb2615aab1a495c256046ee40f6a00012102073132dc08c82ec0b01dd90494b27311cc8062cdd543e1ccd25ea9a042e10e6cffffffffe0f8c952e4dfe597864519f34b292bb4b0094978b8f7bcbf0066d55b5198e934100000006a47304402207d850910a0a6766f59338a4d9bf7c735a54c1ab0b6355bac08e668260a3e85f90220586e1043c5a68c7ffe35271cee0aee1b20272e6e722f18f0f5d532b4b027b73f012103f9b986f00ef7410d06f50eaf324deb4513f83079ab7a42acdf470c12bb078adaffffffff023aa00300000000001976a9146ac3dedcb939dd161cf1cbb79d476e54acf811fb88ac7e111400000000001976a914c3f5faffd3dc6f58e0accdc372e14beaa16e3d3088ac00000000010000000241504c537ef2c3e8a8e967d1c560bbadf257c6287fb248c99ea8c87d7d93973a000000006b48304502210094742457c5afa6e809e502e120bfdce94df70a84346916a1d3058c1ed722ca9202201cedf2acc9b309a20c2c4c6f5ebb3fdbae48a81002de7fc4379ef327af53db74012103f0260d1e5394ecaacbeffbd3081d48ad882a83dd1f6e2911a494af3760a879ceffffffffe0f8c952e4dfe597864519f34b292bb4b0094978b8f7bcbf0066d55b5198e934180000006a47304402200cfa32de673d5e505348b0132f09dd3932a0d6950d64a635726808bf27cd13f102200f0985fe3a11f4c0d80bdb8f58e7ed182b154f611162e11cae353778c875b6fa012103f9b986f00ef7410d06f50eaf324deb4513f83079ab7a42acdf470c12bb078adaffffffff02dac40300000000001976a914b18e3c27cda57acac294c7dcd0fa4af5f03a143888ac51401400000000001976a91479450defec6479b2f87fce094c215749528474b688ac00000000010000000272bd1b874963895687dcd5858f9e623c5274e662a95ddb5965cbdeae95028ec1000000006a473044022049f4841b790589647ad484b55e5c8c2c52be4900e492b7e38b1aa2532a76571202200ebaf2ea0a81eca80de1664d9a24c4f38471bebefba0308e380ceb4e872d6a980121022467cc3e4628dbb9ae6fefb534db67c6856adf85bd9843596260b16f1a394fe8ffffffffe90d2e51a4374b3975923f4f1acc37aff14288a40a4593f79742a382cb81465f590000006b483045022100c92e92776ac2884d6543faf2715f8914cc1950afc22e369a752e731fd6ae65ae022078234809f091f7ea344e54c3e77c6397275a1e7d50131991e3b36825648b5235012103f1b4d21e0b344248185408c1af96bdf05f8ee8921dfe400fb1873add6c6e1cadffffffff020dbf0300000000001976a914a642ba657b0dc95190d7067ff2539594f118d01988ac111b1400000000001976a914a2f8422e68f88d6e5fefd3c3c67b6ce8ed4f8e7788ac000000000100000001204a4dc8fed37cfa57e18b042563b311ebe380e6961c65babd7f3c782d0df3fa000000006b483045022100bdb2b7f8c4391a3567d5d5723188d5f3e385aed4c0681e88a0565fa49401ff5f02202153542de0c016fc041cf0078186ce0014997b0134a031d2c4756fb0e1118f4c012103b9d19bda4f288d75fd43add98605285948554aa94c45a3a5e011ad2ffcae07c0ffffffff0240420f00000000001976a91446840e50aaf1b883089b042e4273fbb5ce2b9c4f88acfce4f101000000001976a91454cb919bcc7807e505b1e4ccad4117c4fadc316c88ac000000000100000001e9acf2718a51f0342117707862a54d4e88dc4687602f10c10c7349a7f0776358010000006b483045022100e80e54812e6f2861bfbf1ac81e7bf3ee6dadfc29a66a90ebaf8a5697e030e4be02203be9e88ddf7c60a350468fc45cdd6bd0b282520720f9c4e920e61e7c6d0509ac012102736a6327cc55da03a66dc9191f05f75f79aeb478a45690f5073f3fa6d25f8df2ffffffff029ce2c201000000001976a9146de811dd2dcf0b5bb946a9674a55a65e97fbe36388ac40420f00000000001976a914a27ce59535bf7df916438477091db364acf7497b88ac000000000100000001cdf2b8d3a53caf43e95573519e367727afaa6b3ca56b81f488a672fe3530dfa9000000006b483045022100c17f5d83727c41cc5e1d6f344543d3608af8e0e601d9e58a6662471bf6691b6d02206ab1fc3af73117c68e3a45298f5eca826abdffc3bd2e45051518e35e2800c14e01210206b70a4bc82d2440036bd60dbac15f86d37c7371d59b7b319bd6070808bde174ffffffff010e4223000000000017a91420a28c44599f373d0634cfc252a687aaf173921e87000000000100000001d1ee1f739f2fb1858c4f3718a6d921e907b4f5e297d824f154ee439c4e928f20000000006a473044022067fbda22575e4350242bc38ad56b96c2f9e218110ff43b5f9ed91cd3b9d97ff5022052ff6569053c08c28db9380abe0454e0210c0b97d86ddb8c1fed6543011c5bc3012103bd9ec3c65e09ab45889e362be3272c407c12c643b3eae3b2ac811d7ad30a1025ffffffff0140420f00000000001976a914276c786a1b5b683e67ca92a7d9f80ddf7a65c90c88ac0000000001000000013fd68ae235d1348d4f53445593744a8983dfdda5381ef8f38597097a27a7ade4010000006a47304402200c4160c7a8c145ac41502326071938cc6c5a4b1504f22c6e0687f7ce7686ef3b022014aea6e911fe99eb7c45b8f3408666713c768987e4605e5e0ce14d75b79e5d1c012103addc0d975ec1447c0dd7c0c5eccb3e090b4da22ffea512b1dd1e52ae45491deeffffffff0271991900000000001976a914b9c5b763d69ac9423bde2514c9c84ea206d45a5188ac7ea10200000000001976a9140df5cdbd67f672f755e2610ce35b574f630f0d0188ac0000000001000000014967704cecca8b905658a6049da6e289b6a1ab3b801766d2ec36378257636f3c010000006a473044022010b961d5a7777595b5237eaf85ded87fa59fad12ee150b35143f9edd9b6097de02204af3dd8d2510bfc7e52cca75612054df417e6754f3b16c023551de5680438fc6012103b403c42ab014e8f24bd8f87bdfa685f3a18efb61a9c1a29992feb259d6b22c5bffffffff0230c80700000000001976a914062ba22c85d9460833e8ffb67d4119c95428136688acdc997d05000000001976a91414c402f2c30b8d80c3719d090f7f8a2316f103e688ac000000000100000001deb965b7a5839de16d9c0142838205b623cc3b8bd7e3cb4b1851330bca7150b4010000006a47304402207120969cc910ef2dade86d1a705e1b7bde0de5837879b4805acdf9226e910530022027807755fbecc227f50613559a528addd58f6c9e9c1ea73b6b4e2ebcce0e0a99012102666ffbffcd2c9987e267dc625cb91bdede1636b2bb744dfb0b5e93e11703d77cffffffff0240420f00000000001976a914145ea67c48eb66b0156b13d2305034e27163f53388ac08c43601000000001976a914f3972c052c64a5b3f05b02c4acad74be76c908c588ac000000000100000001294947ed1ef282bb674293138130056dfb4078fa0b82d34ec1e3dabb88fa7430000000006b483045022100957129e2616f81701f469e80bc264076ffcf7ed6ce1f69c736580c1404095b1f0220549d845704eb8eef6f1a0e77b14d34319b930a82aa7c4320ab48a347e2599254012103eb6cbbcf67da9fd27ebe2ef28b94b0ed0e9006f13a3d4b5948ecdc7c2c8d6d60ffffffff026b9e7b01000000001976a914e5ab2575d010b9648f1a54c9cd8f3fb75785728488ac40420f00000000001976a914dd903bb6103c60287e830caaa39295240e90528e88ac000000000100000001a409a263f66a4d28c5be76d8221db433c1de8616555b1847c446eecfcac37ef8010000006b483045022100ba4592ef246a1eccc09a5c7c8a2468e56dea93561661feaa1303fce25c18eb6702206ed78d93cf92fc9315460a0c059ea60f4d3fdf2755773716b80cab5bb44ad170012103f7f2362d11e2b082387b6802a773adfa3c252cb21485c85f737e7cf62e95249affffffff02905f0100000000001976a914a55e832067cdd5c1e1e59288afd7b2b0f59be37a88ac30629500000000001976a914efe4eebd29f5b7a86492584993e05fac65ed6f5688ac000000000100000001e84d537ccdddbe145c8f872abeb9461067426cb6b6eff86a3859b18bb9d05d5f000000006c493046022100810396dbef50bf434b1ab80feb0b2d46f4180da583302f1324de61e5c25a0d9d0221009f84dd37b27f6663937088034a2b726ba6effc107396653270849a91ab44a632012103166654d5cbdc31d65b81e34cf0631a5a0a29019841b20cbf9ea599e049e5f0b2ffffffff02b8a51000000000001976a914630d1618b712c0404d6068985b80f6c1cf86cbd888ac28230000000000001976a91490fb956fb515c4a1695851d083b92142426d13b488ac000000000100000001d7134cb635b71a79f0b9cfdcac9ef9d6832115864c04683b7c577574c168507f010000008c493046022100a63d1a3a217243f50487f1a86c0217676bf846be2fa59bdee6f5f81dd966a143022100a331c9734918d427167514628a84e0d4d9c2899a3e4288ba188ae78abf1530e40141040cfa3dfb357bdff37c8748c7771e173453da5d7caa32972ab2f5c888fff5bbaeb5fc812b473bf808206930fade81ef4e373e60039886b51022ce68902d96ef70ffffffff02204e00000000000017a91422c4270d877177c1dd33037fb5b7bb3fd76cbb8b8761edc8a80a0000001976a91461b469ada61f37c620010912a9d5d56646015f1688ac000000000100000001d0d7fa24b502e8a08472047696daf53d08807494734fd8459b67eeebfe13f63b010000008b483045022100ac7686b9768ef38d86123e7416ec05558795b2aa1603087f364a433bf9515e4002204656b488276b84cd2c05fdc775b3daf890d4d69d5ce62383580e41b78a6d3bcf0141040cfa3dfb357bdff37c8748c7771e173453da5d7caa32972ab2f5c888fff5bbaeb5fc812b473bf808206930fade81ef4e373e60039886b51022ce68902d96ef70ffffffff02204e00000000000017a91422c4270d877177c1dd33037fb5b7bb3fd76cbb8b873178c8a80a0000001976a91461b469ada61f37c620010912a9d5d56646015f1688ac000000000100000001e1798c22f27e5ba948fe9c5e4de74046191b8723309063ef60f8198bdf67930c010000008b483045022022d734a8eeffdaebd54f42586679e75b428c6f4f230d3f587d3f9019ac25f4350221008efe331e7b69696bab645ac85c305b9f9d2b35419e59c2d0dc89ab12f4b49daf0141040cfa3dfb357bdff37c8748c7771e173453da5d7caa32972ab2f5c888fff5bbaeb5fc812b473bf808206930fade81ef4e373e60039886b51022ce68902d96ef70ffffffff02a0860100000000001976a9146f743423659b1a2b5b52bd3ffaa2f6c30c9b6cc288ac81cac6a80a0000001976a91461b469ada61f37c620010912a9d5d56646015f1688ac0000000001000000019347ffb287b7205e817f096b0d4d424dc54bcfd9cf7412b289090cdc16d78eda010000008b483045022100c5537059373bc3cd04bf559970dc25d83bb4338c2e0f2b321b412873ae0b64430220343358577015b688515b4f950f2d5f8ea4053615558e11552929a0552a4bc3210141040cfa3dfb357bdff37c8748c7771e173453da5d7caa32972ab2f5c888fff5bbaeb5fc812b473bf808206930fade81ef4e373e60039886b51022ce68902d96ef70ffffffff02a0860100000000001976a914ce5da921afe91686f53affbea099dfc7626a64e688acd11cc5a80a0000001976a91461b469ada61f37c620010912a9d5d56646015f1688ac000000000100000001d66a7959dbbcdd5a0b2e089008452227074013fa4030ca947ee74d54f06f2490010000008c49304602210087a40aa7da76d295a133c570757b42aa015b212b548556af3e2f3fb196ccc450022100d14bb406b48694d7da93bb8eb551a5ac9dcfe9bf744d1760b0b3b2c482a2c7960141040cfa3dfb357bdff37c8748c7771e173453da5d7caa32972ab2f5c888fff5bbaeb5fc812b473bf808206930fade81ef4e373e60039886b51022ce68902d96ef70ffffffff02a0860100000000001976a91409154d402a0cf4cf0ccb474e806671409604c13388ac216fc3a80a0000001976a91461b469ada61f37c620010912a9d5d56646015f1688ac000000000100000001416c037d85e6f29c3a60509aedc44bffb6ebfa433b6b17629f1f449f6a8a420b010000008b48304502200f5a1836326e868e3489564ff3ec1ceab1f2b1c833a116a5080fb3af4b1e2880022100bfb66b1f5ee9aa74b453fb06d1241653986bc754368fdd125ce1c0867622aefc0141040cfa3dfb357bdff37c8748c7771e173453da5d7caa32972ab2f5c888fff5bbaeb5fc812b473bf808206930fade81ef4e373e60039886b51022ce68902d96ef70ffffffff02a0860100000000001976a914a602986d920c4f2e95d9a0711eed72883181e22888ac71c1c1a80a0000001976a91461b469ada61f37c620010912a9d5d56646015f1688ac00000000010000000109a635ac99aaa207c92dca0903b6db615f1f2e612286f6af75cd7c9558d608b5010000008c493046022100ac53716a177736b1144ca67722e94ca94bdc5645701908eae0d3f142b4530858022100975419f603798b46555ca0d996f4ddbdaabe689d81c7a274af54c823dbbb9b2c0141040cfa3dfb357bdff37c8748c7771e173453da5d7caa32972ab2f5c888fff5bbaeb5fc812b473bf808206930fade81ef4e373e60039886b51022ce68902d96ef70ffffffff02a0860100000000001976a9146ec46e89f8dcce3e4e714b82b64ca8f5fab551af88acc113c0a80a0000001976a91461b469ada61f37c620010912a9d5d56646015f1688ac00000000010000000175e44081f8cf0b6bb3cd1a93946f54484ab1bed18acd94217bd1d94e6408a640010000008c493046022100fdd199b1b921c272db35ec2ca3bf3e555885d0f9917855be169d2520bb9127eb022100fbf87fc8dbb685079a3c7f09c5cfe57e4c4cd9c574f6c6b03080942b3bdd54d60141040cfa3dfb357bdff37c8748c7771e173453da5d7caa32972ab2f5c888fff5bbaeb5fc812b473bf808206930fade81ef4e373e60039886b51022ce68902d96ef70ffffffff02a0860100000000001976a91468e6a1c0f63f1d62c964da9aa16c393f9198bb7288ac1166bea80a0000001976a91461b469ada61f37c620010912a9d5d56646015f1688ac000000000100000001280e2a0c3b17dbac7ded07a5e029cb84c4db6ecaeda742bc241f024b6e757ad8010000008a473044022022cea724cd06f841d78522bd936724b89bf3f3e0340bf4820a619612d45e2f4102201a55dfceeecb157ec1c36565322ce2f7ebb512de7cb9c85d89842a186a55ddc20141040cfa3dfb357bdff37c8748c7771e173453da5d7caa32972ab2f5c888fff5bbaeb5fc812b473bf808206930fade81ef4e373e60039886b51022ce68902d96ef70ffffffff02a0860100000000001976a914afb415d51ee8df29280c362356aacf8198cf1e7488ac61b8bca80a0000001976a91461b469ada61f37c620010912a9d5d56646015f1688ac0000000001000000017a0c07421a1e3cf12aa73cc193154a2095079b82bb3edf80eb4c32aa2ebcb119000000006a47304402206845ebe0b479193a19cc2360845440680e3258933b03c54019589891840761a2022032366f67ba41bdfe00b41fe95ffc371b2d1146f6de6a0cdb011ebb787a09651d0121024f74d640e1f02b40e9bcba20e72fc8c0d7842429d42dfa16fcb52fd15f3284bcffffffff03781e0000000000001976a9143bb7cb7944b25a8a7b394078311fee351efb170188ac781e000000000000475121024f74d640e1f02b40e9bcba20e72fc8c0d7842429d42dfa16fcb52fd15f3284bc211c434e545250525459000000000053d3fedcce940100000000000000050000000052ae805fc901000000001976a914393971070071930ccd8d1248cf8c7de28cbfdea588ac00000000010000000100e2a34591dad16e1de7259cceee8ea6497b1f3fdf26e412fcf15e70dcd03407020000006a473044022058b96022f23049c347d4bc8f371fff98d768dbb1a43d5becf03a85ff16a6675102204113231cc11471246af502c79686b248dbee3eb392654693caac740382d4fe320121024f74d640e1f02b40e9bcba20e72fc8c0d7842429d42dfa16fcb52fd15f3284bcffffffff0290d00300000000001976a9143bb7cb7944b25a8a7b394078311fee351efb170188ace067c501000000001976a914393971070071930ccd8d1248cf8c7de28cbfdea588ac000000000100000001a50c3619d797f67d512435740d826c651d547d76046cd9415b1bd1d5cd326fb4000000006b48304502210098a9cce4ec26f205d9f0251763abd449c302b9eafcd3b4fae414ab8094ae79220220064f7f6739266e8c19746b063a5d8886a84b41ed76a677bcfed389f6e26f9ba8012103f7f2362d11e2b082387b6802a773adfa3c252cb21485c85f737e7cf62e95249affffffff03781e0000000000001976a914a55e832067cdd5c1e1e59288afd7b2b0f59be37a88ac781e00000000000047512103f7f2362d11e2b082387b6802a773adfa3c252cb21485c85f737e7cf62e95249a211c434e5452505254590000000000000000765c056c0000000005f5e1000000000052ae80329800000000001976a914efe4eebd29f5b7a86492584993e05fac65ed6f5688ac0000000001000000012e286507e0ee9e50dbb9994c0b901122e064797f5399204409ae89f5a2e2d797010000006b483045022100b0eae1db5ada154a646e8b651c554978435f74de527cfb11225e11fc5c05d193022037842a670b6a7428f9b876d61341c3fd8a5f9383eba34e2167cca9d9933c34f20121035b7c88183988d374c281c222a6bf0dbcb19dc4dcb946f36fb097efeffd62fc5bffffffff03781e0000000000001976a914b59e93e7dad549bc109245ca27935d06625d703588ac781e000000000000475121035b7c88183988d374c281c222a6bf0dbcb19dc4dcb946f36fb097efeffd62fc5b211c434e5452505254590000000000000000765c056c0000000005f5e1000000000052ae4021a913000000001976a9142bc7076ced236341c9baef7bd618e7bbfa87675f88ac000000000100000001d0e4440e74cd457503e07431bbbc8759cfffd261b610ecc9c9bde6d11dfe121b020000006b48304502207132240141ce3483cd13a8061ee5b9ec3bc722f195f55238e20f22ee83956ebf022100ee7a635ac62a824a95dd6e5b60f2295dbe1217f44a9cf5d25aab1bca13d1d00f0121035b7c88183988d374c281c222a6bf0dbcb19dc4dcb946f36fb097efeffd62fc5bffffffff02905f0100000000001976a914b59e93e7dad549bc109245ca27935d06625d703588aca09aa713000000001976a9142bc7076ced236341c9baef7bd618e7bbfa87675f88ac0000000001000000012e286507e0ee9e50dbb9994c0b901122e064797f5399204409ae89f5a2e2d797000000006c493046022100ec31bde14bae7ccfc6cf01bef40c91914c5d5adbb1b3ea8de7a5dbf1603666fe022100ab36b5fed2db1ec66a1b34fa9b246dfb6322e09245eaebaf2058ff138632d75d0121030787462dda0f7a77337453482ad9789b6cec5993cbb4275255b731f7dfda3281ffffffff03781e0000000000001976a9142bc7076ced236341c9baef7bd618e7bbfa87675f88ac781e000000000000475121030787462dda0f7a77337453482ad9789b6cec5993cbb4275255b731f7dfda3281211c434e5452505254590000000000f0a6c9ae7ec80100000000000000040000000052aea0220100000000001976a914d80cb565a29d5b5c56595b7f1cd332abad7be5e888ac000000000100000001c3e7aafba938dd71b9819421a490e9c12fbe1841297ec92ea24ab94dea929a06010000006c493046022100c1f207f20c2f29175d6cdb5ffb1fa9654cf499cbaa9a316909341e7fa427321c022100f642360759d1e90a5ddf48901de6f9bd7627ebbd7ea9cb422a0944eaf4483a8b012102754dc3568d9ae70e8fcfcff6508b9e70119b1b17ab3090f7335c46d15fb15779ffffffff03781e00000000000047512102754dc3568d9ae70e8fcfcff6508b9e70119b1b17ab3090f7335c46d15fb157792120434e545250525459000000140000001d4c4105ae00005af3107a40000100000052ae781e00000000000047512102754dc3568d9ae70e8fcfcff6508b9e70119b1b17ab3090f7335c46d15fb1577921120000000000000b50726976617465206b2031000000000000000000000000000052aef0822607000000001976a9140d7ec8cf14b0ea73621e9c831338a7921653b6cd88ac000000000100000001b6f0d9b6197830a4c06df6f4467fb99e87f63bfcea55030dc55863d11758276f00000000fd1b0100483045022100e2a0104a7ac38bec2f6a1fd82402d55272e2355d2456dc5a2dd29dbb50dd98ea02205153cf827bdf8a9304cc14ef40cff099b0ebc4d85a118883d9af54ed0ce4fad3014730440220108371557cb344c7725132d04ebaeeb4c6f7e878051068698a92452970d2b44302204a4497706bf6bd791b63753ad3ad5b326781557a96ad7202b91a011265637809014c8752410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b84104c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a52aeffffffff0110270000000000001976a9141f29cee3509e8268d9500b9b23cc85da1c3b4e5688ac000000000100000002e6c757e91edc7c913416e2306f8cdb3d2e21c49d8f8623e0d92823b9819cbc98000000006a4730440220211e50bb3111449f331f296e2f56558f53a92762ee743d7cbc35b54a5bde2ef1022012783fef6ff4f8586fefe6c4d2bd6f63cdca175332908d494a2db0e10c02f8f60121039c07e5148dbacdf029b3a0fbfc67351b60cc0a347ab4be269a1504318bd26267ffffffff4179331d95be875383f3fcc8d6c280a455064d64328fedf03d1930d4e581e756260000006b48304502210086de5a7d428ba954357f7888c43614533609d5f3a6810ee9395b673f911d88ee022027f6fcd1ffaea6461ef2825c4943f2bc29eada728ddf9751f4dbdd90838e1a6b012103f909a8dc00e2085dabd71213674c090ab0405b903993218eec92bc7c80dfdc54ffffffff02c23b0200000000001976a9147558fc21c84a2aeb097c82489afbc3294608247688ac7fb00600000000001976a914d6f9e26b826174e180fd3f57ce7fecf10a74c7a288ac00000000010000000229e0a2630e8d0070e35f90fc61b77c5d1a67fd3cf7fa870ffeae8e3a4c202f46010000006a473044022031c22aed1b4f58aacfab2a4af552b2d74bae8042d03845e2cf56867748d962a402202cdf57d88dc679461128fec07cd7dc91ca7a87d6e578c65ac817ed30c44841780121030f25bb21ff95d1cf698f0c8b300258d41c64063a2ca81ed68ffdadda803c4bd9ffffffffe90d2e51a4374b3975923f4f1acc37aff14288a40a4593f79742a382cb81465f0a0000006b483045022100901e7427547385db1156b1d823042b88ee8a89e44b92796e9f8587c529cd14c002204786800018ef9c0c39079e08a768fbbe01b02568ea3bbd4c8b670b41b5310c40012103f1b4d21e0b344248185408c1af96bdf05f8ee8921dfe400fb1873add6c6e1cadffffffff027fb00600000000001976a91495267bdeedeb4a5974f46b3dea5f21f81eaa610d88ac51070200000000001976a914311a5aefdb4085af5853d880c8390ea6a1615b4588ac0000000001000000018f631fa01a6797c8fb406b01d2f2dfb9c4c3e2928e77048bce829b0e287c87be00000000fd1c0100483045022100dfd6c4112bbe482a960cffaa6bc534139fc8c035b482610faee2a1ce372728520220257d43ba08c16122601281aea02f2afd501ce67ada619c30583881982483957d01483045022100e0cdfbf616cdffc5a580cd802899daf0cff68a16d7637ae9b80f18b2c46cc8d402203dc2679a41469ef89d037d6bcbc0be5c0fe73de7c3e3c268e2b47aa6ce54ef63014c8752410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b84104c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a52aeffffffff0110270000000000001976a9146fbccef8c5e7553abc4cffa28baf0a604262248488ac0000000001000000024fb19d79fb214b86b0fb0a6577c4250b2cba5358a14d0afd7acfdd7b2b055bfa000000006a47304402204bfc5e7e31f233a2d2ec9e3a72b40f05788ced58783c8f49ba341be427229aae02204ce55d0cceeee999e788fccff011514ac4dc3b776a68a687b84cfb9c5feda1c0012102b5a5c192d56c23f98a057b8f78caaca88363030e50de36452db77d012cf8f3c0ffffffffba76e2bd51af115649e9ee6dee6e89114ab8526749e0a977214aef78c07b090f000000006a47304402202f41bae81d296b5280a25874e5ef6c8dca05a749ed90082ea77aab824a837a06022055f73d4a2c7aad0b90e27d324d9ca91791a79e76552773cacf882a85d9f31af201210282d40b989ce5de98c89e3b240cee339bbee4a086c8520839a4bf3c11b8a18d23ffffffff0240420f00000000001976a9149b99fea987e53dbaea95816444d90975733b3d6e88ac399f1600000000001976a914b48cb92c7c89edf19643d54989cb54608bfaa4e488ac00000000010000000241d4b068fbc46dab6de7b3c6dc3b5b770b37b981b993d06b4333b8a643f10d7f000000006a473044022100bb259144bf62fa224757ae18877b27865dc27ff61d04b76315040666386dadea021f2d66e54f5a6856a2c91a71f05952650c9d5ae92ad29c5896a8963f629fc32201210321182abbb202e124e62864a66396d34d2492bbd08edf6686382dcc6e04d85b45fffffffffc68edf481ba98601dada97a71d6cf7583a36e1e135f082de6f0f7d0eda70b1b010000006a473044022034ee4482a0ce32c87644081dd423c80840081ebd47a0359e5fa29a717b658da10220018401a23671472033d4b963cfafd2c9c333e319baa676b79ced8bd1e362657d0121025313094076052c8ba1884cf800018d1e9371b6ba6819bfc5f1c66a2566d6c433ffffffff0240420f00000000001976a9144546c0d280d292c8fd2bbee8419ef05fb4e385fa88aca02c1000000000001976a91412eaf230e8991ce934e3050d5c6006c050d7b82a88ac000000000100000002ed9934a2a3b72442fd5e1946ebfc9f230bbc8012722ab828e3f028ed76e8c9d8010000006a47304402200614fe73c43fe79d0bfc793e7887d0a4e5507386199e42182b27fd6cfac49f6f0220759322264b80d45ec676dd7225d0d27ed413e0e049744dbcc76d3fd998f54c7b0121029a6793c92d2859d20204ba1738af4aaaefff1113d95668d0fdf1fc268c17a31effffffffc45fde728eec6c6e103e371d36c91a0d743b44557aeaec1b68d485db2223d0a3010000006a473044022020b63c7729c84a7e800ab94fa1c4a05fd15ec2e93da3089193b09f681d54123f02207192509619af256b9f81fe1205ac57d65c98306cc862eb4bb7e0722f2c0aa4e80121037f6f4e51362069dd7380bb1ac09dd430e85bd796d3e41eb88e4058414e5e94b1ffffffff0250da1100000000001976a9142de3d38ba5d6ef95cd1ec2fd025b4873b779fbb888ac40420f00000000001976a914e4d16ca2b60dc603ae9590672e05cfabed2d7cf588ac0000000001000000025559373139aeb2fa33fcf78ba3e571663b20486cfb7d896528f97091975c5605010000006b483045022100afeae347a631c6cf353f75f4898126845d94c45702ee72ebac3928f1a1403e350220038a6dd69dd4d0ae2438aff188e2b05fd108f79b247e6429f1e5ab6b304d322801210208e3d272bc6b00638268d8b192e20fa56415e4522c567ed84ffca8ad2b004bbbffffffff38161aa5e73aaa5bcaa96dbc120fd8019710542c1015ac7fe5067459492c07da010000006a4730440220381e5b8979b34e0ea5aaf5083b5f84090c7a39786a2a774a38b37bdb7387627d0220622787d68e383296cc4c1953f83670881fd64ce478e8a03b5b27e95fb505aa47012102f6052c249e6de26e1f27d36c795ffb17d47d3210cd4ee103c1586f60b2fcdc61ffffffff0240420f00000000001976a914ceb65b1b5b7d14f8ed093cfd7213a8855c7bf94888ac50bc1600000000001976a914aae22d7d8641a83d5c2420ff7c2082a2908d5fb388ac000000000100000002ba0b4f41996673c775527a6cafe9eab0bd4554e8be55d12048837e53f79e5f6b010000006b4830450221009fe8275ddf2dbbeae2a693b4d4468b166611b5ee94c8fe306cb7a54e527bde9302207278a8b5f74dc7ba662040369e397bc41f6651bf6c9f0a0a8dd2e9b0918f6a5501210332c45c0a00234618027fa964a5be59f690bd860cc23c49fefb75d0fd8a2073a9ffffffffba76e2bd51af115649e9ee6dee6e89114ab8526749e0a977214aef78c07b090f010000006a47304402203eac533197a2658d16313906ff9728753f3fdcb0f5aaa2ea30ce813372ba5571022076bb0965ae283df0de9cc5b025aa0f5429051cfcb4108095b7985f3898766de5012102841ff04be8832522f6cda876a21165c6f751df17d11c604ac8ed4b51292d572effffffff02c0c11600000000001976a914c24947532dc8bcb742419c25dd73d25e429b01ba88ac20a10700000000001976a914a54a4680732fb4ab0a5893690f8606cbe24fbc5588ac00000000010000000213dfa0edbf17bea78fa7c124f6f54fad33fc5274892ba95a653d6fe436861313000000006b483045022100dcfd73f39e2261f22fd1a240f55a52cbfa22a6fbb74bbd49abbeb0421a99fd3d0220557e3890c9db89880c5714c88b470f54d59ad986b4cfb93491ead3322101d7ba0121035dba8a054a8ea2d65977fcff2c0c5b875c32a3efcfc8fe01934e2563e0210fc0ffffffffd1ee1f739f2fb1858c4f3718a6d921e907b4f5e297d824f154ee439c4e928f20010000006a473044022060cabd516735ab67636fa62eb3928f3797ad168f8baa713070242d1b0492036102206047245ce7a60bd0310aefd3f49862baaf1dc59cd71a19e5e9716b8611cddee50121020b3a10541b08f1b637f077ff6f2932b0bfb11829ecc8bcff39728ad47327d9e6ffffffff0250bc1600000000001976a914f01d73bde764f82a6bd101d4f361e4a1e789853f88ac20a10700000000001976a914ceb65b1b5b7d14f8ed093cfd7213a8855c7bf94888ac0000000001000000029b6b975a0b360bdd9b5bd4b9a80f73bc2f11b5a637aa2dd8c1bff635795d3fe5000000006a47304402201be59c88a100439f3546508721f189b8d1b6b7e335bb8accdcd5a66729f1f0fa02201e0793e95f44dc1d90af05ad5ce477aa26cb324f58a0e15f5bbd858ded38d037012102b76651433e6c776213507d5c0c83e6a98a6fa6779ddddf78dfbb152fb510f4c2ffffffff81ea989407217d690564d59d9e6d82303842df3a56d0703fc96d39cc563e4ab8010000006b483045022100ace96baaebee4bb4f2365c73bf87ab66c880a725b842d97d17afbc4d63650a42022069850a754204808e5218d94029a458ba41690cb6a32f189f958bcadf35bd39de012103b9a14855a4c79bcd753274b6d8e00c42a20fd584a6d86c48990e032d7a594eeeffffffff0240420f00000000001976a9143740f53b25dd242cf19eb0bb518e4d84633e2b5488acf0601300000000001976a914ec6f79c75930c650ec0a2b018d8d6535dd5c0b1c88ac000000000100000002f18f2189eb6623167a70bb99325b934d9affafe83b6c9cb4cdc93ad8bb9ec2cf010000006b48304502210086340c01a8dd74b983b54f61edd0d8e0c1631877afb9a17dd60174a3d96b1c4102203b3ca5a9c24c7004c18c0844745e38f92f45211484e7d6a177c7f6a844bebbef01210255d19ddbad95e5c05687c5a6a1e6a63fe9f474877601a2339543d372857ecb22ffffffff5397951f23d44e270d3a40178f0beed922c876bd2007bad9d6620d4a451ac7b4000000006a4730440220017efb6435e7bcde15b846727067675a4c3fa046e0bfea1c9acc543a499e848a02203a7d765e9d8a815f6cb2a15cbe2c6fbd3b65ca35d8f2ffd9a1302dd2081fce46012103c6661178c98e7e9d301a0c6939bdc62da7e0f0dad8afa34f1a1d11fad4b546e8ffffffff0240420f00000000001976a9142709334a6bb7493012424df2712989501b87099688acd0a11000000000001976a914a9d84f0654b8131196be4982d9d6e88d4253872388ac0000000001000000027ecc038b50f20ee0f3032fc7b5caa7740339e7851abcb64b810e3a26cbe676e1010000006a473044022075a9dc1c3d8a5a2a8f1edd1e30088533acad905b83bead4d585ed124fd52f679022074a84fdd5a1e0365d6df9dde0fc18c4a681518bc1aed29a9ce42b155edbbe8870121039e060af0c8c0b3af4a4a980bcc19e4c277565705fbe0dbe648b3235cd0201f5fffffffff25d03e44e8983694a421e551facd631ec6b8f7cfcaccf8eda8fba1762db006d0000000006b483045022100b42c8a06c066a827869343d374c9520b7b7c84e11e75aa68df3d42e5b52865c402203b1aeaf33c7c3b0012fde9910ef811ef243bd5f103a7157519369e160c208b0b012102843a5d13cb9b19441ce8e871d39f821c2dba0599be5066e24ccbb22379676ecaffffffff0250da1100000000001976a91497cc9c5302fbcfd8b8d4cf98539499674fb1bba088ac40420f00000000001976a914f34c5ac23e9f429f127c2c2df80d47df470359dc88ac000000000100000002fe61b8289c169cb90feb37e8b00210618764f090798aa3ff606e21832adbb6b2000000006b483045022100e750d15b7673525643f26ffb6ab014616ffb40eca397c96a4767df625ea1a3a40220141282de969b92e3c06301637b0eb5c7f2088b4c6327c562055230ca355407450121038f88fc8b8b0a05b19aa975e51a4e5744b629dbe789918ef2d7c7b4cbf4e53872ffffffffb6e58f2a4270845a5756bb90e4fae997715217143618f7b8e3f494545e2d0cab010000006a4730440220536361adf93c952b14462f4afdf498ed9cb280c84f6ae329bb7f9bd56155cb7c022015397abeec9088b7f2f68c6822a9ba868016deb47cc13143599af92d23f9399501210307c106b32cb3429706df12b46aab8bbea8299ba86f19c8ac047f729ea0712b8cffffffff0240b31100000000001976a91481d09c73c10beee8414d8e6b2f46d219db9ed9b488ac40420f00000000001976a9146abafcc33f777900180fe642440b781f47bd9be888ac000000000100000002deddeb747d0f9a92dc10c981aa473d6b45dcf65bd6f6ca45971f83a7c1ebedcf000000006b483045022100c8d09b2f037c33ee2c25b4c7623cb354af6c134147fa5b2680792b2728cf7b6302202ccf5c97c0b220ba01519eb307e17d76ac788f199670ce8b155ec2f52325d8f201210345004779de4fcb7677310a210849beb5118a149af6607b65e0e41cff77d5d2c3ffffffffd60177b4e5e63c9c466b628c4e9033590b25f23f0d83a933f4935c30dfa7a533000000006a47304402206ad7dfc4e7eb9c8f988c77162e67f296f9e07e9ffbbcf2bb9cdbf5157210586202202ce81877bdcf6106ceff9e9e02b7ed2936583770a2ea690b075d576ef7b198940121024bba329b6047900251f9eb90f54c1a14960a8efc3cf4fee620c5c6e2b9633e2cffffffff02308c1100000000001976a9140621ba5066df4ef72087082967f8f0488545674588ac006a1800000000001976a914f339a2e7e2adb584f07415b84ab7268226e340f688ac000000000100000002d22091c47e75c4b4c113f3c5e334a19cda256809955bd49f416bc95a0d6b8303000000006b483045022100be8bf43aad3d0011ce275ac230e550466235cad09ef5c741e7bfd46ce43dc2560220011ac7da44c43db4a12e7351d7924ddb3ef1fc70bcf521c19c6c0401fb520824012102dff4ded472831dce0e4870cc6546b726a7659fb8ddd93b3abba82e96bef94d6affffffffd22091c47e75c4b4c113f3c5e334a19cda256809955bd49f416bc95a0d6b8303010000006b483045022100ad5f38fc133c9d7a222ff37c4e244b06d22f923e04daad84de20227759aba27002200193f056a202bd224ebad0683f798c234c4f938cd2e515c95428415762595a16012103d5b11e96151847cbe29062265f92d29efbd62c1ad06a2b3b5e478f7282c42cc7ffffffff0240420f00000000001976a9145af5e14c39eb6f41f873e2f14c44a8cb8f0bfd2f88acd0d61c00000000001976a9147b807912b8099aaa2876c921ee48d57d4184561288ac000000000100000002d8fb1ade0575c53bde1c1caff080ed3af2de1da95ac078dd309e77ccf8e55fdf000000006b483045022100e2eb212aa293d02e8547cb1a763e4f798d667fde3997e5876456debb876ce15e02205db9df52ed1185f0e925a594ef315006fc40317b133348ac6572d71a66fef41b0121030249f823cdfcfc2bdc72ca2f75c04c739af3c5c22cbdc03866739d30b0f968beffffffff5faa04057e21f08df130728030585623b316081908175cbabd9a668bda6bd82a000000006b483045022100f0bf6b0ed5e82a14739a62dbe5c2273ae7e268856f8fe99eec2167032b172e97022046a52017f2cb376fd5dd50301a876876ed2e5123682185e4302740efd126d83c0121024146e08c6d6d6ea2c0270c3277d831259de638d27b5be4e265f3ed6305091d4fffffffff0270281200000000001976a914319d0899408cb8a79e5772d7f113fbe25992dbba88ac40420f00000000001976a914c7290c0a5688926f1e30a23b2cf0ec2b4e456a3188ac00000000010000000266e3d4ba8989e71decb07927e8ef373ee8b6c205496eee0e838a9b6cb1753b83000000006b483045022100f314e2775e6d220b492b6fe35515291b041743b46d0e3b3307cc95167bbb6b010220325ed68888406e1bd7e1e5ccfd592bdee4486307f22c3e382d48d6b831ccbddd012103c7f26643421de501c0a1fa9766a2693e153f29df258cb813ba62864fc956dd99ffffffff20c40362cc8e89914af42a74bdff742686efc0ac3479bf4b431d1e6a587a40b8010000006b483045022100cd934bc91a5c48fb71b8607bd7026f99602041dac596ff9086d3553ce49a88af022050e5b94aa701edaa055da221e64dc09d49b3374c094d1fe0d835d93737d669e60121023ba5487e3278e4abdd44b1ee011124909577480de2eff99c1c889cd84ae8f31effffffff0240061900000000001976a9147ca41603764079bf62c4a4e3f8f66baa669d66fc88ac40420f00000000001976a9149f3f25b06eda2318869d40c6af69eba67137221b88ac00000000010000000250be20e7e9f25515c6af564c41ba49b3ff95fa476768801a943ee0bcc8eed476000000006b483045022100d6b8121fa93efe013daecfa6804603758a35931fbbd6435c9fba43c093f3d16902200744263cbe0975fc0eeb2b7d08363fd3f6e62a072dda7a7e9bab56bd1e620b9f0121028d83f9b1491994790ec76ef598c8ba0436183b466b32bddabb1a462c70e29ea6ffffffff40a16cc443545b1ae809148704511609154d4a9d8dbe31dcd54bdee791eecf7f000000006b48304502205d0911a8ac32ed04d602111ef5e664e2bd146c9d7fad88f6388ff008664f7682022100982367a9b3f72d178ed2e7c2cba3ebb51386d6b2ce8df92b797d3767984ebb7a01210325f1f40c9bd38dcdaabe19034c59caf578a8457ca0d6ab88be63f583d2ff1559ffffffff0200e9a435000000001976a914df073d4a974f8134a21608e19dddbb6cf5bfad2f88acf0b9f505000000001976a9143df52b2dc885ba0bc17ccd77cd2146d7bb52f06c88ac000000000100000001be200e8543914878d90b147f5a43bfc3828af510a043a4e0512293df5ea4ad60000000006a4730440220126b0742ea548b8ae80c708f2978563bf0aad9bd44056b585a4d7dc4e7d05bc902205ad040b48dd756f409b921f6bb0e58ea9913215b26b8e1a90a3fd10026810e840121037dcd7819ce02547f96a93deaf7ea5c6244d79f13c21a855be672eb28af964c16ffffffff02f07be111000000001976a914c0ee975d1a949a6bb1161611dcdba5a13b798f8088ac0046c323000000001976a91467e955c5c4a7489f549bd401f003fdc44d0dd93b88ac000000000100000001387daacc7861110fc52535e8a9733d40fb9c56b281708993e1678c79ed927677010000006b483045022018ee4a970e5410210c715e127d289427dca1ab3fae4d06ecaac1c86887fb0132022100cffa070278a90cb98eed8f0bcc18972153073c9360f6ea1b6e4bc7b65917105d01210387a2e1288a74d1b6824bedf9058a788fc84be8bf549f2f091f4618ed95e23d7dffffffff01f01ec323000000001976a9140744f9fd5a3b73a656694171412182a32f2971b788ac0000000001000000025960889040faa5f580fa8ef80023163878bc3be210f6d0684fcebf5f7885fb62000000006b483045022100c3065feaa69ae01789dc2926205bacabc1687df6c0eda39c13f605ad25970b55022012c5e38ba14efa243ed043d9f984707b301efaf2ef84471059b76a45cc3526a90121035b7c88183988d374c281c222a6bf0dbcb19dc4dcb946f36fb097efeffd62fc5bffffffffe22128623c5c237568cec96afa71054779a91aa5737d1f915e10ddf563c4f662010000006c493046022100e598d9f46a70537e27c04bd6bcf5f69e3a50fc638bbf6de3e7cbe00ad707160a022100b15daa637999657d26bd5fe6ca87cc3fd5de9948f2fb8d3167ac2a377a049c520121035b7c88183988d374c281c222a6bf0dbcb19dc4dcb946f36fb097efeffd62fc5bffffffff0240420f00000000001976a914d43ced8367edd29adc6c364fe31b3651046e287788acc84f9813000000001976a9142bc7076ced236341c9baef7bd618e7bbfa87675f88ac00000000010000000298dd0309ce613a9fcf77ec93e5a00202dc91dc383a2f8676e07188df62c50277000000006b483045022100a6559c0df204031a8d9cb2ba05361b4f370887b2558dff3f01e69511e2f82fe002205961193e15f0b1257686ea5019b706538a6fa54dde545352180102420a4a49fb012103c1c329eea1fb6a6eb10da4394b147b2ddc3c8984dda3006933d17edb214be0eafffffffffa2333d87762d7f47a9cb376c1c6b2eed6441d0cebfb7883b14a1b18b6303d20000000006a47304402206772ff2ad24ae7b5cd9c1c723a141bffc84bfa03fc3ae05c90615b7f7075941402204b0f149a190a450b8c08084169f277a9b11d38b3738413915717778bed3aaf8c0121030ae4453a932caeff36af7a222f5e3a78e06c4a41fbd32872e76afa2f5ddc0b32ffffffff0440420f00000000001976a9149a1349d4d28bd2c7eb295578a8db7e900a88dbef88aca02c1000000000001976a9145ea1c5883f45c0f739052222186de2a97d2b477f88ac40420f00000000001976a914b61ff592088a0848653d10f1ee64934e5048320488ac40420f00000000001976a9142ae828b6dace8aa95f7e427406117f3ad979f31e88ac00000000010000000338161aa5e73aaa5bcaa96dbc120fd8019710542c1015ac7fe5067459492c07da000000006a4730440220488cabf142f1f5ec3ba43423fb527319a4ce24e1151429ecd46d2345cb5382f602203304608e4341d6174f91f72b9936482c442ae8f448e846b41ca7d88b9e1d9e0b012103d733c28935de6c3ffb2bb672642b7ad3e40eefdfc4a9a99bc8fc2c71eca165bcffffffff1279ba15d5879005507f98e4817b559c7197365908a289983702976413748147010000006b483045022100cf35ac9a86679ab434f70e6ae77a01afd7794c180813a25b4d2b3f1b7489a897022004171d5fe92535e16e9b5b584437c00b9c0f6fa61da691186fe5f30d4701ea4901210291b5f1bf2ea4f3606c7ed34d573a015f5720b9286b2f8fb3e1ac6bd08eab2f42ffffffff287a6ba0a3a61b4132b374c62ebf576790b80c1679accd0b8a655fe2daf9f282000000006b483045022100db8692c3731454de56c26068ef24bacb339503229c5f07341e5c984bd66914e402202f12b313e94bd3256b0a00f02ca0667d729deeda53e0f46cad19b6aeb5b9fd76012102ed3c03c968b75d5845cd59fbe31f93d4d73f19f375ff3e4de3ab062755fb42f5ffffffff0280841e00000000001976a914f370186a729515e8621421ee5093730b2535d76988ac103e1100000000001976a914477aa18bae7b229784a7857f27f0c4491337674888ac00000000010000000343a4217ec03a86dbd666e2d50a4f81e6ec78888501d9fe8be407315105e327e4010000006a473044022035ad18774bb1ea6ce5851c3453fa4d1bbb919c152bd70c5846c8dae8fd5768d702201cbf6085713047f0ef706d916ca02b5a64485364a2c15cb22c7b8be5585fafc8012103c8a1ce8275752d7fce768b8e2575f6ed8d770282dca77c0e92edc728e9a52660ffffffff9f3a39e94c21c9badce0da020823e1731ce83b9463f67df74cf60e67b0f1380a000000006b483045022100d2b742a1846f5ef083ad196faad1ee2ba65d1880c09070291a166c7075b5ce5a0220070410a486fef2907ae683d51b16b58367bb2e63ae83da0be06436972a5b8953012103fac77292a178d2a13e9c1cd34b62a3e97c9b67f85dc5ecf2cdc00ea30070ead6ffffffffd342b7c9bc1b211cd022e66d639a4f2529aa29e9cd144c65136cefbdf641a23e000000006b483045022100a961bd3d95a8185d81605b348fd2ec26327cb2bb8a0912cdaebd53d67325d4a902204738f384eb47aa8b30fd3255244ddf12d4e9ceadf781f909414c589f4c033f9901210242b91c415dead69340de6589ffc960181773aa85d79561d1bbe628b320a77913ffffffff0240420f00000000001976a914fe22a31bfb0d128784ec50e9bd23a5ab17ece81988ac006d1000000000001976a9141212aa3479493b3783d632cebb8b1b7f85e0f08e88ac00000000010000000363696517922487df9fafdcd2dfa016271bc74a20ff9981bb07309f74fa2ebf77010000006b483045022100800f701f92e9019ce8799c5e34d34bdff474c95fa1ee208bc42a4db94ce3a2b4022015afdf9c6abf97cf10f71f6938c1e14ec24c0ad48a029262f6f83dd228e7a935012103a6e9d75fed339936435f7b934ab24597ac47979619d6024491bc6b3ce7a18a72ffffffffd4676e4b2debcaeb558c34f9bac6b599085b3e872a18e321d59af6012438d91a010000006a473044022071e14e8ef73cf9e0aab6da408e1a03b0b14e5c58b7f7b2e6a85782ee894cc3f402201921f77148d2f88d9bb2c5fd75eb22e71f17b1b31d502519db78e1791703553801210337122f84beb41ee6b26ad030e1161363b2f6bbd46dd4147960ce7203aa4d2d1fffffffff3fd68ae235d1348d4f53445593744a8983dfdda5381ef8f38597097a27a7ade4000000006b483045022100862a808bac6390a0835d2e1ba9a2686d74d6258456df24c3256db2d75bafe96202207e59df20b49da81977ce7fab71a43ff113c8c7adc47b8e9096a42c1b6e27842601210337122f84beb41ee6b26ad030e1161363b2f6bbd46dd4147960ce7203aa4d2d1fffffffff02f2860f00000000001976a914ba9b1adb0717ed39ba70f6017f7d1ba233dcddb588ac7ea10200000000001976a9140df5cdbd67f672f755e2610ce35b574f630f0d0188ac000000000100000004ba0b4f41996673c775527a6cafe9eab0bd4554e8be55d12048837e53f79e5f6b000000006b483045022100e9f596937f73fbf760b8812d2949239013ccf689d210f344a4799bde555b8b3102205eb9f820510cfdbc1c5d14e069649d4ef107448f7dfc43abe4eb789d3db5efdb0121025efb6cf542a84987701be92bbbcb2b7da729a91e46fc82412de2c3121e29c1afffffffff89f749d3a7c6ef70b549a3f7502bdeadc3ab0c98bf80b716d961bf3830150012000000006a4730440220790dc4db98320dd0a5f5bdce54c24ff1aa22bfceafad0a78b7888c367ab4142d02203f2f55e3a6bca3908da221d23d7f7f103254d9cc6bdde398a7c3de6e4f5824c3012102c55d1894bb30e518dc8f04cbf0a839eea6fdd2f84cb7ee8dfb4df2df543d0081ffffffff0fcf7953a6e8f88c430c24b630a61d5a49181afe2e767ce76119ade9fe07c231010000006b4830450221009e5f130a7a3a1b38e69ee04467aae9974ec16323c6931d1143322ad365de2277022034ae51f6bc8683874fca0cc042ffaf10ec2c0843fca16cb817195c0b2e52678a012102c55d1894bb30e518dc8f04cbf0a839eea6fdd2f84cb7ee8dfb4df2df543d0081ffffffff3373ce2c64e6d6ff443dd48d493401b5ef2c7d1cc6838de0bd157492546f2570010000006b483045022100b94000fc2d47ada82eb93b214a715b0269c1fc1f27ef5de03bdde0bfa867a1f202206994d434eb63e9398da5e42d0ddd1991863416e93d8cf3f56606456fe89e813001210208e3d272bc6b00638268d8b192e20fa56415e4522c567ed84ffca8ad2b004bbbffffffff0260e31600000000001976a9143db4f4e6c67e1540ff43420b226d79dc7d8e168f88ac50bc1600000000001976a914cf3b5cc3a0aa4e24de74d38f7643500a73d6c07f88ac0000000001000000015c9cf5021b59f07ef2ed5970b410bfe6470690d309e03f495b4874a50b372b96010000006b483045022100b633d5fa3838c6753fe9fd5a656719a109ff0579cca9bb3272d5614715ba2202022040089f8af701ce448ce69217f731b013a917d9da6f23d9a408779b7a485b0c6d012103d850746fc0287d8550dfba1ec81c340d3c192d10fa9c918908fc913910667f01ffffffff02a0860100000000001976a91421620cd6d05d98cd15d2c7957d3c07bcf9731f8d88acc89f2e00000000001976a91418751b7839491fb642b370760c5887037d30aefc88ac000000000100000001fa16fe96c11d1541c39de3acee0ddf222a4093b4554ec0b79c3cb230b0105faa010000006b483045022100a19ebbf182c5c26605070915d5c93698fc7145e2f96a7e1981d7eaef99b18c9b02204bd5f9729790f2b7073440c71af9e781b10b1be6a20f5295ca10a9cbc390dc260121038463a2dc022b91a5db8c88b115c2beb647ee226f3876818d81a301cc682b49ceffffffff0200688909000000001976a9141cd4c2807911d65d5ff4c575ddf9141ad022458988ac40047962030000001976a9149dec6baca88fd80aa04d2320fbff60225eb8e9e588ac0000000001000000014e417cfa0ddc6a5627e5a312a7a3300a1e96e9f32489b3faaa35317c907d36a5010000006b483045022100fc52565e014c25cccb61913a08e66106791c3dad63d41e4ea8c20fda5631509e02200e495fd33a06809be1965fb2e9cb757c46b3b0aab6cbe4ce775b9b87123654fb012103d850746fc0287d8550dfba1ec81c340d3c192d10fa9c918908fc913910667f01ffffffff0210270000000000001976a91421620cd6d05d98cd15d2c7957d3c07bcf9731f8d88acb8782e00000000001976a91418751b7839491fb642b370760c5887037d30aefc88ac00000000" + } + ], + "invalid": [ + { + "exception": "Buffer too small \\(< 80 bytes\\)", + "hex": "020000003385c4b2a3499669987f5d04fa4127b59dbf2ee625694fa0bf08000000000000cf52f0eb" + } + ] +} diff --git a/test/fixtures/convert.json b/test/fixtures/convert.json deleted file mode 100644 index b75900418..000000000 --- a/test/fixtures/convert.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "valid": [ - { - "hex": "0000000000000000000000000000000000000000", - "wordArray": { - "words": [0, 0, 0, 0, 0], - "sigBytes": 20 - } - }, - { - "hex": "62e907b15cbf27d5425399ebf6f0fb50ebb88f18", - "wordArray": { - "words": [1659439025, 1556031445, 1112775147, -151979184, -340226280], - "sigBytes": 20 - } - }, - { - "hex": "ffffffffffffffffffffffffffffffffffffffff", - "wordArray": { - "words": [-1, -1, -1, -1, -1], - "sigBytes": 20 - } - } - ] -} diff --git a/test/fixtures/ecdsa.json b/test/fixtures/ecdsa.json index 3f1421f07..57814c280 100644 --- a/test/fixtures/ecdsa.json +++ b/test/fixtures/ecdsa.json @@ -1,81 +1,165 @@ { - "valid": [ - { - "d": "01", - "k": "ec633bd56a5774a0940cb97e27a9e4e51dc94af737596a0c5cbb3d30332d92a5", - "message": "Everything should be made as simple as possible, but not simpler.", - "i": 0, - "signature": { - "r": "23362334225185207751494092901091441011938859014081160902781146257181456271561", - "s": "50433721247292933944369538617440297985091596895097604618403996029256432099938" - } - }, - { - "d": "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140", - "k": "9dc74cbfd383980fb4ae5d2680acddac9dac956dca65a28c80ac9c847c2374e4", - "message": "Equations are more important to me, because politics is for the present, but an equation is something for eternity.", - "i": 0, - "signature": { - "r": "38341707918488238920692284707283974715538935465589664377561695343399725051885", - "s": "3180566392414476763164587487324397066658063772201694230600609996154610926757" - } - }, - { - "d": "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140", - "k": "fd27071f01648ebbdd3e1cfbae48facc9fa97edc43bbbc9a7fdc28eae13296f5", - "message": "Not only is the Universe stranger than we think, it is stranger than we can think.", - "i": 0, - "signature": { - "r": "115464191557905790016094131873849783294273568009648050793030031933291767741904", - "s": "50562520307781850052192542766631199590053690478900449960232079510155113443971" - } - }, - { - "d": "0000000000000000000000000000000000000000000000000000000000000001", - "k": "f0cd2ba5fc7c183de589f6416220a36775a146740798756d8d949f7166dcc87f", - "message": "How wonderful that we have met with a paradox. Now we have some hope of making progress.", - "i": 1, - "signature": { - "r": "87230998027579607140680851455601772643840468630989315269459846730712163783123", - "s": "53231320085894623106179381504478252331065330583563809963303318469380290929875" - } - }, - { - "d": "69ec59eaa1f4f2e36b639716b7c30ca86d9a5375c7b38d8918bd9c0ebc80ba64", - "k": "6bb4a594ad57c1aa22dbe991a9d8501daf4688bf50a4892ef21bd7c711afda97", - "message": "Computer science is no more about computers than astronomy is about telescopes.", - "i": 0, - "signature": { - "r": "51348483531757779992459563033975330355971795607481991320287437101831125115997", - "s": "6277080015686056199074771961940657638578000617958603212944619747099038735862" - } - }, - { - "d": "00000000000000000000000000007246174ab1e92e9149c6e446fe194d072637", - "k": "097b5c8ee22c3ea78a4d3635e0ff6fe85a1eb92ce317ded90b9e71aab2b861cb", - "message": "...if you aren't, at any given time, scandalized by code you wrote five or even three years ago, you're not learning anywhere near enough", - "i": 1, - "signature": { - "r": "113979859486826658566290715281614250298918272782414232881639314569529560769671", - "s": "6517071009538626957379450615706485096874328019806177698938278220732027419959" + "valid": { + "ecdsa": [ + { + "d": "01", + "k": "ec633bd56a5774a0940cb97e27a9e4e51dc94af737596a0c5cbb3d30332d92a5", + "message": "Everything should be made as simple as possible, but not simpler.", + "i": 0, + "signature": { + "r": "23362334225185207751494092901091441011938859014081160902781146257181456271561", + "s": "50433721247292933944369538617440297985091596895097604618403996029256432099938" + } + }, + { + "d": "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140", + "k": "9dc74cbfd383980fb4ae5d2680acddac9dac956dca65a28c80ac9c847c2374e4", + "message": "Equations are more important to me, because politics is for the present, but an equation is something for eternity.", + "i": 0, + "signature": { + "r": "38341707918488238920692284707283974715538935465589664377561695343399725051885", + "s": "3180566392414476763164587487324397066658063772201694230600609996154610926757" + } + }, + { + "d": "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140", + "k": "fd27071f01648ebbdd3e1cfbae48facc9fa97edc43bbbc9a7fdc28eae13296f5", + "message": "Not only is the Universe stranger than we think, it is stranger than we can think.", + "i": 0, + "signature": { + "r": "115464191557905790016094131873849783294273568009648050793030031933291767741904", + "s": "50562520307781850052192542766631199590053690478900449960232079510155113443971" + } + }, + { + "d": "0000000000000000000000000000000000000000000000000000000000000001", + "k": "f0cd2ba5fc7c183de589f6416220a36775a146740798756d8d949f7166dcc87f", + "message": "How wonderful that we have met with a paradox. Now we have some hope of making progress.", + "i": 1, + "signature": { + "r": "87230998027579607140680851455601772643840468630989315269459846730712163783123", + "s": "53231320085894623106179381504478252331065330583563809963303318469380290929875" + } + }, + { + "d": "69ec59eaa1f4f2e36b639716b7c30ca86d9a5375c7b38d8918bd9c0ebc80ba64", + "k": "6bb4a594ad57c1aa22dbe991a9d8501daf4688bf50a4892ef21bd7c711afda97", + "message": "Computer science is no more about computers than astronomy is about telescopes.", + "i": 0, + "signature": { + "r": "51348483531757779992459563033975330355971795607481991320287437101831125115997", + "s": "6277080015686056199074771961940657638578000617958603212944619747099038735862" + } + }, + { + "d": "00000000000000000000000000007246174ab1e92e9149c6e446fe194d072637", + "k": "097b5c8ee22c3ea78a4d3635e0ff6fe85a1eb92ce317ded90b9e71aab2b861cb", + "message": "...if you aren't, at any given time, scandalized by code you wrote five or even three years ago, you're not learning anywhere near enough", + "i": 1, + "signature": { + "r": "113979859486826658566290715281614250298918272782414232881639314569529560769671", + "s": "6517071009538626957379450615706485096874328019806177698938278220732027419959" + } + }, + { + "d": "000000000000000000000000000000000000000000056916d0f9b31dc9b637f3", + "k": "19355c36c8cbcdfb2382e23b194b79f8c97bf650040fc7728dfbf6b39a97c25b", + "message": "The question of whether computers can think is like the question of whether submarines can swim.", + "i": 1, + "signature": { + "r": "93122007060065279508564838030979550535085999589142852106617159184757394422777", + "s": "3078539468410661027472930027406594684630312677495124015420811882501887769839" + } } - }, - { - "d": "000000000000000000000000000000000000000000056916d0f9b31dc9b637f3", - "k": "19355c36c8cbcdfb2382e23b194b79f8c97bf650040fc7728dfbf6b39a97c25b", - "message": "The question of whether computers can think is like the question of whether submarines can swim.", - "i": 1, - "signature": { - "r": "93122007060065279508564838030979550535085999589142852106617159184757394422777", - "s": "3078539468410661027472930027406594684630312677495124015420811882501887769839" + ], + "rfc6979": [ + { + "message": "test data", + "d": "fee0a1f7afebf9d2a5a80c0c98a31c709681cce195cbcd06342b517970c0be1e", + "k0": "fcce1de7a9bcd6b2d3defade6afa1913fb9229e3b7ddf4749b55c4848b2a196e", + "k1": "727fbcb59eb48b1d7d46f95a04991fc512eb9dbf9105628e3aec87428df28fd8", + "k15": "398f0e2c9f79728f7b3d84d447ac3a86d8b2083c8f234a0ffa9c4043d68bd258" + }, + { + "message": "Everything should be made as simple as possible, but not simpler.", + "d": "0000000000000000000000000000000000000000000000000000000000000001", + "k0": "ec633bd56a5774a0940cb97e27a9e4e51dc94af737596a0c5cbb3d30332d92a5", + "k1": "df55b6d1b5c48184622b0ead41a0e02bfa5ac3ebdb4c34701454e80aabf36f56", + "k15": "def007a9a3c2f7c769c75da9d47f2af84075af95cadd1407393dc1e26086ef87" + }, + { + "message": "Satoshi Nakamoto", + "d": "0000000000000000000000000000000000000000000000000000000000000002", + "k0": "d3edc1b8224e953f6ee05c8bbf7ae228f461030e47caf97cde91430b4607405e", + "k1": "f86d8e43c09a6a83953f0ab6d0af59fb7446b4660119902e9967067596b58374", + "k15": "241d1f57d6cfd2f73b1ada7907b199951f95ef5ad362b13aed84009656e0254a" + }, + { + "message": "Diffie Hellman", + "d": "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f", + "k0": "c378a41cb17dce12340788dd3503635f54f894c306d52f6e9bc4b8f18d27afcc", + "k1": "90756c96fef41152ac9abe08819c4e95f16da2af472880192c69a2b7bac29114", + "k15": "7b3f53300ab0ccd0f698f4d67db87c44cf3e9e513d9df61137256652b2e94e7c" + }, + { + "message": "Japan", + "d": "8080808080808080808080808080808080808080808080808080808080808080", + "k0": "f471e61b51d2d8db78f3dae19d973616f57cdc54caaa81c269394b8c34edcf59", + "k1": "6819d85b9730acc876fdf59e162bf309e9f63dd35550edf20869d23c2f3e6d17", + "k15": "d8e8bae3ee330a198d1f5e00ad7c5f9ed7c24c357c0a004322abca5d9cd17847" + }, + { + "message": "Bitcoin", + "d": "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140", + "k0": "36c848ffb2cbecc5422c33a994955b807665317c1ce2a0f59c689321aaa631cc", + "k1": "4ed8de1ec952a4f5b3bd79d1ff96446bcd45cabb00fc6ca127183e14671bcb85", + "k15": "56b6f47babc1662c011d3b1f93aa51a6e9b5f6512e9f2e16821a238d450a31f8" + }, + { + "message": "i2FLPP8WEus5WPjpoHwheXOMSobUJVaZM1JPMQZq", + "d": "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140", + "k0": "6e9b434fcc6bbb081a0463c094356b47d62d7efae7da9c518ed7bac23f4e2ed6", + "k1": "ae5323ae338d6117ce8520a43b92eacd2ea1312ae514d53d8e34010154c593bb", + "k15": "3eaa1b61d1b8ab2f1ca71219c399f2b8b3defa624719f1e96fe3957628c2c4ea" + }, + { + "message": "lEE55EJNP7aLrMtjkeJKKux4Yg0E8E1SAJnWTCEh", + "d": "3881e5286abc580bb6139fe8e83d7c8271c6fe5e5c2d640c1f0ed0e1ee37edc9", + "k0": "5b606665a16da29cc1c5411d744ab554640479dd8abd3c04ff23bd6b302e7034", + "k1": "f8b25263152c042807c992eacd2ac2cc5790d1e9957c394f77ea368e3d9923bd", + "k15": "ea624578f7e7964ac1d84adb5b5087dd14f0ee78b49072aa19051cc15dab6f33" + }, + { + "message": "2SaVPvhxkAPrayIVKcsoQO5DKA8Uv5X/esZFlf+y", + "d": "7259dff07922de7f9c4c5720d68c9745e230b32508c497dd24cb95ef18856631", + "k0": "3ab6c19ab5d3aea6aa0c6da37516b1d6e28e3985019b3adb388714e8f536686b", + "k1": "19af21b05004b0ce9cdca82458a371a9d2cf0dc35a813108c557b551c08eb52e", + "k15": "117a32665fca1b7137a91c4739ac5719fec0cf2e146f40f8e7c21b45a07ebc6a" + }, + { + "message": "00A0OwO2THi7j5Z/jp0FmN6nn7N/DQd6eBnCS+/b", + "d": "0d6ea45d62b334777d6995052965c795a4f8506044b4fd7dc59c15656a28f7aa", + "k0": "79487de0c8799158294d94c0eb92ee4b567e4dc7ca18addc86e49d31ce1d2db6", + "k1": "9561d2401164a48a8f600882753b3105ebdd35e2358f4f808c4f549c91490009", + "k15": "b0d273634129ff4dbdf0df317d4062a1dbc58818f88878ffdb4ec511c77976c0" } - } - ], + ] + }, "invalid": { "recoverPubKey": [ + { + "description": "Invalid r value (< 0)", + "exception": "Invalid r value", + "e": "01", + "signature": { + "r": "-01", + "s": "02" + }, + "i": 0 + }, { "description": "Invalid r value (== 0)", - "exception": "nR is not a valid curve point", + "exception": "Invalid r value", "e": "01", "signature": { "r": "00", @@ -83,6 +167,46 @@ }, "i": 0 }, + { + "description": "Invalid s value (< 0)", + "exception": "Invalid s value", + "e": "01", + "signature": { + "r": "02", + "s": "-01" + }, + "i": 0 + }, + { + "description": "Invalid s value (== 0)", + "exception": "Invalid s value", + "e": "01", + "signature": { + "r": "02", + "s": "00" + }, + "i": 0 + }, + { + "description": "Invalid r value (nR is infinity)", + "exception": "nR is not a valid curve point", + "e": "01", + "signature": { + "r": "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141", + "s": "01" + }, + "i": 0 + }, + { + "description": "Invalid curve point", + "exception": "Point is not on the curve", + "e": "01", + "signature": { + "r": "99999999999999999999999999999999999999", + "s": "01" + }, + "i": 0 + }, { "description": "Invalid i value (> 3)", "exception": "Recovery param is more than two bits", @@ -98,16 +222,25 @@ { "description": "The wrong signature", "d": "01", - "e": "06ef2b193b83b3d701f765f1db34672ab84897e1252343cc2197829af3a30456", + "message": "foo", "signature": { "r": "38341707918488238920692284707283974715538935465589664377561695343399725051885", "s": "3180566392414476763164587487324397066658063772201694230600609996154610926757" } }, + { + "description": "Invalid r value (< 0)", + "d": "01", + "message": "foo", + "signature": { + "r": "-01", + "s": "02" + } + }, { "description": "Invalid r value (== 0)", "d": "01", - "e": "01", + "message": "foo", "signature": { "r": "00", "s": "02" @@ -116,16 +249,25 @@ { "description": "Invalid r value (>= n)", "d": "01", - "e": "01", + "message": "foo", "signature": { "r": "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141", "s": "02" } }, + { + "description": "Invalid s value (< 0)", + "d": "01", + "message": "foo", + "signature": { + "r": "02", + "s": "-01" + } + }, { "description": "Invalid s value (== 0)", "d": "01", - "e": "01", + "message": "foo", "signature": { "r": "02", "s": "00" @@ -134,11 +276,20 @@ { "description": "Invalid s value (>= n)", "d": "01", - "e": "01", + "message": "foo", "signature": { "r": "02", "s": "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141" } + }, + { + "description": "Invalid r, s values (r = s = -n)", + "d": "01", + "message": "foo", + "signature": { + "r": "-115792089237316195423570985008687907852837564279074904382605163141518161494337", + "s": "-115792089237316195423570985008687907852837564279074904382605163141518161494337" + } } ] } diff --git a/test/fixtures/ecsignature.json b/test/fixtures/ecsignature.json index 3a18a8117..2c72182cd 100644 --- a/test/fixtures/ecsignature.json +++ b/test/fixtures/ecsignature.json @@ -173,6 +173,26 @@ "exception": "S value excessively padded", "hex": "300c020400ffffff02040000ffff" } + ], + "scriptSignature": [ + { + "exception": "Invalid hashType 7", + "hashType": 7, + "hex": "3044022033a69cd2065432a30f3d1ce4eb0d59b8ab58c74f27c41a7fdb5696ad4e6108c902206f807982866f785d3f6418d24163ddae117b7db4d5fdf0071de069fa5434226207", + "signature": { + "r": "23362334225185207751494092901091441011938859014081160902781146257181456271561", + "s": "50433721247292933944369538617440297985091596895097604618403996029256432099938" + } + }, + { + "exception": "Invalid hashType 140", + "hashType": 140, + "hex": "3044022033a69cd2065432a30f3d1ce4eb0d59b8ab58c74f27c41a7fdb5696ad4e6108c902206f807982866f785d3f6418d24163ddae117b7db4d5fdf0071de069fa543422628c", + "signature": { + "r": "23362334225185207751494092901091441011938859014081160902781146257181456271561", + "s": "50433721247292933944369538617440297985091596895097604618403996029256432099938" + } + } ] } } diff --git a/test/fixtures/hdnode.json b/test/fixtures/hdnode.json index 86040a3cc..797f940bd 100644 --- a/test/fixtures/hdnode.json +++ b/test/fixtures/hdnode.json @@ -1,6 +1,7 @@ { "valid": [ { + "network": "bitcoin", "master": { "seed": "000102030405060708090a0b0c0d0e0f", "wif": "L52XzL2cMkHxqxBXRyEpnPQZGUs3uKiL3R11XbAdHigRzDozKZeW", @@ -8,7 +9,6 @@ "chainCode": "873dff81c02f525623fd1fe5167eac3a55a049de3d314bb42ee227ffed37d508", "hex": "0488b21e000000000000000000873dff81c02f525623fd1fe5167eac3a55a049de3d314bb42ee227ffed37d5080339a36013301597daef41fbe593a02cc513d0b55527ec2df1050e2e8ff49c85c2", "hexPriv": "0488ade4000000000000000000873dff81c02f525623fd1fe5167eac3a55a049de3d314bb42ee227ffed37d50800e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35", - "network": "bitcoin", "base58": "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8", "base58Priv": "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi", "identifier": "3442193e1bb70916e914552172cd4e2dbc9df811", @@ -23,9 +23,6 @@ "wif": "L5BmPijJjrKbiUfG4zbiFKNqkvuJ8usooJmzuD7Z8dkRoTThYnAT", "pubKey": "035a784662a4a20a65bf6aab9ae98a6c068a81c52e4b032c0fb5400c706cfccc56", "chainCode": "47fdacbd0f1097043b78c63c20c34ef4ed9a111d980047ad16282c7ae6236141", - "hex": "0488b21e013442193e8000000047fdacbd0f1097043b78c63c20c34ef4ed9a111d980047ad16282c7ae6236141035a784662a4a20a65bf6aab9ae98a6c068a81c52e4b032c0fb5400c706cfccc56", - "hexPriv": "0488ade4013442193e8000000047fdacbd0f1097043b78c63c20c34ef4ed9a111d980047ad16282c7ae623614100edb2e14f9ee77d26dd93b4ecede8d16ed408ce149b6cd80b0715a2d911a0afea", - "network": "bitcoin", "base58": "xpub68Gmy5EdvgibQVfPdqkBBCHxA5htiqg55crXYuXoQRKfDBFA1WEjWgP6LHhwBZeNK1VTsfTFUHCdrfp1bgwQ9xv5ski8PX9rL2dZXvgGDnw", "base58Priv": "xprv9uHRZZhk6KAJC1avXpDAp4MDc3sQKNxDiPvvkX8Br5ngLNv1TxvUxt4cV1rGL5hj6KCesnDYUhd7oWgT11eZG7XnxHrnYeSvkzY7d2bhkJ7", "identifier": "5c1bd648ed23aa5fd50ba52b2457c11e9e80a6a7", @@ -38,9 +35,6 @@ "wif": "KyFAjQ5rgrKvhXvNMtFB5PCSKUYD1yyPEe3xr3T34TZSUHycXtMM", "pubKey": "03501e454bf00751f24b1b489aa925215d66af2234e3891c3b21a52bedb3cd711c", "chainCode": "2a7857631386ba23dacac34180dd1983734e444fdbf774041578e9b6adb37c19", - "hex": "0488b21e025c1bd648000000012a7857631386ba23dacac34180dd1983734e444fdbf774041578e9b6adb37c1903501e454bf00751f24b1b489aa925215d66af2234e3891c3b21a52bedb3cd711c", - "hexPriv": "0488ade4025c1bd648000000012a7857631386ba23dacac34180dd1983734e444fdbf774041578e9b6adb37c19003c6cb8d0f6a264c91ea8b5030fadaa8e538b020f0a387421a12de9319dc93368", - "network": "bitcoin", "base58": "xpub6ASuArnXKPbfEwhqN6e3mwBcDTgzisQN1wXN9BJcM47sSikHjJf3UFHKkNAWbWMiGj7Wf5uMash7SyYq527Hqck2AxYysAA7xmALppuCkwQ", "base58Priv": "xprv9wTYmMFdV23N2TdNG573QoEsfRrWKQgWeibmLntzniatZvR9BmLnvSxqu53Kw1UmYPxLgboyZQaXwTCg8MSY3H2EU4pWcQDnRnrVA1xe8fs", "identifier": "bef5a2f9a56a94aab12459f72ad9cf8cf19c7bbe", @@ -54,9 +48,6 @@ "wif": "L43t3od1Gh7Lj55Bzjj1xDAgJDcL7YFo2nEcNaMGiyRZS1CidBVU", "pubKey": "0357bfe1e341d01c69fe5654309956cbea516822fba8a601743a012a7896ee8dc2", "chainCode": "04466b9cc8e161e966409ca52986c584f07e9dc81f735db683c3ff6ec7b1503f", - "hex": "0488b21e03bef5a2f98000000204466b9cc8e161e966409ca52986c584f07e9dc81f735db683c3ff6ec7b1503f0357bfe1e341d01c69fe5654309956cbea516822fba8a601743a012a7896ee8dc2", - "hexPriv": "0488ade403bef5a2f98000000204466b9cc8e161e966409ca52986c584f07e9dc81f735db683c3ff6ec7b1503f00cbce0d719ecf7431d88e6a89fa1483e02e35092af60c042b1df2ff59fa424dca", - "network": "bitcoin", "base58": "xpub6D4BDPcP2GT577Vvch3R8wDkScZWzQzMMUm3PWbmWvVJrZwQY4VUNgqFJPMM3No2dFDFGTsxxpG5uJh7n7epu4trkrX7x7DogT5Uv6fcLW5", "base58Priv": "xprv9z4pot5VBttmtdRTWfWQmoH1taj2axGVzFqSb8C9xaxKymcFzXBDptWmT7FwuEzG3ryjH4ktypQSAewRiNMjANTtpgP4mLTj34bhnZX7UiM", "identifier": "ee7ab90cde56a8c0e2bb086ac49748b8db9dce72", @@ -69,9 +60,6 @@ "wif": "KwjQsVuMjbCP2Zmr3VaFaStav7NvevwjvvkqrWd5Qmh1XVnCteBR", "pubKey": "02e8445082a72f29b75ca48748a914df60622a609cacfce8ed0e35804560741d29", "chainCode": "cfb71883f01676f587d023cc53a35bc7f88f724b1f8c2892ac1275ac822a3edd", - "hex": "0488b21e04ee7ab90c00000002cfb71883f01676f587d023cc53a35bc7f88f724b1f8c2892ac1275ac822a3edd02e8445082a72f29b75ca48748a914df60622a609cacfce8ed0e35804560741d29", - "hexPriv": "0488ade404ee7ab90c00000002cfb71883f01676f587d023cc53a35bc7f88f724b1f8c2892ac1275ac822a3edd000f479245fb19a38a1954c5c7c0ebab2f9bdfd96a17563ef28a6a4b1a2a764ef4", - "network": "bitcoin", "base58": "xpub6FHa3pjLCk84BayeJxFW2SP4XRrFd1JYnxeLeU8EqN3vDfZmbqBqaGJAyiLjTAwm6ZLRQUMv1ZACTj37sR62cfN7fe5JnJ7dh8zL4fiyLHV", "base58Priv": "xprvA2JDeKCSNNZky6uBCviVfJSKyQ1mDYahRjijr5idH2WwLsEd4Hsb2Tyh8RfQMuPh7f7RtyzTtdrbdqqsunu5Mm3wDvUAKRHSC34sJ7in334", "identifier": "d880d7d893848509a62d8fb74e32148dac68412f", @@ -84,9 +72,6 @@ "wif": "Kybw8izYevo5xMh1TK7aUr7jHFCxXS1zv8p3oqFz3o2zFbhRXHYs", "pubKey": "022a471424da5e657499d1ff51cb43c47481a03b1e77f951fe64cec9f5a48f7011", "chainCode": "c783e67b921d2beb8f6b389cc646d7263b4145701dadd2161548a8b078e65e9e", - "hex": "0488b21e05d880d7d83b9aca00c783e67b921d2beb8f6b389cc646d7263b4145701dadd2161548a8b078e65e9e022a471424da5e657499d1ff51cb43c47481a03b1e77f951fe64cec9f5a48f7011", - "hexPriv": "0488ade405d880d7d83b9aca00c783e67b921d2beb8f6b389cc646d7263b4145701dadd2161548a8b078e65e9e00471b76e389e528d6de6d816857e012c5455051cad6660850e58372a6c3e6e7c8", - "network": "bitcoin", "base58": "xpub6H1LXWLaKsWFhvm6RVpEL9P4KfRZSW7abD2ttkWP3SSQvnyA8FSVqNTEcYFgJS2UaFcxupHiYkro49S8yGasTvXEYBVPamhGW6cFJodrTHy", "base58Priv": "xprvA41z7zogVVwxVSgdKUHDy1SKmdb533PjDz7J6N6mV6uS3ze1ai8FHa8kmHScGpWmj4WggLyQjgPie1rFSruoUihUZREPSL39UNdE3BBDu76", "identifier": "d69aa102255fed74378278c7812701ea641fdf32", @@ -96,12 +81,12 @@ ] }, { + "network": "bitcoin", "master": { "seed": "fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542", "wif": "KyjXhyHF9wTphBkfpxjL8hkDXDUSbE3tKANT94kXSyh6vn6nKaoy", "pubKey": "03cbcaa9c98c877a26977d00825c956a238e8dddfbd322cce4f74b0b5bd6ace4a7", "chainCode": "60499f801b896d83179a4374aeb7822aaeaceaa0db1f85ee3e904c4defbd9689", - "network": "bitcoin", "base58": "xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB", "base58Priv": "xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U", "hex": "0488b21e00000000000000000060499f801b896d83179a4374aeb7822aaeaceaa0db1f85ee3e904c4defbd968903cbcaa9c98c877a26977d00825c956a238e8dddfbd322cce4f74b0b5bd6ace4a7", @@ -117,9 +102,6 @@ "wif": "L2ysLrR6KMSAtx7uPqmYpoTeiRzydXBattRXjXz5GDFPrdfPzKbj", "pubKey": "02fc9e5af0ac8d9b3cecfe2a888e2117ba3d089d8585886c9c826b6b22a98d12ea", "chainCode": "f0909affaa7ee7abe5dd4e100598d4dc53cd709d5a5c2cac40e7412f232f7c9c", - "hex": "0488b21e01bd16bee500000000f0909affaa7ee7abe5dd4e100598d4dc53cd709d5a5c2cac40e7412f232f7c9c02fc9e5af0ac8d9b3cecfe2a888e2117ba3d089d8585886c9c826b6b22a98d12ea", - "hexPriv": "0488ade401bd16bee500000000f0909affaa7ee7abe5dd4e100598d4dc53cd709d5a5c2cac40e7412f232f7c9c00abe74a98f6c7eabee0428f53798f0ab8aa1bd37873999041703c742f15ac7e1e", - "network": "bitcoin", "base58": "xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH", "base58Priv": "xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt", "identifier": "5a61ff8eb7aaca3010db97ebda76121610b78096", @@ -133,9 +115,6 @@ "wif": "L1m5VpbXmMp57P3knskwhoMTLdhAAaXiHvnGLMribbfwzVRpz2Sr", "pubKey": "03c01e7425647bdefa82b12d9bad5e3e6865bee0502694b94ca58b666abc0a5c3b", "chainCode": "be17a268474a6bb9c61e1d720cf6215e2a88c5406c4aee7b38547f585c9a37d9", - "hex": "0488b21e025a61ff8effffffffbe17a268474a6bb9c61e1d720cf6215e2a88c5406c4aee7b38547f585c9a37d903c01e7425647bdefa82b12d9bad5e3e6865bee0502694b94ca58b666abc0a5c3b", - "hexPriv": "0488ade4025a61ff8effffffffbe17a268474a6bb9c61e1d720cf6215e2a88c5406c4aee7b38547f585c9a37d900877c779ad9687164e9c2f4f0f4ff0340814392330693ce95a58fe18fd52e6e93", - "network": "bitcoin", "base58": "xpub6ASAVgeehLbnwdqV6UKMHVzgqAG8Gr6riv3Fxxpj8ksbH9ebxaEyBLZ85ySDhKiLDBrQSARLq1uNRts8RuJiHjaDMBU4Zn9h8LZNnBC5y4a", "base58Priv": "xprv9wSp6B7kry3Vj9m1zSnLvN3xH8RdsPP1Mh7fAaR7aRLcQMKTR2vidYEeEg2mUCTAwCd6vnxVrcjfy2kRgVsFawNzmjuHc2YmYRmagcEPdU9", "identifier": "d8ab493736da02f11ed682f88339e720fb0379d1", @@ -148,9 +127,6 @@ "wif": "KzyzXnznxSv249b4KuNkBwowaN3akiNeEHy5FWoPCJpStZbEKXN2", "pubKey": "03a7d1d856deb74c508e05031f9895dab54626251b3806e16b4bd12e781a7df5b9", "chainCode": "f366f48f1ea9f2d1d3fe958c95ca84ea18e4c4ddb9366c336c927eb246fb38cb", - "hex": "0488b21e03d8ab493700000001f366f48f1ea9f2d1d3fe958c95ca84ea18e4c4ddb9366c336c927eb246fb38cb03a7d1d856deb74c508e05031f9895dab54626251b3806e16b4bd12e781a7df5b9", - "hexPriv": "0488ade403d8ab493700000001f366f48f1ea9f2d1d3fe958c95ca84ea18e4c4ddb9366c336c927eb246fb38cb00704addf544a06e5ee4bea37098463c23613da32020d604506da8c0518e1da4b7", - "network": "bitcoin", "base58": "xpub6DF8uhdarytz3FWdA8TvFSvvAh8dP3283MY7p2V4SeE2wyWmG5mg5EwVvmdMVCQcoNJxGoWaU9DCWh89LojfZ537wTfunKau47EL2dhHKon", "base58Priv": "xprv9zFnWC6h2cLgpmSA46vutJzBcfJ8yaJGg8cX1e5StJh45BBciYTRXSd25UEPVuesF9yog62tGAQtHjXajPPdbRCHuWS6T8XA2ECKADdw4Ef", "identifier": "78412e3a2296a40de124307b6485bd19833e2e34", @@ -164,9 +140,6 @@ "wif": "L5KhaMvPYRW1ZoFmRjUtxxPypQ94m6BcDrPhqArhggdaTbbAFJEF", "pubKey": "02d2b36900396c9282fa14628566582f206a5dd0bcc8d5e892611806cafb0301f0", "chainCode": "637807030d55d01f9a0cb3a7839515d796bd07706386a6eddf06cc29a65a0e29", - "hex": "0488b21e0478412e3afffffffe637807030d55d01f9a0cb3a7839515d796bd07706386a6eddf06cc29a65a0e2902d2b36900396c9282fa14628566582f206a5dd0bcc8d5e892611806cafb0301f0", - "hexPriv": "0488ade40478412e3afffffffe637807030d55d01f9a0cb3a7839515d796bd07706386a6eddf06cc29a65a0e2900f1c7c871a54a804afe328b4c83a1c33b8e5ff48f5087273f04efa83b247d6a2d", - "network": "bitcoin", "base58": "xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL", "base58Priv": "xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc", "identifier": "31a507b815593dfc51ffc7245ae7e5aee304246e", @@ -179,9 +152,6 @@ "wif": "L3WAYNAZPxx1fr7KCz7GN9nD5qMBnNiqEJNJMU1z9MMaannAt4aK", "pubKey": "024d902e1a2fc7a8755ab5b694c575fce742c48d9ff192e63df5193e4c7afe1f9c", "chainCode": "9452b549be8cea3ecb7a84bec10dcfd94afe4d129ebfd3b3cb58eedf394ed271", - "hex": "0488b21e0531a507b8000000029452b549be8cea3ecb7a84bec10dcfd94afe4d129ebfd3b3cb58eedf394ed271024d902e1a2fc7a8755ab5b694c575fce742c48d9ff192e63df5193e4c7afe1f9c", - "hexPriv": "0488ade40531a507b8000000029452b549be8cea3ecb7a84bec10dcfd94afe4d129ebfd3b3cb58eedf394ed27100bb7d39bdb83ecf58f2fd82b6d918341cbef428661ef01ab97c28a4842125ac23", - "network": "bitcoin", "base58": "xpub6FnCn6nSzZAw5Tw7cgR9bi15UV96gLZhjDstkXXxvCLsUXBGXPdSnLFbdpq8p9HmGsApME5hQTZ3emM2rnY5agb9rXpVGyy3bdW6EEgAtqt", "base58Priv": "xprvA2nrNbFZABcdryreWet9Ea4LvTJcGsqrMzxHx98MMrotbir7yrKCEXw7nadnHM8Dq38EGfSh6dqA9QWTyefMLEcBYJUuekgW4BYPJcr9E7j", "identifier": "26132fdbe7bf89cbc64cf8dafa3f9f88b8666220", @@ -189,6 +159,37 @@ "address": "14UKfRV9ZPUp6ZC9PLhqbRtxdihW9em3xt" } ] + }, + { + "network": "litecoin", + "master": { + "seed": "000102030405060708090a0b0c0d0e0f", + "wif": "TAroS5Knm8GZcnpPycBgzjwwDLWMyQjDrcuGPPoArgrbW7Ln22qp", + "pubKey": "0339a36013301597daef41fbe593a02cc513d0b55527ec2df1050e2e8ff49c85c2", + "chainCode": "873dff81c02f525623fd1fe5167eac3a55a049de3d314bb42ee227ffed37d508", + "hex": "0488b21e000000000000000000873dff81c02f525623fd1fe5167eac3a55a049de3d314bb42ee227ffed37d5080339a36013301597daef41fbe593a02cc513d0b55527ec2df1050e2e8ff49c85c2", + "hexPriv": "019d9cfe000000000000000000873dff81c02f525623fd1fe5167eac3a55a049de3d314bb42ee227ffed37d50800e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35", + "base58": "Ltub2SSUS19CirucWFod2ZsYA2J4v4U76YiCXHdcQttnoiy5aGanFHCPDBX7utfG6f95u1cUbZJNafmvzNCzZZJTw1EmyFoL8u1gJbGM8ipu491", + "base58Priv": "Ltpv71G8qDifUiNetP6nmxPA5STrUVmv2J9YSmXajv8VsYBUyuPhvN9xCaQrfX2wo5xxJNtEazYCFRUu5FmokYMM79pcqz8pcdo4rNXAFPgyB4k", + "identifier": "3442193e1bb70916e914552172cd4e2dbc9df811", + "fingerprint": "3442193e", + "address": "LPzGaoLUtXFkmNo3u1chDxGxDnSaBQTTxm" + }, + "children": [ + { + "description": "m/0'", + "m": 0, + "hardened": true, + "wif": "TB22qU2V9EJCVKJ8cdYaTfvDhnYcCzthcWgFm1k6hbvbKM1NLxoL", + "pubKey": "035a784662a4a20a65bf6aab9ae98a6c068a81c52e4b032c0fb5400c706cfccc56", + "chainCode": "47fdacbd0f1097043b78c63c20c34ef4ed9a111d980047ad16282c7ae6236141", + "base58": "Ltub2UhtRiSfp82berwLEKkB34QBEt2TUdCDCu4WNzGumvAMwYsxfWjULKsXhADxqy3cuDu3TnqoKJr1xmB8Wb2qzthWAtbb4CutpXPuSU1YMgG", + "base58Priv": "Ltpv73XYpw28ZyVe2zEVyiFnxUZxoKLGQNdZ8NxUi1WcqjNmMBgtLbh3KimGSnPHCoLv1RmvxHs4dnKmo1oXQ8dXuDu8uroxrbVxZPA1gXboYvx", + "identifier": "5c1bd648ed23aa5fd50ba52b2457c11e9e80a6a7", + "fingerprint": "5c1bd648", + "address": "LTcyn1jun6g9hvxtsT7cqMRSyix7AULC76" + } + ] } ], "invalid": { @@ -196,6 +197,15 @@ { "exception": "Invalid checksum", "string": "xprvQQQQQQQQQQQQQQQQCviVfJSKyQ1mDYahRjijr5idH2WwLsEd4Hsb2Tyh8RfQMuPh7f7RtyzTtdrbdqqsunu5Mm3wDvUAKRHSC34sJ7in334" + }, + { + "exception": "Could not find network for 0", + "string": "1111111111111adADjFaSNPxwXqLjHLj4mBfYxuewDPbw9hEj1uaXCzMxRPXDFF3cUoezTFYom4sEmEVSQmENPPR315cFk9YUFVek73wE9" + }, + { + "exception": "Network doesn\\'t match", + "string": "Ltpv73XYpw28ZyVe2zEVyiFnxUZxoKLGQNdZ8NxUi1WcqjNmMBgtLbh3KimGSnPHCoLv1RmvxHs4dnKmo1oXQ8dXuDu8uroxrbVxZPA1gXboYvx", + "network": "bitcoin" } ], "fromBuffer": [ @@ -216,7 +226,7 @@ "hex": "0488b21e0000000000ffffffff7ffc03d4a1f2fb41ef93374c69e4d19e42e27c9a87ec8b799a205eecd3b43b5f02948d03e260a571e21bcf5bfd8e3b6602800df154906e06b2bc88eee410aee355" }, { - "exception": "Could not find version 22222222", + "exception": "Could not find network for 22222222", "hex": "222222220000000000000000007ffc03d4a1f2fb41ef93374c69e4d19e42e27c9a87ec8b799a205eecd3b43b5f02948d03e260a571e21bcf5bfd8e3b6602800df154906e06b2bc88eee410aee355" }, { diff --git a/test/fixtures/network.json b/test/fixtures/network.json index e2ed7674c..b638221c9 100644 --- a/test/fixtures/network.json +++ b/test/fixtures/network.json @@ -1,84 +1,151 @@ { - "valid": [ - { - "description": "when txSize < 1kb", - "network": "bitcoin", - "txSize": 1, - "fee": 10000 - }, - { - "description": "when txSize >= 1kb", - "network": "bitcoin", - "txSize": 1000, - "fee": 10000 - }, - { - "description": "rounding", - "network": "bitcoin", - "txSize": 2800, - "fee": 30000 - }, - { - "description": "when outputs.value > DUST_SOFT_LIMIT, feePerKb is used", - "network": "dogecoin", - "txSize": 1000, - "outputs": [ - { - "value": 100000000 + "valid": { + "constants": [ + { + "network": "bitcoin", + "bip32": { + "private": "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi", + "public": "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8" } - ], - "fee": 100000000 - }, - { - "description": "when not every outputs.value > DUST_SOFT_LIMIT", - "network": "dogecoin", - "txSize": 1000, - "outputs": [ - { - "value": 99999999 - }, - { - "value": 99999999 + }, + { + "network": "testnet", + "bip32": { + "private": "tprv8ZgxMBicQKsPeDgjzdC36fs6bMjGApWDNLR9erAXMs5skhMv36j9MV5ecvfavji5khqjWaWSFhN3YcCUUdiKH6isR4Pwy3U5y5egddBr16m", + "public": "tpubD6NzVbkrYhZ4XgiXtGrdW5XDAPFCL9h7we1vwNCpn8tGbBcgfVYjXyhWo4E1xkh56hjod1RhGjxbaTLV3X4FyWuejifB9jusQ46QzG87VKp" } - ], - "fee": 300000000 - }, - { - "description": "rounding", - "network": "dogecoin", - "txSize": 2800, - "fee": 300000000 - }, - { - "description": "when outputs.value > DUST_SOFT_LIMIT, feePerKb is used", - "network": "litecoin", - "txSize": 1000, - "outputs": [ - { - "value": 100000 + }, + { + "network": "litecoin", + "bip32": { + "private": "Ltpv71G8qDifUiNetP6nmxPA5STrUVmv2J9YSmXajv8VsYBUyuPhvN9xCaQrfX2wo5xxJNtEazYCFRUu5FmokYMM79pcqz8pcdo4rNXAFPgyB4k", + "public": "Ltub2SSUS19CirucWFod2ZsYA2J4v4U76YiCXHdcQttnoiy5aGanFHCPDBX7utfG6f95u1cUbZJNafmvzNCzZZJTw1EmyFoL8u1gJbGM8ipu491" } - ], - "fee": 100000 - }, - { - "description": "when not every outputs.value > DUST_SOFT_LIMIT", - "network": "litecoin", - "txSize": 1000, - "outputs": [ - { - "value": 99999 - }, - { - "value": 99999 + }, + { + "network": "dogecoin", + "bip32": { + "private": "dgpv51eADS3spNJh9Gjth94XcPwAczvQaDJs9rqx11kvxKs6r3Ek8AgERHhjLs6mzXQFHRzQqGwqdeoDkZmr8jQMBfi43b7sT3sx3cCSk5fGeUR", + "public": "dgub8kXBZ7ymNWy2S8Q3jNgVjFUm5ZJ3QLLaSTdAA89ukSv7Q6MSXwE14b7Nv6eDpE9JJXinTKc8LeLVu19uDPrm5uJuhpKNzV2kAgncwo6bNpP" } - ], - "fee": 300000 - }, - { - "description": "rounding", - "network": "litecoin", - "txSize": 2800, - "fee": 300000 - } - ] + }, + { + "network": "viacoin", + "bip32": { + "private": "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi", + "public": "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8" + } + }, + { + "network": "viacointestnet", + "bip32": { + "private": "tprv8ZgxMBicQKsPeDgjzdC36fs6bMjGApWDNLR9erAXMs5skhMv36j9MV5ecvfavji5khqjWaWSFhN3YcCUUdiKH6isR4Pwy3U5y5egddBr16m", + "public": "tpubD6NzVbkrYhZ4XgiXtGrdW5XDAPFCL9h7we1vwNCpn8tGbBcgfVYjXyhWo4E1xkh56hjod1RhGjxbaTLV3X4FyWuejifB9jusQ46QzG87VKp" + } + }, + { + "network": "gamerscoin", + "bip32": { + "private": "Ltpv71G8qDifUiNetP6nmxPA5STrUVmv2J9YSmXajv8VsYBUyuPhvN9xCaQrfX2wo5xxJNtEazYCFRUu5FmokYMM79pcqz8pcdo4rNXAFPgyB4k", + "public": "Ltub2SSUS19CirucWFod2ZsYA2J4v4U76YiCXHdcQttnoiy5aGanFHCPDBX7utfG6f95u1cUbZJNafmvzNCzZZJTw1EmyFoL8u1gJbGM8ipu491" + } + }, + { + "network": "jumbucks", + "bip32": { + "private": "jprv5eCacBgN4Bz4zYxgVQ7RDt1a3eREhEaj8KjAcJ7YwogxGo2rmBF5kvAQS53JwZpo5wnUmJ9Q7kB6b2gQ1MzC6yaTc188hr6hXZ5t8Ruria1", + "public": "jpub1sBw1hDFtZYND339bReRb1xJbgFj6hJaVYemQgXAW9Dw9bN1JiZLJiUtHLgcTTEs1UgRGFAYm3XQPYsYJbpqj1aYPhrMsNcJHfgdAhvFZBB" + } + }, + { + "network": "zetacoin", + "bip32": { + "private": "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi", + "public": "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8" + } + } + ], + "estimateFee": [ + { + "description": "when txSize < 1kb", + "network": "bitcoin", + "txSize": 1, + "fee": 10000 + }, + { + "description": "when txSize >= 1kb", + "network": "bitcoin", + "txSize": 1000, + "fee": 10000 + }, + { + "description": "rounding", + "network": "bitcoin", + "txSize": 2800, + "fee": 30000 + }, + { + "description": "when outputs.value > DUST_SOFT_LIMIT, feePerKb is used", + "network": "dogecoin", + "txSize": 1000, + "outputs": [ + { + "value": 100000000 + } + ], + "fee": 100000000 + }, + { + "description": "when not every outputs.value > DUST_SOFT_LIMIT", + "network": "dogecoin", + "txSize": 1000, + "outputs": [ + { + "value": 99999999 + }, + { + "value": 99999999 + } + ], + "fee": 300000000 + }, + { + "description": "rounding", + "network": "dogecoin", + "txSize": 2800, + "fee": 300000000 + }, + { + "description": "when outputs.value > DUST_SOFT_LIMIT, feePerKb is used", + "network": "litecoin", + "txSize": 1000, + "outputs": [ + { + "value": 100000 + } + ], + "fee": 100000 + }, + { + "description": "when not every outputs.value > DUST_SOFT_LIMIT", + "network": "litecoin", + "txSize": 1000, + "outputs": [ + { + "value": 99999 + }, + { + "value": 99999 + } + ], + "fee": 300000 + }, + { + "description": "rounding", + "network": "litecoin", + "txSize": 2800, + "fee": 300000 + } + ] + } } diff --git a/test/fixtures/scripts.json b/test/fixtures/scripts.json index 666ea592e..e79a6e76a 100644 --- a/test/fixtures/scripts.json +++ b/test/fixtures/scripts.json @@ -48,48 +48,139 @@ "redeemScriptSig": "OP_0 304402207515cf147d201f411092e6be5a64a6006f9308fad7b2a8fdaab22cd86ce764c202200974b8aca7bf51dbf54150d3884e1ae04f675637b926ec33bf75939446f6ca2801 3045022100ef253c1faa39e65115872519e5f0a33bbecf430c0f35cf562beabbad4da24d8d02201742be8ee49812a73adea3007c9641ce6725c32cd44ddb8e3a3af460015d140501", "scriptSig": "OP_0 304402207515cf147d201f411092e6be5a64a6006f9308fad7b2a8fdaab22cd86ce764c202200974b8aca7bf51dbf54150d3884e1ae04f675637b926ec33bf75939446f6ca2801 3045022100ef253c1faa39e65115872519e5f0a33bbecf430c0f35cf562beabbad4da24d8d02201742be8ee49812a73adea3007c9641ce6725c32cd44ddb8e3a3af460015d140501 522102359c6e3f04cefbf089cf1d6670dc47c3fb4df68e2bad1fa5a369f9ce4b42bbd1210395a9d84d47d524548f79f435758c01faec5da2b7e551d3b8c995b7e06326ae4a52ae", "scriptPubKey": "OP_HASH160 722ff0bc2c3f47b35c20df646c395594da24e90e OP_EQUAL" + }, + { + "type": "nulldata", + "data": "deadffffffffffffffffffffffffffffffffbeef", + "scriptPubKey": "OP_RETURN deadffffffffffffffffffffffffffffffffbeef" + }, + { + "type": "nonstandard", + "typeIncomplete": "multisig", + "pubKeys": [ + "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", + "02b80011a883a0fd621ad46dfc405df1e74bf075cbaf700fd4aebef6e96f848340", + "024289801366bcee6172b771cf5a7f13aaecd237a0b9a1ff9d769cabc2e6b70a34" + ], + "signatures": [ + null, + "3044022001ab168e80b863fdec694350b587339bb72a37108ac3c989849251444d13ebba02201811272023e3c1038478eb972a82d3ad431bfc2408e88e4da990f1a7ecbb263901", + "3045022100aaeb7204c17eee2f2c4ff1c9f8b39b79e75e7fbf33e92cc67ac51be8f15b75f90220659eee314a4943a6384d2b154fa5821ef7a084814d7ee2c6f9f7f0ffb53be34b01" + ], + "scriptSig": "OP_0 OP_0 3044022001ab168e80b863fdec694350b587339bb72a37108ac3c989849251444d13ebba02201811272023e3c1038478eb972a82d3ad431bfc2408e88e4da990f1a7ecbb263901 3045022100aaeb7204c17eee2f2c4ff1c9f8b39b79e75e7fbf33e92cc67ac51be8f15b75f90220659eee314a4943a6384d2b154fa5821ef7a084814d7ee2c6f9f7f0ffb53be34b01" + }, + { + "type": "nonstandard", + "typeIncomplete": "multisig", + "pubKeys": [ + "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", + "02b80011a883a0fd621ad46dfc405df1e74bf075cbaf700fd4aebef6e96f848340", + "024289801366bcee6172b771cf5a7f13aaecd237a0b9a1ff9d769cabc2e6b70a34" + ], + "signatures": [ + null, + null, + null + ], + "scriptSig": "OP_0 OP_0 OP_0 OP_0" + }, + { + "type": "nonstandard", + "typeIncomplete": "scripthash", + "pubKeys": [ + "0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8", + "04c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a" + ], + "signatures": [ + null, + "30450221009c92c1ae1767ac04e424da7f6db045d979b08cde86b1ddba48621d59a109d818022004f5bb21ad72255177270abaeb2d7940ac18f1e5ca1f53db4f3fd1045647a8a801" + ], + "redeemScript": "OP_2 0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8 04c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a OP_2 OP_CHECKMULTISIG", + "redeemScriptSig": "OP_0 OP_0 30450221009c92c1ae1767ac04e424da7f6db045d979b08cde86b1ddba48621d59a109d818022004f5bb21ad72255177270abaeb2d7940ac18f1e5ca1f53db4f3fd1045647a8a801", + "scriptSig": "OP_0 OP_0 30450221009c92c1ae1767ac04e424da7f6db045d979b08cde86b1ddba48621d59a109d818022004f5bb21ad72255177270abaeb2d7940ac18f1e5ca1f53db4f3fd1045647a8a801 52410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b84104c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a52ae" } ], "invalid": { - "classify": [ + "isPubKeyHashInput": [ { - "description": "multisig output : m > n", - "scriptPubKey": "OP_2 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 02b80011a883a0fd621ad46dfc405df1e74bf075cbaf700fd4aebef6e96f848340 OP_1 OP_CHECKMULTISIG" + "description": "pubKeyHash input : extraneous data", + "scriptSig": "304402207515cf147d201f411092e6be5a64a6006f9308fad7b2a8fdaab22cd86ce764c202200974b8aca7bf51dbf54150d3884e1ae04f675637b926ec33bf75939446f6ca2801 02359c6e3f04cefbf089cf1d6670dc47c3fb4df68e2bad1fa5a369f9ce4b42bbd1 ffffffff" + } + ], + "isScriptHashInput": [ + { + "description": "redeemScript not data", + "scriptSig": "OP_0 304402207515cf147d201f411092e6be5a64a6006f9308fad7b2a8fdaab22cd86ce764c202200974b8aca7bf51dbf54150d3884e1ae04f675637b926ec33bf75939446f6ca2801 3045022100ef253c1faa39e65115872519e5f0a33bbecf430c0f35cf562beabbad4da24d8d02201742be8ee49812a73adea3007c9641ce6725c32cd44ddb8e3a3af460015d140501 OP_RESERVED" }, { - "description": "multisig output : n === 0", - "scriptPubKey": "OP_0 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 02b80011a883a0fd621ad46dfc405df1e74bf075cbaf700fd4aebef6e96f848340 OP_0 OP_CHECKMULTISIG" + "description": "signature forms invalid script", + "scriptSig": "OP_0 3045022100e12b17b3a4c80c401a1687487bd2bafee9e5f1f8f1ffc6180ce186672ad7b43a02205e316d1e5e71822f5ef301b694e578fa9c94af4f5f098c952c833f4691307f4e01" + } + ], + "isPubKeyInput": [ + { + "description": "non-canonical signature", + "scriptSig": "304402207515cf147d201f411092e6be5a64a6006f9308fad7b2a8fdaab22cd86ce764c202200974b8aca7bf51dbf54150d3884e1ae04f675637b926ec33bf7593ffffffffffffffff" + } + ], + "isMultisigOutput": [ + { + "description": "OP_CHECKMULTISIG not found", + "scriptPubKey": "OP_0 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 02b80011a883a0fd621ad46dfc405df1e74bf075cbaf700fd4aebef6e96f848340 OP_2 OP_HASH160" }, { - "description": "multisig output : not (m <= len(pubKeys) <= n)", - "scriptPubKey": "OP_2 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 02b80011a883a0fd621ad46dfc405df1e74bf075cbaf700fd4aebef6e96f848340 024289801366bcee6172b771cf5a7f13aaecd237a0b9a1ff9d769cabc2e6b70a34 OP_2 OP_CHECKMULTISIG" + "description": "less than 4 chunks", + "scriptPubKey": "OP_0 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 OP_HASH160" }, { - "description": "multisig output : m not a small int", - "scriptPubKey": "OP_HASH160 024289801366bcee6172b771cf5a7f13aaecd237a0b9a1ff9d769cabc2e6b70a34 OP_1 OP_CHECKMULTISIG" + "description": "m === 0", + "scriptPubKey": "OP_0 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 02b80011a883a0fd621ad46dfc405df1e74bf075cbaf700fd4aebef6e96f848340 OP_2 OP_CHECKMULTISIG" }, { - "description": "multisig output : n not a small int", - "scriptPubKey": "OP_1 024289801366bcee6172b771cf5a7f13aaecd237a0b9a1ff9d769cabc2e6b70a34 OP_HASH160 OP_CHECKMULTISIG" + "description": "m < OP_1", + "scriptPubKey": "OP_1NEGATE 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 02b80011a883a0fd621ad46dfc405df1e74bf075cbaf700fd4aebef6e96f848340 OP_2 OP_CHECKMULTISIG" }, { - "description": "multisig output : non-canonical pubKey (bad length)", + "description": "m > OP_16", + "scriptPubKey": "OP_NOP 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 02b80011a883a0fd621ad46dfc405df1e74bf075cbaf700fd4aebef6e96f848340 OP_2 OP_CHECKMULTISIG" + }, + { + "description": "n === 0", + "scriptPubKey": "OP_1 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 02b80011a883a0fd621ad46dfc405df1e74bf075cbaf700fd4aebef6e96f848340 OP_0 OP_CHECKMULTISIG" + }, + { + "description": "n < OP_1", + "scriptPubKey": "OP_1 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 02b80011a883a0fd621ad46dfc405df1e74bf075cbaf700fd4aebef6e96f848340 OP_1NEGATE OP_CHECKMULTISIG" + }, + { + "description": "n > OP_16", + "scriptPubKey": "OP_1 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 02b80011a883a0fd621ad46dfc405df1e74bf075cbaf700fd4aebef6e96f848340 OP_NOP OP_CHECKMULTISIG" + }, + { + "description": "n < m", + "scriptPubKey": "OP_2 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 02b80011a883a0fd621ad46dfc405df1e74bf075cbaf700fd4aebef6e96f848340 OP_1 OP_CHECKMULTISIG" + }, + { + "description": "n < len(pubKeys)", + "scriptPubKey": "OP_2 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 02b80011a883a0fd621ad46dfc405df1e74bf075cbaf700fd4aebef6e96f848340 024289801366bcee6172b771cf5a7f13aaecd237a0b9a1ff9d769cabc2e6b70a34 OP_2 OP_CHECKMULTISIG" + }, + { + "description": "non-canonical pubKey (bad length)", "scriptPubKey": "OP_1 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ffff OP_1 OP_CHECKMULTISIG" } ], - "multisig": [ + "multisigInput": [ { - "exception": "Not enough pubKeys provided", - "m": 4, + "description": "Not enough signatures provided", + "type": "multisig", "pubKeys": [ - "02ea1297665dd733d444f31ec2581020004892cdaaf3dd6c0107c615afb839785f", - "02fab2dea1458990793f56f42e4a47dbf35a12a351f26fa5d7e0cc7447eaafa21f", - "036c6802ce7e8113723dd92cdb852e492ebb157a871ca532c3cb9ed08248ff0e19" + "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", + "02b80011a883a0fd621ad46dfc405df1e74bf075cbaf700fd4aebef6e96f848340" ], "signatures": [ - "304402207515cf147d201f411092e6be5a64a6006f9308fad7b2a8fdaab22cd86ce764c202200974b8aca7bf51dbf54150d3884e1ae04f675637b926ec33bf75939446f6ca2801" - ], - "scriptPubKey": true + null, + null + ] }, { "exception": "Not enough signatures provided", @@ -99,8 +190,33 @@ ], "signatures": [ "304402207515cf147d201f411092e6be5a64a6006f9308fad7b2a8fdaab22cd86ce764c202200974b8aca7bf51dbf54150d3884e1ae04f675637b926ec33bf75939446f6ca2801" + ] + }, + { + "exception": "Too many signatures provided", + "pubKeys": [ + "02359c6e3f04cefbf089cf1d6670dc47c3fb4df68e2bad1fa5a369f9ce4b42bbd1", + "0395a9d84d47d524548f79f435758c01faec5da2b7e551d3b8c995b7e06326ae4a" ], - "scriptPubKey": false + "signatures": [ + "304402207515cf147d201f411092e6be5a64a6006f9308fad7b2a8fdaab22cd86ce764c202200974b8aca7bf51dbf54150d3884e1ae04f675637b926ec33bf75939446f6ca2801", + "3045022100ef253c1faa39e65115872519e5f0a33bbecf430c0f35cf562beabbad4da24d8d02201742be8ee49812a73adea3007c9641ce6725c32cd44ddb8e3a3af460015d140501", + "3045022100ef253c1faa39e65115872519e5f0a33bbecf430c0f35cf562beabbad4da24d8d02201742be8ee49812a73adea3007c9641ce6725c32cd44ddb8e3a3af460015d140501" + ] + } + ], + "multisigOutput": [ + { + "exception": "Not enough pubKeys provided", + "m": 4, + "pubKeys": [ + "02ea1297665dd733d444f31ec2581020004892cdaaf3dd6c0107c615afb839785f", + "02fab2dea1458990793f56f42e4a47dbf35a12a351f26fa5d7e0cc7447eaafa21f", + "036c6802ce7e8113723dd92cdb852e492ebb157a871ca532c3cb9ed08248ff0e19" + ], + "signatures": [ + "304402207515cf147d201f411092e6be5a64a6006f9308fad7b2a8fdaab22cd86ce764c202200974b8aca7bf51dbf54150d3884e1ae04f675637b926ec33bf75939446f6ca2801" + ] } ] } diff --git a/test/fixtures/transaction.json b/test/fixtures/transaction.json index c47332330..043dfed03 100644 --- a/test/fixtures/transaction.json +++ b/test/fixtures/transaction.json @@ -2,7 +2,7 @@ "valid": [ { "description": "Standard transaction (1:1)", - "txid": "a0ff943d3f644d8832b1fa74be4d0ad2577615dc28a7ef74ff8c271b603a082a", + "id": "a0ff943d3f644d8832b1fa74be4d0ad2577615dc28a7ef74ff8c271b603a082a", "hash": "2a083a601b278cff74efa728dc157657d20a4dbe74fab132884d643f3d94ffa0", "raw": { "version": 1, @@ -10,13 +10,12 @@ { "hash": "f1fefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefe", "index": 0, - "script": "4830450221008732a460737d956fd94d49a31890b2908f7ed7025a9c1d0f25e43290f1841716022004fa7d608a291d44ebbbebbadaac18f943031e7de39ef3bf9920998c43e60c0401210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", - "sequence": 4294967295 + "script": "30450221008732a460737d956fd94d49a31890b2908f7ed7025a9c1d0f25e43290f1841716022004fa7d608a291d44ebbbebbadaac18f943031e7de39ef3bf9920998c43e60c0401 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798" } ], "outs": [ { - "script": "76a914c42e7ef92fdb603af844d064faad95db9bcdfd3d88ac", + "script": "OP_DUP OP_HASH160 c42e7ef92fdb603af844d064faad95db9bcdfd3d OP_EQUALVERIFY OP_CHECKSIG", "value": 100000 } ], @@ -26,41 +25,41 @@ }, { "description": "Standard transaction (2:2)", - "txid": "eb1c3a8b1bd7d38a6bd8f3c48e8fc950cf3ddf9b34e91594d8c1b31e0bcf8240", - "hash": "4082cf0b1eb3c1d89415e9349bdf3dcf50c98f8ec4f3d86b8ad3d71b8b3a1ceb", + "id": "fcdd6d89c43e76dcff94285d9b6e31d5c60cb5e397a76ebc4920befad30907bc", + "hash": "bc0709d3fabe2049bc6ea797e3b50cc6d5316e9b5d2894ffdc763ec4896dddfc", "raw": { "version": 1, "ins": [ { "hash": "f1fefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefe", "index": 0, - "script": "483045022100e661badd8d2cf1af27eb3b82e61b5d3f5d5512084591796ae31487f5b82df948022006df3c2a2cac79f68e4b179f4bbb8185a0bb3c4a2486d4405c59b2ba07a74c2101210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", + "script": "3045022100e661badd8d2cf1af27eb3b82e61b5d3f5d5512084591796ae31487f5b82df948022006df3c2a2cac79f68e4b179f4bbb8185a0bb3c4a2486d4405c59b2ba07a74c2101 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", "sequence": 4294967295 }, { "hash": "f2fefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefe", "index": 1, - "script": "483045022100be54a46a44fb7e6bf4ebf348061d0dace7ddcbb92d4147ce181cf4789c7061f0022068ccab2a89a47fc29bb5074bca99ae846ab446eecf3c3aaeb238a13838783c78012102c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee517a9147ccb85f0ab2d599bc17246c98babd5a20b1cdc7687", - "sequence": 4294967295 + "script": "3045022100be54a46a44fb7e6bf4ebf348061d0dace7ddcbb92d4147ce181cf4789c7061f0022068ccab2a89a47fc29bb5074bca99ae846ab446eecf3c3aaeb238a13838783c7801 02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5 a9147ccb85f0ab2d599bc17246c98babd5a20b1cdc7687", + "sequence": 2147483648 } ], "outs": [ { - "script": "76a914c42e7ef92fdb603af844d064faad95db9bcdfd3d88ac", + "script": "OP_DUP OP_HASH160 c42e7ef92fdb603af844d064faad95db9bcdfd3d OP_EQUALVERIFY OP_CHECKSIG", "value": 50000 }, { - "script": "a9147ccb85f0ab2d599bc17246c98babd5a20b1cdc7687", + "script": "OP_HASH160 7ccb85f0ab2d599bc17246c98babd5a20b1cdc76 OP_EQUAL", "value": 150000 } ], "locktime": 0 }, - "hex": "0100000002f1fefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefe000000006b483045022100e661badd8d2cf1af27eb3b82e61b5d3f5d5512084591796ae31487f5b82df948022006df3c2a2cac79f68e4b179f4bbb8185a0bb3c4a2486d4405c59b2ba07a74c2101210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798fffffffff2fefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefe0100000083483045022100be54a46a44fb7e6bf4ebf348061d0dace7ddcbb92d4147ce181cf4789c7061f0022068ccab2a89a47fc29bb5074bca99ae846ab446eecf3c3aaeb238a13838783c78012102c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee517a9147ccb85f0ab2d599bc17246c98babd5a20b1cdc7687ffffffff0250c30000000000001976a914c42e7ef92fdb603af844d064faad95db9bcdfd3d88acf04902000000000017a9147ccb85f0ab2d599bc17246c98babd5a20b1cdc768700000000" + "hex": "0100000002f1fefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefe000000006b483045022100e661badd8d2cf1af27eb3b82e61b5d3f5d5512084591796ae31487f5b82df948022006df3c2a2cac79f68e4b179f4bbb8185a0bb3c4a2486d4405c59b2ba07a74c2101210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798fffffffff2fefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefe0100000083483045022100be54a46a44fb7e6bf4ebf348061d0dace7ddcbb92d4147ce181cf4789c7061f0022068ccab2a89a47fc29bb5074bca99ae846ab446eecf3c3aaeb238a13838783c78012102c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee517a9147ccb85f0ab2d599bc17246c98babd5a20b1cdc7687000000800250c30000000000001976a914c42e7ef92fdb603af844d064faad95db9bcdfd3d88acf04902000000000017a9147ccb85f0ab2d599bc17246c98babd5a20b1cdc768700000000" }, { "description": "Standard transaction (14:2)", - "txid": "39d57bc27f72e904d81f6b5ef7b4e6e17fa33a06b11e5114a43435830d7b5563", + "id": "39d57bc27f72e904d81f6b5ef7b4e6e17fa33a06b11e5114a43435830d7b5563", "hash": "63557b0d833534a414511eb1063aa37fe1e6b4f75e6b1fd804e9727fc27bd539", "raw": { "version": 1, @@ -69,100 +68,111 @@ { "hash": "e7b73e229790c1e79a02f0c871813b3cf26a4156c5b8d942e88b38fe8d3f43a0", "index": 0, - "script": "493046022100fd3d8fef44fb0962ba3f07bee1d4cafb84e60e38e6c7d9274504b3638a8d2f520221009fce009044e615b6883d4bf62e04c48f9fe236e19d644b082b2f0ae5c98e045c014104aa592c859fd00ed2a02609aad3a1bf72e0b42de67713e632c70a33cc488c15598a0fb419370a54d1c275b44380e8777fc01b6dc3cd43a416c6bab0e30dc1e19f", - "sequence": 4294967295 + "script": "3046022100fd3d8fef44fb0962ba3f07bee1d4cafb84e60e38e6c7d9274504b3638a8d2f520221009fce009044e615b6883d4bf62e04c48f9fe236e19d644b082b2f0ae5c98e045c01 04aa592c859fd00ed2a02609aad3a1bf72e0b42de67713e632c70a33cc488c15598a0fb419370a54d1c275b44380e8777fc01b6dc3cd43a416c6bab0e30dc1e19f", + "sequence": null }, { "hash": "7bfc005f3880a606027c7cd7dd02a0f6a6572eeb84a91aa158311be13695a7ea", "index": 1, - "script": "483045022100e2e61c40f26e2510b76dc72ea2f568ec514fce185c719e18bca9caaef2b20e9e02207f1100fc79eb0584e970c7f18fb226f178951d481767b4092d50d13c50ccba8b014104aa592c859fd00ed2a02609aad3a1bf72e0b42de67713e632c70a33cc488c15598a0fb419370a54d1c275b44380e8777fc01b6dc3cd43a416c6bab0e30dc1e19f", - "sequence": 4294967295 + "script": "3045022100e2e61c40f26e2510b76dc72ea2f568ec514fce185c719e18bca9caaef2b20e9e02207f1100fc79eb0584e970c7f18fb226f178951d481767b4092d50d13c50ccba8b01 04aa592c859fd00ed2a02609aad3a1bf72e0b42de67713e632c70a33cc488c15598a0fb419370a54d1c275b44380e8777fc01b6dc3cd43a416c6bab0e30dc1e19f" }, { "hash": "0e0f8e6bf951fbb84d7d8ef833a1cbf5bb046ea7251973ac6e7661c755386ee3", "index": 1, - "script": "473044022048f1611e403710f248f7caf479965a6a5f63cdfbd9a714fef4ec1b68331ade1d022074919e79376c363d4575b2fc21513d5949471703efebd4c5ca2885e810eb1fa4014104aa592c859fd00ed2a02609aad3a1bf72e0b42de67713e632c70a33cc488c15598a0fb419370a54d1c275b44380e8777fc01b6dc3cd43a416c6bab0e30dc1e19f", - "sequence": 4294967295 + "script": "3044022048f1611e403710f248f7caf479965a6a5f63cdfbd9a714fef4ec1b68331ade1d022074919e79376c363d4575b2fc21513d5949471703efebd4c5ca2885e810eb1fa401 04aa592c859fd00ed2a02609aad3a1bf72e0b42de67713e632c70a33cc488c15598a0fb419370a54d1c275b44380e8777fc01b6dc3cd43a416c6bab0e30dc1e19f" }, { "hash": "e6f17f35bf9f0aa7a4242ab3e29edbdb74c5274bf263e53043dddb8045cb585b", "index": 0, - "script": "483045022100886c07cad489dfcf4b364af561835d5cf985f07adf8bd1d5bd6ddea82b0ce6b2022045bdcbcc2b5fc55191bb997039cf59ff70e8515c56b62f293a9add770ba26738014104aa592c859fd00ed2a02609aad3a1bf72e0b42de67713e632c70a33cc488c15598a0fb419370a54d1c275b44380e8777fc01b6dc3cd43a416c6bab0e30dc1e19f", - "sequence": 4294967295 + "script": "3045022100886c07cad489dfcf4b364af561835d5cf985f07adf8bd1d5bd6ddea82b0ce6b2022045bdcbcc2b5fc55191bb997039cf59ff70e8515c56b62f293a9add770ba2673801 04aa592c859fd00ed2a02609aad3a1bf72e0b42de67713e632c70a33cc488c15598a0fb419370a54d1c275b44380e8777fc01b6dc3cd43a416c6bab0e30dc1e19f" }, { "hash": "e6f17f35bf9f0aa7a4242ab3e29edbdb74c5274bf263e53043dddb8045cb585b", "index": 1, - "script": "4730440220535d49b819fdf294d27d82aff2865ed4e18580f0ca9796d793f611cb43a44f47022019584d5e300c415f642e37ba2a814a1e1106b4a9b91dc2a30fb57ceafe041181014104aa592c859fd00ed2a02609aad3a1bf72e0b42de67713e632c70a33cc488c15598a0fb419370a54d1c275b44380e8777fc01b6dc3cd43a416c6bab0e30dc1e19f", - "sequence": 4294967295 + "script": "30440220535d49b819fdf294d27d82aff2865ed4e18580f0ca9796d793f611cb43a44f47022019584d5e300c415f642e37ba2a814a1e1106b4a9b91dc2a30fb57ceafe04118101 04aa592c859fd00ed2a02609aad3a1bf72e0b42de67713e632c70a33cc488c15598a0fb419370a54d1c275b44380e8777fc01b6dc3cd43a416c6bab0e30dc1e19f" }, { "hash": "d3051677216ea53baa2e6d7f6a75434ac338438c59f314801c8496d1e6d1bf6d", "index": 1, - "script": "483045022100bf612b0fa46f49e70ab318ca3458d1ed5f59727aa782f7fac5503f54d9b43a590220358d7ed0e3cee63a5a7e972d9fad41f825d95de2fd0c5560382468610848d489014104aa592c859fd00ed2a02609aad3a1bf72e0b42de67713e632c70a33cc488c15598a0fb419370a54d1c275b44380e8777fc01b6dc3cd43a416c6bab0e30dc1e19f", - "sequence": 4294967295 + "script": "3045022100bf612b0fa46f49e70ab318ca3458d1ed5f59727aa782f7fac5503f54d9b43a590220358d7ed0e3cee63a5a7e972d9fad41f825d95de2fd0c5560382468610848d48901 04aa592c859fd00ed2a02609aad3a1bf72e0b42de67713e632c70a33cc488c15598a0fb419370a54d1c275b44380e8777fc01b6dc3cd43a416c6bab0e30dc1e19f" }, { "hash": "1e751ccc4e7d973201e9174ec78ece050ef2fadd6a108f40f76a9fa314979c31", "index": 1, - "script": "483045022006e263d5f73e05c48a603e3bd236e8314e5420721d5e9020114b93e8c9220e1102210099d3dead22f4a792123347a238c87e67b55b28a94a0bb7793144cc7ad94a0168014104aa592c859fd00ed2a02609aad3a1bf72e0b42de67713e632c70a33cc488c15598a0fb419370a54d1c275b44380e8777fc01b6dc3cd43a416c6bab0e30dc1e19f", - "sequence": 4294967295 + "script": "3045022006e263d5f73e05c48a603e3bd236e8314e5420721d5e9020114b93e8c9220e1102210099d3dead22f4a792123347a238c87e67b55b28a94a0bb7793144cc7ad94a016801 04aa592c859fd00ed2a02609aad3a1bf72e0b42de67713e632c70a33cc488c15598a0fb419370a54d1c275b44380e8777fc01b6dc3cd43a416c6bab0e30dc1e19f" }, { "hash": "25c4cf2c61743b3f4252d921d937cca942cf32e4f3fa4a544d0b26f014337084", "index": 1, - "script": "47304402207d6e87588be47bf2d97eaf427bdd992e9d6b306255711328aee38533366a88b50220623099595ae442cb77eaddb3f91753a4fc9df56fde69cfec584c7f97e05533c8014104aa592c859fd00ed2a02609aad3a1bf72e0b42de67713e632c70a33cc488c15598a0fb419370a54d1c275b44380e8777fc01b6dc3cd43a416c6bab0e30dc1e19f", - "sequence": 4294967295 + "script": "304402207d6e87588be47bf2d97eaf427bdd992e9d6b306255711328aee38533366a88b50220623099595ae442cb77eaddb3f91753a4fc9df56fde69cfec584c7f97e05533c801 04aa592c859fd00ed2a02609aad3a1bf72e0b42de67713e632c70a33cc488c15598a0fb419370a54d1c275b44380e8777fc01b6dc3cd43a416c6bab0e30dc1e19f" }, { "hash": "ecd93c87eb43c48481e6694904305349bdea94b01104579fa9f02bff66c89663", "index": 1, - "script": "473044022020f59498aee0cf82cb113768ef3cb721000346d381ff439adb4d405f791252510220448de723aa59412266fabbc689ec25dc94b1688c27a614982047513a80173514014104aa592c859fd00ed2a02609aad3a1bf72e0b42de67713e632c70a33cc488c15598a0fb419370a54d1c275b44380e8777fc01b6dc3cd43a416c6bab0e30dc1e19f", - "sequence": 4294967295 + "script": "3044022020f59498aee0cf82cb113768ef3cb721000346d381ff439adb4d405f791252510220448de723aa59412266fabbc689ec25dc94b1688c27a614982047513a8017351401 04aa592c859fd00ed2a02609aad3a1bf72e0b42de67713e632c70a33cc488c15598a0fb419370a54d1c275b44380e8777fc01b6dc3cd43a416c6bab0e30dc1e19f" }, { "hash": "a1fdc0a79ff98d5b6154176e321c22f4f8450dbd950bd013ad31135f5604411e", "index": 1, - "script": "48304502210088167867f87327f9c0db0444267ff0b6a026eedd629d8f16fe44a34c18e706bf0220675c8baebf89930e2d6e4463adefc50922653af99375242e38f5ee677418738a014104aa592c859fd00ed2a02609aad3a1bf72e0b42de67713e632c70a33cc488c15598a0fb419370a54d1c275b44380e8777fc01b6dc3cd43a416c6bab0e30dc1e19f", - "sequence": 4294967295 + "script": "304502210088167867f87327f9c0db0444267ff0b6a026eedd629d8f16fe44a34c18e706bf0220675c8baebf89930e2d6e4463adefc50922653af99375242e38f5ee677418738a01 04aa592c859fd00ed2a02609aad3a1bf72e0b42de67713e632c70a33cc488c15598a0fb419370a54d1c275b44380e8777fc01b6dc3cd43a416c6bab0e30dc1e19f" }, { "hash": "b89e8249c3573b58bf1ec7433185452dd57ab8e1daab01c3cc6ddc8b66ad3de8", "index": 0, - "script": "4830450220073d50ac5ec8388d5b3906921f9368c31ad078c8e1fb72f26d36b533f35ee327022100c398b23e6692e11dca8a1b64aae2ff70c6a781ed5ee99181b56a2f583a967cd4014104aa592c859fd00ed2a02609aad3a1bf72e0b42de67713e632c70a33cc488c15598a0fb419370a54d1c275b44380e8777fc01b6dc3cd43a416c6bab0e30dc1e19f", - "sequence": 4294967295 + "script": "30450220073d50ac5ec8388d5b3906921f9368c31ad078c8e1fb72f26d36b533f35ee327022100c398b23e6692e11dca8a1b64aae2ff70c6a781ed5ee99181b56a2f583a967cd401 04aa592c859fd00ed2a02609aad3a1bf72e0b42de67713e632c70a33cc488c15598a0fb419370a54d1c275b44380e8777fc01b6dc3cd43a416c6bab0e30dc1e19f" }, { "hash": "45ee07e182084454dacfad1e61b04ffdf9c7b01003060a6c841a01f4fff8a5a0", "index": 1, - "script": "483045022100991d1bf60c41358f08b20e53718a24e05ac0608915df4f6305a5b47cb61e5da7022003f14fc1cc5b737e2c3279a4f9be1852b49dbb3d9d6cc4c8af6a666f600dced8014104aa592c859fd00ed2a02609aad3a1bf72e0b42de67713e632c70a33cc488c15598a0fb419370a54d1c275b44380e8777fc01b6dc3cd43a416c6bab0e30dc1e19f", - "sequence": 4294967295 + "script": "3045022100991d1bf60c41358f08b20e53718a24e05ac0608915df4f6305a5b47cb61e5da7022003f14fc1cc5b737e2c3279a4f9be1852b49dbb3d9d6cc4c8af6a666f600dced801 04aa592c859fd00ed2a02609aad3a1bf72e0b42de67713e632c70a33cc488c15598a0fb419370a54d1c275b44380e8777fc01b6dc3cd43a416c6bab0e30dc1e19f" }, { "hash": "4cba12549f1d70f8e60aea8b546c8357f7c099e7c7d9d8691d6ee16e7dfa3170", "index": 1, - "script": "493046022100f14e2b0ef8a8e206db350413d204bc0a5cd779e556b1191c2d30b5ec023cde6f022100b90b2d2bf256c98a88f7c3a653b93cec7d25bb6a517db9087d11dbd189e8851c014104aa592c859fd00ed2a02609aad3a1bf72e0b42de67713e632c70a33cc488c15598a0fb419370a54d1c275b44380e8777fc01b6dc3cd43a416c6bab0e30dc1e19f", - "sequence": 4294967295 + "script": "3046022100f14e2b0ef8a8e206db350413d204bc0a5cd779e556b1191c2d30b5ec023cde6f022100b90b2d2bf256c98a88f7c3a653b93cec7d25bb6a517db9087d11dbd189e8851c01 04aa592c859fd00ed2a02609aad3a1bf72e0b42de67713e632c70a33cc488c15598a0fb419370a54d1c275b44380e8777fc01b6dc3cd43a416c6bab0e30dc1e19f" }, { "hash": "a4b3aed39eb2a1dc6eae4609d9909724e211c153927c230d02bd33add3026959", "index": 1, - "script": "483045022100a8cebb4f1c58f5ba1af91cb8bd4a2ed4e684e9605f5a9dc8b432ed00922d289d0220251145d2d56f06d936fd0c51fa884b4a6a5fafd0c3318f72fb05a5c9aa372195014104aa592c859fd00ed2a02609aad3a1bf72e0b42de67713e632c70a33cc488c15598a0fb419370a54d1c275b44380e8777fc01b6dc3cd43a416c6bab0e30dc1e19f", - "sequence": 4294967295 + "script": "3045022100a8cebb4f1c58f5ba1af91cb8bd4a2ed4e684e9605f5a9dc8b432ed00922d289d0220251145d2d56f06d936fd0c51fa884b4a6a5fafd0c3318f72fb05a5c9aa37219501 04aa592c859fd00ed2a02609aad3a1bf72e0b42de67713e632c70a33cc488c15598a0fb419370a54d1c275b44380e8777fc01b6dc3cd43a416c6bab0e30dc1e19f" } ], "outs": [ { "value": 52680000, - "script": "76a914167c3e1f10cc3b691c73afbdb211e156e3e3f25c88ac" + "script": "OP_DUP OP_HASH160 167c3e1f10cc3b691c73afbdb211e156e3e3f25c OP_EQUALVERIFY OP_CHECKSIG" }, { "value": 3032597, - "script": "76a914290f7d617b75993e770e5606335fa0999a28d71388ac" + "script": "OP_DUP OP_HASH160 290f7d617b75993e770e5606335fa0999a28d713 OP_EQUALVERIFY OP_CHECKSIG" } ] }, "hex": "010000000ee7b73e229790c1e79a02f0c871813b3cf26a4156c5b8d942e88b38fe8d3f43a0000000008c493046022100fd3d8fef44fb0962ba3f07bee1d4cafb84e60e38e6c7d9274504b3638a8d2f520221009fce009044e615b6883d4bf62e04c48f9fe236e19d644b082b2f0ae5c98e045c014104aa592c859fd00ed2a02609aad3a1bf72e0b42de67713e632c70a33cc488c15598a0fb419370a54d1c275b44380e8777fc01b6dc3cd43a416c6bab0e30dc1e19fffffffff7bfc005f3880a606027c7cd7dd02a0f6a6572eeb84a91aa158311be13695a7ea010000008b483045022100e2e61c40f26e2510b76dc72ea2f568ec514fce185c719e18bca9caaef2b20e9e02207f1100fc79eb0584e970c7f18fb226f178951d481767b4092d50d13c50ccba8b014104aa592c859fd00ed2a02609aad3a1bf72e0b42de67713e632c70a33cc488c15598a0fb419370a54d1c275b44380e8777fc01b6dc3cd43a416c6bab0e30dc1e19fffffffff0e0f8e6bf951fbb84d7d8ef833a1cbf5bb046ea7251973ac6e7661c755386ee3010000008a473044022048f1611e403710f248f7caf479965a6a5f63cdfbd9a714fef4ec1b68331ade1d022074919e79376c363d4575b2fc21513d5949471703efebd4c5ca2885e810eb1fa4014104aa592c859fd00ed2a02609aad3a1bf72e0b42de67713e632c70a33cc488c15598a0fb419370a54d1c275b44380e8777fc01b6dc3cd43a416c6bab0e30dc1e19fffffffffe6f17f35bf9f0aa7a4242ab3e29edbdb74c5274bf263e53043dddb8045cb585b000000008b483045022100886c07cad489dfcf4b364af561835d5cf985f07adf8bd1d5bd6ddea82b0ce6b2022045bdcbcc2b5fc55191bb997039cf59ff70e8515c56b62f293a9add770ba26738014104aa592c859fd00ed2a02609aad3a1bf72e0b42de67713e632c70a33cc488c15598a0fb419370a54d1c275b44380e8777fc01b6dc3cd43a416c6bab0e30dc1e19fffffffffe6f17f35bf9f0aa7a4242ab3e29edbdb74c5274bf263e53043dddb8045cb585b010000008a4730440220535d49b819fdf294d27d82aff2865ed4e18580f0ca9796d793f611cb43a44f47022019584d5e300c415f642e37ba2a814a1e1106b4a9b91dc2a30fb57ceafe041181014104aa592c859fd00ed2a02609aad3a1bf72e0b42de67713e632c70a33cc488c15598a0fb419370a54d1c275b44380e8777fc01b6dc3cd43a416c6bab0e30dc1e19fffffffffd3051677216ea53baa2e6d7f6a75434ac338438c59f314801c8496d1e6d1bf6d010000008b483045022100bf612b0fa46f49e70ab318ca3458d1ed5f59727aa782f7fac5503f54d9b43a590220358d7ed0e3cee63a5a7e972d9fad41f825d95de2fd0c5560382468610848d489014104aa592c859fd00ed2a02609aad3a1bf72e0b42de67713e632c70a33cc488c15598a0fb419370a54d1c275b44380e8777fc01b6dc3cd43a416c6bab0e30dc1e19fffffffff1e751ccc4e7d973201e9174ec78ece050ef2fadd6a108f40f76a9fa314979c31010000008b483045022006e263d5f73e05c48a603e3bd236e8314e5420721d5e9020114b93e8c9220e1102210099d3dead22f4a792123347a238c87e67b55b28a94a0bb7793144cc7ad94a0168014104aa592c859fd00ed2a02609aad3a1bf72e0b42de67713e632c70a33cc488c15598a0fb419370a54d1c275b44380e8777fc01b6dc3cd43a416c6bab0e30dc1e19fffffffff25c4cf2c61743b3f4252d921d937cca942cf32e4f3fa4a544d0b26f014337084010000008a47304402207d6e87588be47bf2d97eaf427bdd992e9d6b306255711328aee38533366a88b50220623099595ae442cb77eaddb3f91753a4fc9df56fde69cfec584c7f97e05533c8014104aa592c859fd00ed2a02609aad3a1bf72e0b42de67713e632c70a33cc488c15598a0fb419370a54d1c275b44380e8777fc01b6dc3cd43a416c6bab0e30dc1e19fffffffffecd93c87eb43c48481e6694904305349bdea94b01104579fa9f02bff66c89663010000008a473044022020f59498aee0cf82cb113768ef3cb721000346d381ff439adb4d405f791252510220448de723aa59412266fabbc689ec25dc94b1688c27a614982047513a80173514014104aa592c859fd00ed2a02609aad3a1bf72e0b42de67713e632c70a33cc488c15598a0fb419370a54d1c275b44380e8777fc01b6dc3cd43a416c6bab0e30dc1e19fffffffffa1fdc0a79ff98d5b6154176e321c22f4f8450dbd950bd013ad31135f5604411e010000008b48304502210088167867f87327f9c0db0444267ff0b6a026eedd629d8f16fe44a34c18e706bf0220675c8baebf89930e2d6e4463adefc50922653af99375242e38f5ee677418738a014104aa592c859fd00ed2a02609aad3a1bf72e0b42de67713e632c70a33cc488c15598a0fb419370a54d1c275b44380e8777fc01b6dc3cd43a416c6bab0e30dc1e19fffffffffb89e8249c3573b58bf1ec7433185452dd57ab8e1daab01c3cc6ddc8b66ad3de8000000008b4830450220073d50ac5ec8388d5b3906921f9368c31ad078c8e1fb72f26d36b533f35ee327022100c398b23e6692e11dca8a1b64aae2ff70c6a781ed5ee99181b56a2f583a967cd4014104aa592c859fd00ed2a02609aad3a1bf72e0b42de67713e632c70a33cc488c15598a0fb419370a54d1c275b44380e8777fc01b6dc3cd43a416c6bab0e30dc1e19fffffffff45ee07e182084454dacfad1e61b04ffdf9c7b01003060a6c841a01f4fff8a5a0010000008b483045022100991d1bf60c41358f08b20e53718a24e05ac0608915df4f6305a5b47cb61e5da7022003f14fc1cc5b737e2c3279a4f9be1852b49dbb3d9d6cc4c8af6a666f600dced8014104aa592c859fd00ed2a02609aad3a1bf72e0b42de67713e632c70a33cc488c15598a0fb419370a54d1c275b44380e8777fc01b6dc3cd43a416c6bab0e30dc1e19fffffffff4cba12549f1d70f8e60aea8b546c8357f7c099e7c7d9d8691d6ee16e7dfa3170010000008c493046022100f14e2b0ef8a8e206db350413d204bc0a5cd779e556b1191c2d30b5ec023cde6f022100b90b2d2bf256c98a88f7c3a653b93cec7d25bb6a517db9087d11dbd189e8851c014104aa592c859fd00ed2a02609aad3a1bf72e0b42de67713e632c70a33cc488c15598a0fb419370a54d1c275b44380e8777fc01b6dc3cd43a416c6bab0e30dc1e19fffffffffa4b3aed39eb2a1dc6eae4609d9909724e211c153927c230d02bd33add3026959010000008b483045022100a8cebb4f1c58f5ba1af91cb8bd4a2ed4e684e9605f5a9dc8b432ed00922d289d0220251145d2d56f06d936fd0c51fa884b4a6a5fafd0c3318f72fb05a5c9aa372195014104aa592c859fd00ed2a02609aad3a1bf72e0b42de67713e632c70a33cc488c15598a0fb419370a54d1c275b44380e8777fc01b6dc3cd43a416c6bab0e30dc1e19fffffffff0240d52303000000001976a914167c3e1f10cc3b691c73afbdb211e156e3e3f25c88ac15462e00000000001976a914290f7d617b75993e770e5606335fa0999a28d71388ac00000000" + }, + { + "description": "Coinbase transaction", + "id": "8e070d4eb85eb02e02dd938d6552316b9d723330707870c518064b7a0d232da3", + "hash": "a32d230d7a4b0618c57078703033729d6b3152658d93dd022eb05eb84e0d078e", + "hex": "01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff29032832051c4d696e656420627920416e74506f6f6c20626a343a45ef0454c5de8d5e5300004e2c0000ffffffff01414f1995000000001976a914b05793fe86a9f51a5f5ae3a6f07fd31932128a3f88ac00000000", + "raw": { + "version": 1, + "ins": [ + { + "hash": "0000000000000000000000000000000000000000000000000000000000000000", + "index": 4294967295, + "data": "032832051c4d696e656420627920416e74506f6f6c20626a343a45ef0454c5de8d5e5300004e2c0000", + "sequence": 4294967295 + } + ], + "outs": [ + { + "script": "OP_DUP OP_HASH160 b05793fe86a9f51a5f5ae3a6f07fd31932128a3f OP_EQUALVERIFY OP_CHECKSIG", + "value": 2501463873 + } + ], + "locktime": 0 + } } ], "invalid": { diff --git a/test/fixtures/transaction_builder.json b/test/fixtures/transaction_builder.json new file mode 100644 index 000000000..1845a703b --- /dev/null +++ b/test/fixtures/transaction_builder.json @@ -0,0 +1,513 @@ +{ + "valid": { + "build": [ + { + "description": "Transaction w/ pubKeyHash -> pubKeyHash", + "txHex": "0100000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000006b483045022100a3b254e1c10b5d039f36c05f323995d6e5a367d98dd78a13d5bbc3991b35720e022022fccea3897d594de0689601fbd486588d5bfa6915be2386db0397ee9a6e80b601210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ffffffff0110270000000000001976a914aa4d7985c57e011a8b3dd8e0e5a73aaef41629c588ac00000000", + "inputs": [ + { + "txId": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "vout": 0, + "signs": [ + { + "privKey": "KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn" + } + ] + } + ], + "outputs": [ + { + "script": "OP_DUP OP_HASH160 aa4d7985c57e011a8b3dd8e0e5a73aaef41629c5 OP_EQUALVERIFY OP_CHECKSIG", + "value": 10000 + } + ] + }, + { + "description": "Transaction w/ pubKey -> pubKeyHash", + "txHex": "0100000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000494830450221009833abb3ab49d7004c06bcc79eafd6905ada3eee91f3376ad388548034acd9a702202e84dda6ef2678c82256afcfc459aaa68e179b2bb0e6b2dc3f1410e132c5e6c301ffffffff0100f90295000000001976a914aa4d7985c57e011a8b3dd8e0e5a73aaef41629c588ac00000000", + "inputs": [ + { + "txId": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "vout": 0, + "prevTxScript": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 OP_CHECKSIG", + "signs": [ + { + "privKey": "KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn" + } + ] + } + ], + "outputs": [ + { + "script": "OP_DUP OP_HASH160 aa4d7985c57e011a8b3dd8e0e5a73aaef41629c5 OP_EQUALVERIFY OP_CHECKSIG", + "value": 2500000000 + } + ] + }, + { + "description": "Transaction w/ scriptHash(multisig 2-of-2) -> pubKeyHash", + "txHex": "0100000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000fd1b0100483045022100b7a9bab60c4307349de9571ce0bd26ebb9d68d4e9ab3f9173e1f736f1390a04a022020931ff70e87033cdd94bdf434e865993b2258065c5c222a53f29d077bcfa4480147304402206d79ad83f1ab12fc9feee9e66412de842fcbf8de0632beb4433d469f24f0fb4e022079e6df186582f2686a3292bde8e50dac36cb9bec3991995fe331e1daef7df8a4014c8752410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b84104c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a52aeffffffff0110270000000000001976a914faf1d99bf040ea9c7f8cc9f14ac6733ad75ce24688ac00000000", + "inputs": [ + { + "txId": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "vout": 0, + "signs": [ + { + "privKey": "91avARGdfge8E4tZfYLoxeJ5sGBdNJQH4kvjJoQFacbgwmaKkrx", + "redeemScript": "OP_2 0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8 04c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a OP_2 OP_CHECKMULTISIG" + }, + { + "privKey": "91avARGdfge8E4tZfYLoxeJ5sGBdNJQH4kvjJoQFacbgww7vXtT" + } + ] + } + ], + "outputs": [ + { + "script": "OP_DUP OP_HASH160 faf1d99bf040ea9c7f8cc9f14ac6733ad75ce246 OP_EQUALVERIFY OP_CHECKSIG", + "value": 10000 + } + ] + }, + { + "description": "Transaction w/ multisig 2-of-2 -> pubKeyHash", + "txHex": "0100000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000009200483045022100b7a9bab60c4307349de9571ce0bd26ebb9d68d4e9ab3f9173e1f736f1390a04a022020931ff70e87033cdd94bdf434e865993b2258065c5c222a53f29d077bcfa4480147304402206d79ad83f1ab12fc9feee9e66412de842fcbf8de0632beb4433d469f24f0fb4e022079e6df186582f2686a3292bde8e50dac36cb9bec3991995fe331e1daef7df8a401ffffffff0110270000000000001976a914faf1d99bf040ea9c7f8cc9f14ac6733ad75ce24688ac00000000", + "inputs": [ + { + "txId": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "vout": 0, + "prevTxScript": "OP_2 0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8 04c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a OP_2 OP_CHECKMULTISIG", + "signs": [ + { + "privKey": "91avARGdfge8E4tZfYLoxeJ5sGBdNJQH4kvjJoQFacbgwmaKkrx" + }, + { + "privKey": "91avARGdfge8E4tZfYLoxeJ5sGBdNJQH4kvjJoQFacbgww7vXtT" + } + ] + } + ], + "outputs": [ + { + "script": "OP_DUP OP_HASH160 faf1d99bf040ea9c7f8cc9f14ac6733ad75ce246 OP_EQUALVERIFY OP_CHECKSIG", + "value": 10000 + } + ] + }, + { + "description": "Transaction w/ multisig 2-of-2 (reverse order) -> pubKeyHash", + "txHex": "0100000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000009200483045022100b7a9bab60c4307349de9571ce0bd26ebb9d68d4e9ab3f9173e1f736f1390a04a022020931ff70e87033cdd94bdf434e865993b2258065c5c222a53f29d077bcfa4480147304402206d79ad83f1ab12fc9feee9e66412de842fcbf8de0632beb4433d469f24f0fb4e022079e6df186582f2686a3292bde8e50dac36cb9bec3991995fe331e1daef7df8a401ffffffff0110270000000000001976a914faf1d99bf040ea9c7f8cc9f14ac6733ad75ce24688ac00000000", + "inputs": [ + { + "txId": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "vout": 0, + "prevTxScript": "OP_2 0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8 04c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a OP_2 OP_CHECKMULTISIG", + "signs": [ + { + "privKey": "91avARGdfge8E4tZfYLoxeJ5sGBdNJQH4kvjJoQFacbgww7vXtT" + }, + { + "privKey": "91avARGdfge8E4tZfYLoxeJ5sGBdNJQH4kvjJoQFacbgwmaKkrx" + } + ] + } + ], + "outputs": [ + { + "script": "OP_DUP OP_HASH160 faf1d99bf040ea9c7f8cc9f14ac6733ad75ce246 OP_EQUALVERIFY OP_CHECKSIG", + "value": 10000 + } + ] + }, + { + "description": "Transaction w/ scriptHash(pubKey) -> pubKeyHash", + "txHex": "0100000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000006c47304402201115644b134932c8a7a8e925769d130a801288d477130e2bf6fadda20b33754d02202ecefbf63844d7cb2d5868539c39f973fe019f72e5c31a707836c0d61ef317db012321033e29aea1168a835d5e386c292082db7b7807172a10ec634ad34226f36d79e70facffffffff0100f90295000000001976a914aa4d7985c57e011a8b3dd8e0e5a73aaef41629c588ac00000000", + "inputs": [ + { + "txId": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "vout": 0, + "prevTxScript": "OP_HASH160 e89677d91455e541630d62c63718bef738b478b1 OP_EQUAL", + "signs": [ + { + "privKey": "KxLDMPtVM7sLSu2v5n1LybDibw6P9FFbL4pUwJ51UDm7rp5AmXWW", + "redeemScript": "033e29aea1168a835d5e386c292082db7b7807172a10ec634ad34226f36d79e70f OP_CHECKSIG" + } + ] + } + ], + "outputs": [ + { + "script": "OP_DUP OP_HASH160 aa4d7985c57e011a8b3dd8e0e5a73aaef41629c5 OP_EQUALVERIFY OP_CHECKSIG", + "value": 2500000000 + } + ] + }, + { + "description": "Transaction w/ non-zero vin inputs", + "txHex": "0100000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff010000006a47304402205c80bbb5125b35d5e5a8324b1336832d29a6fc004859c8a9ff6bef47ba7fc348022018612216e57a521b2c4543f1f4fd738a76814c37c074e88adfe12464fff31cf901210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ffffffff0110270000000000001976a914aa4d7985c57e011a8b3dd8e0e5a73aaef41629c588ac00000000", + "inputs": [ + { + "txId": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "vout": 1, + "signs": [ + { + "privKey": "KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn" + } + ] + } + ], + "outputs": [ + { + "script": "OP_DUP OP_HASH160 aa4d7985c57e011a8b3dd8e0e5a73aaef41629c5 OP_EQUALVERIFY OP_CHECKSIG", + "value": 10000 + } + ] + }, + { + "description": "Transaction w/ non-default input sequence numbers, version and locktime", + "txHex": "0400000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff020000006b483045022100c5bcd521df085481e2dcc2c0f14173043f0fa2001dca582b45186a95d248d28002204c571eabcec1410bd53a7da29b9da6b4c858c3fdabbfdb110a030c507ff5bc0501210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798b9c220000110270000000000001976a914aa4d7985c57e011a8b3dd8e0e5a73aaef41629c588ac09990400", + "version": 4, + "locktime": 301321, + "inputs": [ + { + "txId": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "vout": 2, + "sequence": 2147001, + "signs": [ + { + "privKey": "KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn" + } + ] + } + ], + "outputs": [ + { + "script": "OP_DUP OP_HASH160 aa4d7985c57e011a8b3dd8e0e5a73aaef41629c5 OP_EQUALVERIFY OP_CHECKSIG", + "value": 10000 + } + ] + } + ] + }, + "invalid": { + "build": [ + { + "exception": "Transaction has no inputs", + "inputs": [], + "outputs": [ + { + "script": "OP_DUP OP_HASH160 aa4d7985c57e011a8b3dd8e0e5a73aaef41629c5 OP_EQUALVERIFY OP_CHECKSIG", + "value": 1000 + } + ] + }, + { + "exception": "Transaction has no outputs", + "inputs": [ + { + "txId": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "vout": 0, + "signs": [] + } + ], + "outputs": [] + }, + { + "description": "Incomplete transaction, nothing assumed", + "exception": "Transaction is not complete", + "inputs": [ + { + "txId": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "vout": 0, + "signs": [] + } + ], + "outputs": [ + { + "script": "OP_DUP OP_HASH160 aa4d7985c57e011a8b3dd8e0e5a73aaef41629c5 OP_EQUALVERIFY OP_CHECKSIG", + "value": 1000 + } + ] + }, + { + "description": "Incomplete transaction w/ prevTxScript defined", + "exception": "Transaction is missing signatures", + "alwaysThrows": true, + "inputs": [ + { + "txId": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "vout": 0, + "signs": [ + { + "privKey": "KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn" + } + ] + }, + { + "txId": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "vout": 1, + "prevTxScript": "OP_DUP OP_HASH160 aa4d7985c57e011a8b3dd8e0e5a73aaef41629c5 OP_EQUALVERIFY OP_CHECKSIG", + "signs": [] + } + ], + "outputs": [ + { + "script": "OP_DUP OP_HASH160 aa4d7985c57e011a8b3dd8e0e5a73aaef41629c5 OP_EQUALVERIFY OP_CHECKSIG", + "value": 1000 + } + ] + }, + { + "description": "Complete transaction w/ non-standard inputs", + "exception": "nonstandard not supported", + "txHex": "010000000100000000171a0000e028f2000000000050178500000000000d0000000e000000000000002009f691b2263260e71f363d1db51ff3100d285956a40cc0e4f8c8c2c4a80559b1ffffffff0110270000000000001976a914aa4d7985c57e011a8b3dd8e0e5a73aaef41629c588ac00000000" + } + ], + "sign": [ + { + "description": "Too many signatures - pubKeyHash", + "exception": "Signature already exists", + "inputs": [ + { + "txId": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "vout": 1, + "signs": [ + { + "privKey": "KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn" + }, + { + "privKey": "KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn", + "throws": true + } + ] + } + ], + "outputs": [ + { + "script": "OP_DUP OP_HASH160 aa4d7985c57e011a8b3dd8e0e5a73aaef41629c5 OP_EQUALVERIFY OP_CHECKSIG", + "value": 1000 + } + ] + }, + { + "exception": "RedeemScript not supported \\(nulldata\\)", + "inputs": [ + { + "txId": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "vout": 1, + "signs": [ + { + "privKey": "KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn", + "redeemScript": "OP_RETURN 06deadbeef03f895a2ad89fb6d696497af486cb7c644a27aa568c7a18dd06113401115185474", + "throws": true + } + ] + } + ], + "outputs": [ + { + "script": "OP_DUP OP_HASH160 aa4d7985c57e011a8b3dd8e0e5a73aaef41629c5 OP_EQUALVERIFY OP_CHECKSIG", + "value": 1000 + } + ] + }, + { + "exception": "PrevOutScript is P2SH, missing redeemScript", + "inputs": [ + { + "txId": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "vout": 1, + "prevTxScript": "OP_HASH160 7f67f0521934a57d3039f77f9f32cf313f3ac74b OP_EQUAL", + "signs": [ + { + "privKey": "KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn", + "throws": true + } + ] + } + ], + "outputs": [ + { + "script": "OP_DUP OP_HASH160 aa4d7985c57e011a8b3dd8e0e5a73aaef41629c5 OP_EQUALVERIFY OP_CHECKSIG", + "value": 1000 + } + ] + }, + { + "exception": "Inconsistent redeemScript", + "inputs": [ + { + "txId": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "vout": 0, + "signs": [ + { + "redeemScript": "OP_2 0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8 04c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a OP_2 OP_CHECKMULTISIG", + "privKey": "91avARGdfge8E4tZfYLoxeJ5sGBdNJQH4kvjJoQFacbgwmaKkrx" + }, + { + "redeemScript": "OP_1 0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8 04c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a OP_2 OP_CHECKMULTISIG", + "privKey": "91avARGdfge8E4tZfYLoxeJ5sGBdNJQH4kvjJoQFacbgww7vXtT", + "throws": true + } + ] + } + ], + "outputs": [ + { + "script": "OP_DUP OP_HASH160 aa4d7985c57e011a8b3dd8e0e5a73aaef41629c5 OP_EQUALVERIFY OP_CHECKSIG", + "value": 1000 + } + ] + }, + { + "exception": "Inconsistent hashType", + "inputs": [ + { + "txId": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "vout": 0, + "signs": [ + { + "redeemScript": "OP_2 0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8 04c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a OP_2 OP_CHECKMULTISIG", + "privKey": "91avARGdfge8E4tZfYLoxeJ5sGBdNJQH4kvjJoQFacbgwmaKkrx", + "hashType": 4 + }, + { + "privKey": "91avARGdfge8E4tZfYLoxeJ5sGBdNJQH4kvjJoQFacbgww7vXtT", + "hashType": 2, + "throws": true + } + ] + } + ], + "outputs": [ + { + "script": "OP_DUP OP_HASH160 aa4d7985c57e011a8b3dd8e0e5a73aaef41629c5 OP_EQUALVERIFY OP_CHECKSIG", + "value": 1000 + } + ] + }, + { + "exception": "RedeemScript not supported \\(scripthash\\)", + "inputs": [ + { + "txId": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "vout": 1, + "signs": [ + { + "privKey": "KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn", + "redeemScript": "OP_HASH160 7f67f0521934a57d3039f77f9f32cf313f3ac74b OP_EQUAL", + "throws": true + } + ] + } + ], + "outputs": [ + { + "script": "OP_DUP OP_HASH160 aa4d7985c57e011a8b3dd8e0e5a73aaef41629c5 OP_EQUALVERIFY OP_CHECKSIG", + "value": 1000 + } + ] + }, + { + "exception": "PrevOutScript must be P2SH", + "inputs": [ + { + "txId": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "vout": 1, + "prevTxScript": "OP_DUP OP_HASH160 aa4d7985c57e011a8b3dd8e0e5a73aaef41629c5 OP_EQUALVERIFY OP_CHECKSIG", + "signs": [ + { + "privKey": "KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn", + "redeemScript": "OP_1 0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8 OP_1 OP_CHECKMULTISIG", + "throws": true + } + ] + } + ], + "outputs": [ + { + "script": "OP_DUP OP_HASH160 aa4d7985c57e011a8b3dd8e0e5a73aaef41629c5 OP_EQUALVERIFY OP_CHECKSIG", + "value": 1000 + } + ] + }, + { + "description": "Too many signatures - scriptHash(multisig 1-of-1)", + "exception": "Signature already exists", + "inputs": [ + { + "txId": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "vout": 1, + "signs": [ + { + "privKey": "KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn", + "redeemScript": "OP_1 0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8 OP_1 OP_CHECKMULTISIG" + }, + { + "privKey": "KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn", + "throws": true + } + ] + } + ], + "outputs": [ + { + "script": "OP_DUP OP_HASH160 aa4d7985c57e011a8b3dd8e0e5a73aaef41629c5 OP_EQUALVERIFY OP_CHECKSIG", + "value": 1000 + } + ] + }, + { + "description": "Wrong private key for multisig redeemScript", + "exception": "privateKey cannot sign for this input", + "inputs": [ + { + "txId": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "vout": 1, + "signs": [ + { + "privKey": "KzrA86mCVMGWnLGBQu9yzQa32qbxb5dvSK4XhyjjGAWSBKYX4rHx", + "redeemScript": "OP_1 0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8 OP_1 OP_CHECKMULTISIG", + "throws": true + } + ] + } + ], + "outputs": [ + { + "script": "OP_DUP OP_HASH160 aa4d7985c57e011a8b3dd8e0e5a73aaef41629c5 OP_EQUALVERIFY OP_CHECKSIG", + "value": 1000 + } + ] + }, + { + "exception": "nulldata not supported", + "inputs": [ + { + "txId": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "vout": 0, + "prevTxScript": "OP_RETURN 06deadbeef03f895a2ad89fb6d696497af486cb7c644a27aa568c7a18dd06113401115185474", + "signs": [ + { + "privKey": "KzrA86mCVMGWnLGBQu9yzQa32qbxb5dvSK4XhyjjGAWSBKYX4rHx", + "throws": true + } + ] + } + ], + "outputs": [ + { + "script": "OP_DUP OP_HASH160 aa4d7985c57e011a8b3dd8e0e5a73aaef41629c5 OP_EQUALVERIFY OP_CHECKSIG", + "value": 1000 + } + ] + } + ], + "fromTransaction": [ + { + "exception": "coinbase inputs not supported", + "txHex": "01000000010000000000000000000000000000000000000000000000000000000000000000000000006b483045022100a3b254e1c10b5d039f36c05f323995d6e5a367d98dd78a13d5bbc3991b35720e022022fccea3897d594de0689601fbd486588d5bfa6915be2386db0397ee9a6e80b601210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ffffffff0110270000000000001976a914aa4d7985c57e011a8b3dd8e0e5a73aaef41629c588ac00000000" + } + ] + } +} diff --git a/test/hdnode.js b/test/hdnode.js index bc951a288..a1cb97a14 100644 --- a/test/hdnode.js +++ b/test/hdnode.js @@ -49,7 +49,13 @@ describe('HDNode', function() { assert.equal(hd.network, networks.testnet) }) - it('throws an exception when an unknown network is given', function() { + it('throws when an invalid length chain code is given', function() { + assert.throws(function() { + new HDNode(d, chainCode.slice(0, 20), networks.testnet) + }, /Expected chainCode length of 32, got 20/) + }) + + it('throws when an unknown network is given', function() { assert.throws(function() { new HDNode(d, chainCode, {}) }, /Unknown BIP32 constants for network/) @@ -59,9 +65,10 @@ describe('HDNode', function() { describe('fromSeed*', function() { fixtures.valid.forEach(function(f) { it('calculates privKey and chainCode for ' + f.master.fingerprint, function() { - var hd = HDNode.fromSeedHex(f.master.seed) + var network = networks[f.network] + var hd = HDNode.fromSeedHex(f.master.seed, network) - assert.equal(hd.privKey.toWIF(), f.master.wif) + assert.equal(hd.privKey.toWIF(network), f.master.wif) assert.equal(hd.chainCode.toString('hex'), f.master.chainCode) }) }) @@ -82,20 +89,23 @@ describe('HDNode', function() { describe('toBase58', function() { fixtures.valid.forEach(function(f) { it('exports ' + f.master.base58 + ' (public) correctly', function() { - var hd = HDNode.fromSeedHex(f.master.seed) + var network = networks[f.network] + var hd = HDNode.fromSeedHex(f.master.seed, network).neutered() - assert.equal(hd.toBase58(false), f.master.base58) + assert.equal(hd.toBase58(), f.master.base58) }) }) fixtures.valid.forEach(function(f) { it('exports ' + f.master.base58Priv + ' (private) correctly', function() { - var hd = HDNode.fromSeedHex(f.master.seed) + var network = networks[f.network] + var hd = HDNode.fromSeedHex(f.master.seed, network) - assert.equal(hd.toBase58(true), f.master.base58Priv) + assert.equal(hd.toBase58(), f.master.base58Priv) }) }) + // FIXME: remove in 2.x.y it('fails when there is no private key', function() { var hd = HDNode.fromBase58(fixtures.valid[0].master.base58) @@ -125,7 +135,9 @@ describe('HDNode', function() { fixtures.invalid.fromBase58.forEach(function(f) { it('throws on ' + f.string, function() { assert.throws(function() { - HDNode.fromBase58(f.string) + var network = networks[f.network] + + HDNode.fromBase58(f.string, network) }, new RegExp(f.exception)) }) }) @@ -160,20 +172,22 @@ describe('HDNode', function() { describe('toBuffer/toHex', function() { fixtures.valid.forEach(function(f) { it('exports ' + f.master.hex + ' (public) correctly', function() { - var hd = HDNode.fromSeedHex(f.master.seed) + var hd = HDNode.fromSeedHex(f.master.seed).neutered() - assert.equal(hd.toHex(false), f.master.hex) + assert.equal(hd.toHex(), f.master.hex) }) }) fixtures.valid.forEach(function(f) { it('exports ' + f.master.hexPriv + ' (private) correctly', function() { - var hd = HDNode.fromSeedHex(f.master.seed) + var network = networks[f.network] + var hd = HDNode.fromSeedHex(f.master.seed, network) - assert.equal(hd.toHex(true), f.master.hexPriv) + assert.equal(hd.toHex(), f.master.hexPriv) }) }) + // FIXME: remove in 2.x.y it('fails when there is no private key', function() { var hd = HDNode.fromHex(fixtures.valid[0].master.hex) @@ -204,25 +218,33 @@ describe('HDNode', function() { }) describe('getAddress', function() { - var f = fixtures.valid[0] - - it('returns the Address (pubHash) for ' + f.master.fingerprint, function() { - var hd = HDNode.fromBase58(f.master.base58) + fixtures.valid.forEach(function(f) { + it('returns ' + f.master.address + ' for ' + f.master.fingerprint, function() { + var hd = HDNode.fromBase58(f.master.base58) - assert.equal(hd.getAddress().toString(), f.master.address) + assert.equal(hd.getAddress().toString(), f.master.address) + }) }) + }) - it('supports alternative networks', function() { + describe('neutered', function() { + var f = fixtures.valid[0] + + it('strips all private information', function() { var hd = HDNode.fromBase58(f.master.base58) - hd.network = networks.testnet + var hdn = hd.neutered() - assert.equal(hd.getAddress().version, networks.testnet.pubKeyHash) + assert.equal(hdn.privKey, undefined) + assert.equal(hdn.pubKey.toHex(), hd.pubKey.toHex()) + assert.equal(hdn.chainCode, hd.chainCode) + assert.equal(hdn.depth, hd.depth) + assert.equal(hdn.index, hd.index) }) }) describe('derive', function() { - function verifyVector(hd, v, depth) { - assert.equal(hd.privKey.toWIF(), v.wif) + function verifyVector(hd, network, v, depth) { + assert.equal(hd.privKey.toWIF(network), v.wif) assert.equal(hd.pubKey.toHex(), v.pubKey) assert.equal(hd.chainCode.toString('hex'), v.chainCode) assert.equal(hd.depth, depth || 0) @@ -234,8 +256,9 @@ describe('HDNode', function() { } } - fixtures.valid.forEach(function(f, j) { - var hd = HDNode.fromSeedHex(f.master.seed) + fixtures.valid.forEach(function(f) { + var network = networks[f.network] + var hd = HDNode.fromSeedHex(f.master.seed, network) // FIXME: test data is only testing Private -> private for now f.children.forEach(function(c, i) { @@ -247,7 +270,7 @@ describe('HDNode', function() { hd = hd.derive(c.m) } - verifyVector(hd, c, i + 1) + verifyVector(hd, network, c, i + 1) }) }) }) @@ -256,32 +279,28 @@ describe('HDNode', function() { var f = fixtures.valid[1] var c = f.children[0] - var parentNode = HDNode.fromBase58(f.master.base58Priv) - var child = parentNode.derive(c.m) + var master = HDNode.fromBase58(f.master.base58Priv) + var child = master.derive(c.m).neutered() - // FIXME: N(CKDpriv((kpar, cpar), i)), could be done better... - var childNeutered = HDNode.fromBase58(child.toBase58(false)) // neuter - assert.equal(childNeutered.toBase58(), c.base58) + assert.equal(child.toBase58(), c.base58) }) it('works for Private -> public (neutered, hardened)', function() { var f = fixtures.valid[0] var c = f.children[0] - var parentNode = HDNode.fromBase58(f.master.base58Priv) - var child = parentNode.deriveHardened(c.m) + var master = HDNode.fromBase58(f.master.base58Priv) + var child = master.deriveHardened(c.m).neutered() - // FIXME: N(CKDpriv((kpar, cpar), i)), could be done better... - var childNeutered = HDNode.fromBase58(child.toBase58(false)) // neuter - assert.equal(childNeutered.toBase58(), c.base58) + assert.equal(child.toBase58(), c.base58) }) it('works for Public -> public', function() { var f = fixtures.valid[1] var c = f.children[0] - var parentNode = HDNode.fromBase58(f.master.base58) - var child = parentNode.derive(c.m) + var master = HDNode.fromBase58(f.master.base58) + var child = master.derive(c.m) assert.equal(child.toBase58(), c.base58) }) @@ -290,10 +309,10 @@ describe('HDNode', function() { var f = fixtures.valid[0] var c = f.children[0] - var parentNode = HDNode.fromBase58(f.master.base58) + var master = HDNode.fromBase58(f.master.base58) assert.throws(function() { - parentNode.deriveHardened(c.m) + master.deriveHardened(c.m) }, /Could not derive hardened child key/) }) }) diff --git a/test/integration/advanced.js b/test/integration/advanced.js new file mode 100644 index 000000000..482d316f8 --- /dev/null +++ b/test/integration/advanced.js @@ -0,0 +1,66 @@ +var assert = require('assert') +var bitcoin = require('../../') +var blockchain = new (require('cb-helloblock'))('testnet') + +describe('bitcoinjs-lib (advanced)', function() { + it('can sign a Bitcoin message', function() { + var key = bitcoin.ECKey.fromWIF('5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss') + var message = 'This is an example of a signed message.' + + var signature = bitcoin.Message.sign(key, message) + assert.equal(signature.toString('base64'), 'G9L5yLFjti0QTHhPyFrZCT1V/MMnBtXKmoiKDZ78NDBjERki6ZTQZdSMCtkgoNmp17By9ItJr8o7ChX0XxY91nk=') + }) + + it('can verify a Bitcoin message', function() { + var address = '1HZwkjkeaoZfTSaJxDw6aKkxp45agDiEzN' + var signature = 'HJLQlDWLyb1Ef8bQKEISzFbDAKctIlaqOpGbrk3YVtRsjmC61lpE5ErkPRUFtDKtx98vHFGUWlFhsh3DiW6N0rE' + var message = 'This is an example of a signed message.' + + assert(bitcoin.Message.verify(address, signature, message)) + }) + + it('can create an OP_RETURN transaction', function(done) { + this.timeout(20000) + + var key = bitcoin.ECKey.fromWIF("L1uyy5qTuGrVXrmrsvHWHgVzW9kKdrp27wBC7Vs6nZDTF2BRUVwy") + var address = key.pub.getAddress(bitcoin.networks.testnet).toString() + + blockchain.addresses.__faucetWithdraw(address, 2e4, function(err) { + if (err) return done(err) + + blockchain.addresses.unspents(address, function(err, unspents) { + if (err) return done(err) + + var tx = new bitcoin.TransactionBuilder() + var data = new Buffer('bitcoinjs-lib') + var dataScript = bitcoin.scripts.nullDataOutput(data) + + var unspent = unspents.pop() + + tx.addInput(unspent.txId, unspent.vout) + tx.addOutput(dataScript, 1000) + tx.sign(0, key) + + var txBuilt = tx.build() + + blockchain.transactions.propagate(txBuilt.toHex(), function(err) { + if (err) return done(err) + + // check that the message was propagated + blockchain.transactions.get(txBuilt.getId(), function(err, transaction) { + if (err) return done(err) + + var actual = bitcoin.Transaction.fromHex(transaction.txHex) + var dataScript2 = actual.outs[0].script + var data2 = dataScript2.chunks[1] + + assert.deepEqual(dataScript, dataScript2) + assert.deepEqual(data, data2) + + done() + }) + }) + }) + }) + }) +}) diff --git a/test/integration/basic.js b/test/integration/basic.js new file mode 100644 index 000000000..8e0b3d86a --- /dev/null +++ b/test/integration/basic.js @@ -0,0 +1,46 @@ +var assert = require('assert') +var bigi = require('bigi') +var bitcoin = require('../../') +var crypto = require('crypto') +var sinon = require('sinon') + +describe('bitcoinjs-lib (basic)', function() { + it('can generate a random bitcoin address', sinon.test(function() { + // for testing only + this.mock(crypto).expects('randomBytes') + .onCall(0).returns(new Buffer('zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz')) + + // generate random key + var key = bitcoin.ECKey.makeRandom() + var address = key.pub.getAddress().toString() + + assert.equal(address, '1F5VhMHukdnUES9kfXqzPzMeF1GPHKiF64') + })) + + it('can generate an address from a SHA256 hash', function() { + var hash = bitcoin.crypto.sha256('correct horse battery staple') + var d = bigi.fromBuffer(hash) + + var key = new bitcoin.ECKey(d) + + assert.equal(key.pub.getAddress().toString(), '1C7zdTfnkzmr13HfA2vNm5SJYRK6nEKyq8') + }) + + it('can import an address via WIF', function() { + var key = bitcoin.ECKey.fromWIF('Kxr9tQED9H44gCmp6HAdmemAzU3n84H3dGkuWTKvE23JgHMW8gct') + var address = key.pub.getAddress().toString() + + assert.equal(address, '19AAjaTUbRjQCMuVczepkoPswiZRhjtg31') + }) + + it('can create a Transaction', function() { + var key = bitcoin.ECKey.fromWIF("L1uyy5qTuGrVXrmrsvHWHgVzW9kKdrp27wBC7Vs6nZDTF2BRUVwy") + var tx = new bitcoin.TransactionBuilder() + + tx.addInput("aa94ab02c182214f090e99a0d57021caffd0f195a81c24602b1028b130b63e31", 0) + tx.addOutput("1Gokm82v6DmtwKEB8AiVhm82hyFSsEvBDK", 15000) + tx.sign(0, key) + + assert.equal(tx.build().toHex(), '0100000001313eb630b128102b60241ca895f1d0ffca2170d5a0990e094f2182c102ab94aa000000006b483045022100aefbcf847900b01dd3e3debe054d3b6d03d715d50aea8525f5ea3396f168a1fb022013d181d05b15b90111808b22ef4f9ebe701caf2ab48db269691fdf4e9048f4f60121029f50f51d63b345039a290c94bffd3180c99ed659ff6ea6b1242bca47eb93b59fffffffff01983a0000000000001976a914ad618cf4333b3b248f9744e8e81db2964d0ae39788ac00000000') + }) +}) diff --git a/test/integration/crypto.js b/test/integration/crypto.js new file mode 100644 index 000000000..06e3cf18a --- /dev/null +++ b/test/integration/crypto.js @@ -0,0 +1,183 @@ +var assert = require('assert') +var async = require('async') +var bigi = require('bigi') +var bitcoin = require('../../') +var blockchain = new (require('cb-helloblock'))('bitcoin') +var crypto = require('crypto') + +describe('bitcoinjs-lib (crypto)', function() { + it('can generate a single-key stealth address', function() { + var receiver = bitcoin.ECKey.fromWIF('5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss') + + // XXX: ephemeral, must be random (and secret to sender) to preserve privacy + var sender = bitcoin.ECKey.fromWIF('Kxr9tQED9H44gCmp6HAdmemAzU3n84H3dGkuWTKvE23JgHMW8gct') + + var G = bitcoin.ECKey.curve.G + var d = receiver.d // secret (receiver only) + var Q = receiver.pub.Q // shared + + var e = sender.d // secret (sender only) + var P = sender.pub.Q // shared + + // derived shared secret + var eQ = Q.multiply(e) // sender + var dP = P.multiply(d) // receiver + assert.deepEqual(eQ.getEncoded(), dP.getEncoded()) + + var c = bigi.fromBuffer(bitcoin.crypto.sha256(eQ.getEncoded())) + var cG = G.multiply(c) + + // derived public key + var QprimeS = Q.add(cG) + var QprimeR = G.multiply(d.add(c)) + assert.deepEqual(QprimeR.getEncoded(), QprimeS.getEncoded()) + + // derived shared-secret address + var address = new bitcoin.ECPubKey(QprimeS).getAddress().toString() + + assert.equal(address, '1EwCNJNZM5q58YPPTnjR1H5BvYRNeyZi47') + }) + + // TODO + it.skip('can generate a dual-key stealth address', function() {}) + + it('can recover a parent private key from the parent\'s public key and a derived non-hardened child private key', function() { + function recoverParent(master, child) { + assert(!master.privKey, 'You already have the parent private key') + assert(child.privKey, 'Missing child private key') + + var curve = bitcoin.ECKey.curve + var QP = master.pubKey.toBuffer() + var QP64 = QP.toString('base64') + var d1 = child.privKey.d + var d2 + var indexBuffer = new Buffer(4) + + // search index space until we find it + for (var i = 0; i < bitcoin.HDNode.HIGHEST_BIT; ++i) { + indexBuffer.writeUInt32BE(i, 0) + + // calculate I + var data = Buffer.concat([QP, indexBuffer]) + var I = crypto.createHmac('sha512', master.chainCode).update(data).digest() + var IL = I.slice(0, 32) + var pIL = bigi.fromBuffer(IL) + + // See hdnode.js:273 to understand + d2 = d1.subtract(pIL).mod(curve.n) + + var Qp = new bitcoin.ECKey(d2, true).pub.toBuffer() + if (Qp.toString('base64') === QP64) break + } + + var node = new bitcoin.HDNode(d2, master.chainCode, master.network) + node.depth = master.depth + node.index = master.index + node.masterFingerprint = master.masterFingerprint + return node + } + + var seed = crypto.randomBytes(32) + var master = bitcoin.HDNode.fromSeedBuffer(seed) + var child = master.derive(6) // m/6 + + // now for the recovery + var neuteredMaster = master.neutered() + var recovered = recoverParent(neuteredMaster, child) + assert.equal(recovered.toBase58(), master.toBase58()) + }) + + it('can recover a private key from duplicate R values', function() { + var inputs = [ + { + txId: "f4c16475f2a6e9c602e4a287f9db3040e319eb9ece74761a4b84bc820fbeef50", + vout: 0 + }, + { + txId: "f4c16475f2a6e9c602e4a287f9db3040e319eb9ece74761a4b84bc820fbeef50", + vout: 1 + } + ] + + var txIds = inputs.map(function(x) { return x.txId }) + + // first retrieve the relevant transactions + blockchain.transactions.get(txIds, function(err, results) { + assert.ifError(err) + + var transactions = {} + results.forEach(function(tx) { + transactions[tx.txId] = bitcoin.Transaction.fromHex(tx.txHex) + }) + + var tasks = [] + + // now we need to collect/transform a bit of data from the selected inputs + inputs.forEach(function(input) { + var transaction = transactions[input.txId] + var script = transaction.ins[input.vout].script + assert(bitcoin.scripts.isPubKeyHashInput(script), 'Expected pubKeyHash script') + + var prevOutTxId = bitcoin.bufferutils.reverse(transaction.ins[input.vout].hash).toString('hex') + var prevVout = transaction.ins[input.vout].index + + tasks.push(function(callback) { + blockchain.transactions.get(prevOutTxId, function(err, result) { + if (err) return callback(err) + + var prevOut = bitcoin.Transaction.fromHex(result.txHex) + var prevOutScript = prevOut.outs[prevVout].script + + var scriptSignature = bitcoin.ECSignature.parseScriptSignature(script.chunks[0]) + var publicKey = bitcoin.ECPubKey.fromBuffer(script.chunks[1]) + + var m = transaction.hashForSignature(input.vout, prevOutScript, scriptSignature.hashType) + assert(publicKey.verify(m, scriptSignature.signature), 'Invalid m') + + // store the required information + input.signature = scriptSignature.signature + input.z = bigi.fromBuffer(m) + + return callback() + }) + }) + }) + + // finally, run the tasks, then on to the math + async.parallel(tasks, function(err) { + if (err) throw err + var n = bitcoin.ECKey.curve.n + + for (var i = 0; i < inputs.length; ++i) { + for (var j = i + 1; j < inputs.length; ++j) { + var inputA = inputs[i] + var inputB = inputs[j] + + // enforce matching r values + assert.equal(inputA.signature.r.toString(), inputB.signature.r.toString()) + var r = inputA.signature.r + var rInv = r.modInverse(n) + + var s1 = inputA.signature.s + var s2 = inputB.signature.s + var z1 = inputA.z + var z2 = inputB.z + + var zz = z1.subtract(z2).mod(n) + var ss = s1.subtract(s2).mod(n) + + // k = (z1 - z2) / (s1 - s2) + // d1 = (s1 * k - z1) / r + // d2 = (s2 * k - z2) / r + var k = zz.multiply(ss.modInverse(n)).mod(n) + var d1 = (( s1.multiply(k).mod(n) ).subtract(z1).mod(n) ).multiply(rInv).mod(n) + var d2 = (( s2.multiply(k).mod(n) ).subtract(z2).mod(n) ).multiply(rInv).mod(n) + + // enforce matching private keys + assert.equal(d1.toString(), d2.toString()) + } + } + }) + }) + }) +}) diff --git a/test/integration/multisig.js b/test/integration/multisig.js new file mode 100644 index 000000000..911affddb --- /dev/null +++ b/test/integration/multisig.js @@ -0,0 +1,74 @@ +var assert = require('assert') +var bitcoin = require('../../') +var blockchain = new (require('cb-helloblock'))('testnet') + +describe('bitcoinjs-lib (multisig)', function() { + it('can create a 2-of-3 multisig P2SH address', function() { + var pubKeys = [ + '026477115981fe981a6918a6297d9803c4dc04f328f22041bedff886bbc2962e01', + '02c96db2302d19b43d4c69368babace7854cc84eb9e061cde51cfa77ca4a22b8b9', + '03c6103b3b83e4a24a0e33a4df246ef11772f9992663db0c35759a5e2ebf68d8e9' + ].map(bitcoin.ECPubKey.fromHex) + + var redeemScript = bitcoin.scripts.multisigOutput(2, pubKeys) // 2 of 3 + var scriptPubKey = bitcoin.scripts.scriptHashOutput(redeemScript.getHash()) + var address = bitcoin.Address.fromOutputScript(scriptPubKey).toString() + + assert.equal(address, '36NUkt6FWUi3LAWBqWRdDmdTWbt91Yvfu7') + }) + + it('can spend from a 2-of-2 multsig P2SH address', function(done) { + this.timeout(20000) + + var privKeys = [ + '91avARGdfge8E4tZfYLoxeJ5sGBdNJQH4kvjJoQFacbgwmaKkrx', + '91avARGdfge8E4tZfYLoxeJ5sGBdNJQH4kvjJoQFacbgww7vXtT' + ].map(bitcoin.ECKey.fromWIF) + var pubKeys = privKeys.map(function(x) { return x.pub }) + + var redeemScript = bitcoin.scripts.multisigOutput(2, pubKeys) // 2 of 2 + var scriptPubKey = bitcoin.scripts.scriptHashOutput(redeemScript.getHash()) + var address = bitcoin.Address.fromOutputScript(scriptPubKey, bitcoin.networks.testnet).toString() + + // Attempt to send funds to the source address + blockchain.addresses.__faucetWithdraw(address, 2e4, function(err) { + if (err) return done(err) + + // get latest unspents from the address + blockchain.addresses.unspents(address, function(err, unspents) { + if (err) return done(err) + + // filter small unspents + unspents = unspents.filter(function(unspent) { return unspent.value > 1e4 }) + + // use the oldest unspent + var unspent = unspents.pop() + + // make a random destination address + var targetAddress = bitcoin.ECKey.makeRandom().pub.getAddress(bitcoin.networks.testnet).toString() + + var txb = new bitcoin.TransactionBuilder() + txb.addInput(unspent.txId, unspent.vout) + txb.addOutput(targetAddress, 1e4) + + // sign w/ each private key + privKeys.forEach(function(privKey) { + txb.sign(0, privKey, redeemScript) + }) + + // broadcast our transaction + blockchain.transactions.propagate(txb.build().toHex(), function(err) { + if (err) return done(err) + + // check that the funds (1e4 Satoshis) indeed arrived at the intended address + blockchain.addresses.summary(targetAddress, function(err, result) { + if (err) return done(err) + + assert.equal(result.balance, 1e4) + done() + }) + }) + }) + }) + }) +}) diff --git a/test/integration/p2sh.js b/test/integration/p2sh.js deleted file mode 100644 index ff7946c65..000000000 --- a/test/integration/p2sh.js +++ /dev/null @@ -1,80 +0,0 @@ -var assert = require('assert') - -var bitcoin = require('../../') -var crypto = bitcoin.crypto -var networks = bitcoin.networks -var scripts = bitcoin.scripts - -var Address = bitcoin.Address -var ECKey = bitcoin.ECKey -var Transaction = bitcoin.Transaction -var Script = bitcoin.Script - -var helloblock = require('helloblock-js')({ - network: 'testnet' -}) - -describe('Bitcoin-js', function() { - this.timeout(10000) - - it('can spend from a 2-of-2 address', function(done) { - var privKeys = [ - '91avARGdfge8E4tZfYLoxeJ5sGBdNJQH4kvjJoQFacbgwmaKkrx', - '91avARGdfge8E4tZfYLoxeJ5sGBdNJQH4kvjJoQFacbgww7vXtT' - ].map(function(wif) { - return ECKey.fromWIF(wif) - }) - - var coldAmount = 2e4 - var outputAmount = 1e4 - - var pubKeys = privKeys.map(function(eck) { return eck.pub }) - var redeemScript = scripts.multisigOutput(2, pubKeys) - var scriptPubKey = scripts.scriptHashOutput(redeemScript.getHash()) - - var multisigAddress = Address.fromOutputScript(scriptPubKey, networks.testnet).toString() - - // Attempt to send funds to the source address, providing some unspents for later - helloblock.faucet.withdraw(multisigAddress, coldAmount, function(err) { - if (err) return done(err) - }) - - // make a random private key - var targetAddress = ECKey.makeRandom().pub.getAddress(networks.testnet).toString() - - // get latest unspents from the multisigAddress - helloblock.addresses.getUnspents(multisigAddress, function(err, resp, resource) { - if (err) return done(err) - - // use the oldest unspent - var unspent = resource[resource.length - 1] - var spendAmount = Math.min(unspent.value, outputAmount) - - var tx = new Transaction() - tx.addInput(unspent.txHash, unspent.index) - tx.addOutput(targetAddress, spendAmount) - - var signatures = privKeys.map(function(privKey) { - return tx.signInput(0, redeemScript, privKey) - }) - - var redeemScriptSig = scripts.multisigInput(signatures) - var scriptSig = scripts.scriptHashInput(redeemScriptSig, redeemScript) - tx.setInputScript(0, scriptSig) - - // broadcast our transaction - helloblock.transactions.propagate(tx.toHex(), function(err, resp, resource) { - // no err means that the transaction has been successfully propagated - if (err) return done(err) - - // Check that the funds (spendAmount Satoshis) indeed arrived at the intended address - helloblock.addresses.get(targetAddress, function(err, resp, resource) { - if (err) return done(err) - - assert.equal(resource.balance, spendAmount) - done() - }) - }) - }) - }) -}) diff --git a/test/message.js b/test/message.js index d69a41f6d..37ed1d872 100644 --- a/test/message.js +++ b/test/message.js @@ -26,29 +26,24 @@ describe('Message', function() { var network = networks[f.network] var address = Address.fromBase58Check(f.address) - var signature = new Buffer(f.signature, 'base64') - assert.ok(Message.verify(address, signature, f.message, network)) + assert(Message.verify(address, f.signature, f.message, network)) }) fixtures.valid.verify.forEach(function(f) { it('verifies a valid signature for \"' + f.message + '\" (' + f.network + ')', function() { var network = networks[f.network] - var signature = new Buffer(f.signature, 'base64') - assert.ok(Message.verify(f.address, signature, f.message, network)) + assert(Message.verify(f.address, f.signature, f.message, network)) if (f.compressed) { - var compressedSignature = new Buffer(f.compressed.signature, 'base64') - - assert.ok(Message.verify(f.compressed.address, compressedSignature, f.message, network)) + assert(Message.verify(f.compressed.address, f.compressed.signature, f.message, network)) } }) }) fixtures.invalid.verify.forEach(function(f) { it(f.description, function() { - var signature = new Buffer(f.signature, 'base64') - assert.ok(!Message.verify(f.address, signature, f.message)) + assert(!Message.verify(f.address, f.signature, f.message)) }) }) }) diff --git a/test/network.js b/test/network.js index ad674b635..b4cd89f77 100644 --- a/test/network.js +++ b/test/network.js @@ -1,6 +1,8 @@ var assert = require('assert') var networks = require('../src/networks') var sinon = require('sinon') + +var HDNode = require('../src/hdnode') var Transaction = require('../src/transaction') var fixtures = require('./fixtures/network') @@ -15,19 +17,35 @@ describe('networks', function() { Transaction.prototype.toBuffer.restore() }) - fixtures.valid.forEach(function(f) { - describe(f.network + ' estimateFee', function() { + describe('constants', function() { + fixtures.valid.constants.forEach(function(f) { var network = networks[f.network] - it('calculates the fee correctly for ' + f.description, function() { - var buffer = new Buffer(f.txSize) - txToBuffer.returns(buffer) + Object.keys(f.bip32).forEach(function(name) { + var extb58 = f.bip32[name] + + it('resolves ' + extb58 + ' to ' + f.network, function() { + assert.equal(HDNode.fromBase58(extb58, network).network, network) + }) + }) + }) + }) + + describe('estimateFee', function() { + fixtures.valid.estimateFee.forEach(function(f) { + describe('(' + f.network + ')', function() { + var network = networks[f.network] + + it('calculates the fee correctly for ' + f.description, function() { + var buffer = new Buffer(f.txSize) + txToBuffer.returns(buffer) - var estimateFee = network.estimateFee - var tx = new Transaction() - tx.outs = f.outputs || [] + var estimateFee = network.estimateFee + var tx = new Transaction() + tx.outs = f.outputs || [] - assert.equal(estimateFee(tx), f.fee) + assert.equal(estimateFee(tx), f.fee) + }) }) }) }) diff --git a/test/scripts.js b/test/scripts.js index b50ec3a1f..13ebe1ab6 100644 --- a/test/scripts.js +++ b/test/scripts.js @@ -1,13 +1,17 @@ var assert = require('assert') +var ops = require('../src/opcodes') var scripts = require('../src/scripts') -var Address = require('../src/address') var ECPubKey = require('../src/ecpubkey') var Script = require('../src/script') var fixtures = require('./fixtures/scripts.json') describe('Scripts', function() { + // TODO + describe.skip('isCanonicalPubKey', function() {}) + describe.skip('isCanonicalSignature', function() {}) + describe('classifyInput', function() { fixtures.valid.forEach(function(f) { if (!f.scriptSig) return @@ -19,6 +23,18 @@ describe('Scripts', function() { assert.equal(type, f.type) }) }) + + fixtures.valid.forEach(function(f) { + if (!f.scriptSig) return + if (!f.typeIncomplete) return + + it('classifies incomplete ' + f.scriptSig + ' as ' + f.typeIncomplete, function() { + var script = Script.fromASM(f.scriptSig) + var type = scripts.classifyInput(script, true) + + assert.equal(type, f.typeIncomplete) + }) + }) }) describe('classifyOutput', function() { @@ -32,142 +48,224 @@ describe('Scripts', function() { assert.equal(type, f.type) }) }) + }) - fixtures.invalid.classify.forEach(function(f) { - it('returns nonstandard for ' + f.description, function() { - var script = Script.fromASM(f.scriptPubKey) - var type = scripts.classifyOutput(script) + ;['PubKey', 'PubKeyHash', 'ScriptHash', 'Multisig', 'NullData'].forEach(function(type) { + var inputFnName = 'is' + type + 'Input' + var outputFnName = 'is' + type + 'Output' + + var inputFn = scripts[inputFnName] + var outputFn= scripts[outputFnName] + + describe('is' + type + 'Input', function() { + fixtures.valid.forEach(function(f) { + var expected = type.toLowerCase() === f.type + + if (inputFn && f.scriptSig) { + it('returns ' + expected + ' for ' + f.scriptSig, function() { + var script = Script.fromASM(f.scriptSig) + + assert.equal(inputFn(script), expected) + }) + + if (f.typeIncomplete) { + var expectedIncomplete = type.toLowerCase() === f.typeIncomplete + + it('returns ' + expected + ' for ' + f.scriptSig, function() { + var script = Script.fromASM(f.scriptSig) + + assert.equal(inputFn(script, true), expectedIncomplete) + }) + } + } + }) + + if (!(inputFnName in fixtures.invalid)) return - assert.equal(type, 'nonstandard') + fixtures.invalid[inputFnName].forEach(function(f) { + if (inputFn && f.scriptSig) { + it('returns false for ' + f.scriptSig, function() { + var script = Script.fromASM(f.scriptSig) + + assert.equal(inputFn(script), false) + }) + } + }) + }) + + describe('is' + type + 'Output', function() { + fixtures.valid.forEach(function(f) { + var expected = type.toLowerCase() === f.type + + if (outputFn && f.scriptPubKey) { + it('returns ' + expected + ' for ' + f.scriptPubKey, function() { + var script = Script.fromASM(f.scriptPubKey) + + assert.equal(outputFn(script), expected) + }) + } + }) + + if (!(outputFnName in fixtures.invalid)) return + + fixtures.invalid[outputFnName].forEach(function(f) { + if (outputFn && f.scriptPubKey) { + it('returns false for ' + f.scriptPubKey, function() { + var script = Script.fromASM(f.scriptPubKey) + + assert.equal(outputFn(script), false) + }) + } }) }) }) - describe('pubKey', function() { + describe('pubKeyInput', function() { fixtures.valid.forEach(function(f) { if (f.type !== 'pubkey') return - describe('input script', function() { - it('is generated correctly for ' + f.pubKey, function() { - var signature = new Buffer(f.signature, 'hex') + it('returns ' + f.scriptSig, function() { + var signature = new Buffer(f.signature, 'hex') - var scriptSig = scripts.pubKeyInput(signature) - assert.equal(scriptSig.toASM(), f.scriptSig) - }) + var scriptSig = scripts.pubKeyInput(signature) + assert.equal(scriptSig.toASM(), f.scriptSig) }) + }) + }) - describe('output script', function() { - it('is generated correctly for ' + f.pubKey, function() { - var pubKey = ECPubKey.fromHex(f.pubKey) + describe('pubKeyOutput', function() { + fixtures.valid.forEach(function(f) { + if (f.type !== 'pubkey') return - var scriptPubKey = scripts.pubKeyOutput(pubKey) - assert.equal(scriptPubKey.toASM(), f.scriptPubKey) - }) + it('returns ' + f.scriptPubKey, function() { + var pubKey = ECPubKey.fromHex(f.pubKey) + + var scriptPubKey = scripts.pubKeyOutput(pubKey) + assert.equal(scriptPubKey.toASM(), f.scriptPubKey) }) }) }) - describe('pubKeyHash', function() { + describe('pubKeyHashInput', function() { fixtures.valid.forEach(function(f) { if (f.type !== 'pubkeyhash') return var pubKey = ECPubKey.fromHex(f.pubKey) - var address = pubKey.getAddress() - describe('input script', function() { - it('is generated correctly for ' + address, function() { - var signature = new Buffer(f.signature, 'hex') + it('returns ' + f.scriptSig, function() { + var signature = new Buffer(f.signature, 'hex') - var scriptSig = scripts.pubKeyHashInput(signature, pubKey) - assert.equal(scriptSig.toASM(), f.scriptSig) - }) + var scriptSig = scripts.pubKeyHashInput(signature, pubKey) + assert.equal(scriptSig.toASM(), f.scriptSig) }) + }) + }) - describe('output script', function() { - it('is generated correctly for ' + address, function() { - var scriptPubKey = scripts.pubKeyHashOutput(address.hash) - assert.equal(scriptPubKey.toASM(), f.scriptPubKey) - }) + describe('pubKeyHashOutput', function() { + fixtures.valid.forEach(function(f) { + if (f.type !== 'pubkeyhash') return + + var pubKey = ECPubKey.fromHex(f.pubKey) + var address = pubKey.getAddress() + + it('returns ' + f.scriptPubKey, function() { + var scriptPubKey = scripts.pubKeyHashOutput(address.hash) + assert.equal(scriptPubKey.toASM(), f.scriptPubKey) }) }) }) - describe('multisig', function() { + describe('multisigInput', function() { fixtures.valid.forEach(function(f) { if (f.type !== 'multisig') return + it('returns ' + f.scriptSig, function() { + var signatures = f.signatures.map(function(signature) { + return signature ? new Buffer(signature, 'hex') : ops.OP_0 + }) + + var scriptSig = scripts.multisigInput(signatures) + assert.equal(scriptSig.toASM(), f.scriptSig) + }) + }) + + fixtures.invalid.multisigInput.forEach(function(f) { var pubKeys = f.pubKeys.map(ECPubKey.fromHex) var scriptPubKey = scripts.multisigOutput(pubKeys.length, pubKeys) - describe('input script', function() { - it('is generated correctly for ' + f.scriptPubKey, function() { - var signatures = f.signatures.map(function(signature) { - return new Buffer(signature, 'hex') - }) - - var scriptSig = scripts.multisigInput(signatures) - assert.equal(scriptSig.toASM(), f.scriptSig) + it('throws on ' + f.exception, function() { + var signatures = f.signatures.map(function(signature) { + return signature ? new Buffer(signature, 'hex') : ops.OP_0 }) - }) - describe('output script', function() { - it('is generated correctly for ' + f.scriptPubKey, function() { - assert.equal(scriptPubKey.toASM(), f.scriptPubKey) - }) + assert.throws(function() { + scripts.multisigInput(signatures, scriptPubKey) + }, new RegExp(f.exception)) }) }) + }) + + describe('multisigOutput', function() { + fixtures.valid.forEach(function(f) { + if (f.type !== 'multisig') return - fixtures.invalid.multisig.forEach(function(f) { var pubKeys = f.pubKeys.map(ECPubKey.fromHex) var scriptPubKey = scripts.multisigOutput(pubKeys.length, pubKeys) - if (f.scriptPubKey) { - describe('output script', function() { - it('throws on ' + f.exception, function() { - assert.throws(function() { - scripts.multisigOutput(f.m, pubKeys) - }, new RegExp(f.exception)) - }) - }) - } else { - describe('input script', function() { - it('throws on ' + f.exception, function() { - var signatures = f.signatures.map(function(signature) { - return new Buffer(signature, 'hex') - }) + it('returns ' + f.scriptPubKey, function() { + assert.equal(scriptPubKey.toASM(), f.scriptPubKey) + }) + }) - assert.throws(function() { - scripts.multisigInput(signatures, scriptPubKey) - }, new RegExp(f.exception)) - }) - }) - } + fixtures.invalid.multisigOutput.forEach(function(f) { + var pubKeys = f.pubKeys.map(ECPubKey.fromHex) + + it('throws on ' + f.exception, function() { + assert.throws(function() { + scripts.multisigOutput(f.m, pubKeys) + }, new RegExp(f.exception)) + }) }) }) - describe('scripthash', function() { + describe('scriptHashInput', function() { fixtures.valid.forEach(function(f) { if (f.type !== 'scripthash') return var redeemScript = Script.fromASM(f.redeemScript) var redeemScriptSig = Script.fromASM(f.redeemScriptSig) - var address = Address.fromOutputScript(Script.fromASM(f.scriptPubKey)) + it('returns ' + f.scriptSig, function() { + var scriptSig = scripts.scriptHashInput(redeemScriptSig, redeemScript) - describe('input script', function() { - it('is generated correctly for ' + address, function() { - var scriptSig = scripts.scriptHashInput(redeemScriptSig, redeemScript) + assert.equal(scriptSig.toASM(), f.scriptSig) + }) + }) + }) - assert.equal(scriptSig.toASM(), f.scriptSig) - }) + describe('scriptHashOutput', function() { + fixtures.valid.forEach(function(f) { + if (f.type !== 'scripthash') return + + var redeemScript = Script.fromASM(f.redeemScript) + + it('returns ' + f.scriptPubKey, function() { + var scriptPubKey = scripts.scriptHashOutput(redeemScript.getHash()) + + assert.equal(scriptPubKey.toASM(), f.scriptPubKey) }) + }) + }) - describe('output script', function() { - it('is generated correctly for ' + address, function() { - var scriptPubKey = scripts.scriptHashOutput(redeemScript.getHash()) + describe('nullDataOutput', function() { + fixtures.valid.forEach(function(f) { + if (f.type !== 'nulldata') return - assert.equal(scriptPubKey.toASM(), f.scriptPubKey) - }) + var data = new Buffer(f.data, 'hex') + var scriptPubKey = scripts.nullDataOutput(data) + + it('returns ' + f.scriptPubKey, function() { + assert.equal(scriptPubKey.toASM(), f.scriptPubKey) }) }) }) diff --git a/test/transaction.js b/test/transaction.js index a8aa1d79c..c7fd4f87f 100644 --- a/test/transaction.js +++ b/test/transaction.js @@ -1,34 +1,46 @@ var assert = require('assert') -var networks = require('../src/networks') var scripts = require('../src/scripts') var Address = require('../src/address') var ECKey = require('../src/eckey') var Transaction = require('../src/transaction') +var Script = require('../src/script') var fixtures = require('./fixtures/transaction') -// FIXME: what is a better way to do this, seems a bit odd -fixtures.valid.forEach(function(f) { - var Script = require('../src/script') +describe('Transaction', function() { + function fromRaw(raw) { + var tx = new Transaction() + tx.version = raw.version + tx.locktime = raw.locktime - f.raw.ins.forEach(function(fin) { - fin.hash = new Buffer(fin.hash, 'hex') - fin.script = Script.fromHex(fin.script) - }) + raw.ins.forEach(function(txIn) { + var txHash = new Buffer(txIn.hash, 'hex') + var script - f.raw.outs.forEach(function(fout) { - fout.script = Script.fromHex(fout.script) - }) -}) + if (txIn.data) { + script = new Script(new Buffer(txIn.data, 'hex'), []) + + } else if (txIn.script) { + script = Script.fromASM(txIn.script) + } + + tx.addInput(txHash, txIn.index, txIn.sequence, script) + }) + + raw.outs.forEach(function(txOut) { + tx.addOutput(Script.fromASM(txOut.script), txOut.value) + }) + + return tx + } -describe('Transaction', function() { describe('fromBuffer/fromHex', function() { fixtures.valid.forEach(function(f) { - it('imports ' + f.txid + ' correctly', function() { + it('imports ' + f.id + ' correctly', function() { var actual = Transaction.fromHex(f.hex) - assert.deepEqual(actual, f.raw) + assert.deepEqual(actual.toHex(), f.hex) }) }) @@ -43,10 +55,10 @@ describe('Transaction', function() { describe('toBuffer/toHex', function() { fixtures.valid.forEach(function(f) { - it('exports ' + f.txid + ' correctly', function() { - var actual = Transaction.prototype.toBuffer.call(f.raw) + it('exports ' + f.id + ' correctly', function() { + var actual = fromRaw(f.raw) - assert.equal(actual.toString('hex'), f.hex) + assert.deepEqual(actual.toHex(), f.hex) }) }) }) @@ -96,22 +108,11 @@ describe('Transaction', function() { assert.equal(tx.ins[0].sequence, Transaction.DEFAULT_SEQUENCE) }) - fixtures.valid.forEach(function(f) { - it('should add the inputs for ' + f.txid + ' correctly', function() { - var tx = new Transaction() - - f.raw.ins.forEach(function(txIn, i) { - var j = tx.addInput(txIn.hash, txIn.index, txIn.sequence) - - assert.equal(i, j) - assert.deepEqual(tx.ins[i].hash, txIn.hash) - assert.equal(tx.ins[i].index, txIn.index) + it('defaults to empty script', function() { + var tx = new Transaction() + tx.addInput(prevTxHash, 0) - var sequence = txIn.sequence - if (sequence == undefined) sequence = Transaction.DEFAULT_SEQUENCE - assert.equal(tx.ins[i].sequence, sequence) - }) - }) + assert.equal(tx.ins[0].script, Script.EMPTY) }) fixtures.invalid.addInput.forEach(function(f) { @@ -165,26 +166,16 @@ describe('Transaction', function() { assert.equal(tx.addOutput(destScript, 40000), 0) assert.equal(tx.addOutput(destScript, 40000), 1) }) - - fixtures.valid.forEach(function(f) { - it('should add the outputs for ' + f.txid + ' correctly', function() { - var tx = new Transaction() - - f.raw.outs.forEach(function(txOut, i) { - var j = tx.addOutput(txOut.script, txOut.value) - - assert.equal(i, j) - }) - - assert.deepEqual(tx.outs, f.raw.outs) - }) - }) }) describe('clone', function() { fixtures.valid.forEach(function(f) { - var expected = Transaction.fromHex(f.hex) - var actual = expected.clone() + var actual, expected + + beforeEach(function() { + expected = Transaction.fromHex(f.hex) + actual = expected.clone() + }) it('should have value equality', function() { assert.deepEqual(actual, expected) @@ -198,18 +189,18 @@ describe('Transaction', function() { describe('getId', function() { fixtures.valid.forEach(function(f) { - it('should return the txid for ' + f.txid, function() { + it('should return the id for ' + f.id, function() { var tx = Transaction.fromHex(f.hex) var actual = tx.getId() - assert.equal(actual, f.txid) + assert.equal(actual, f.id) }) }) }) describe('getHash', function() { fixtures.valid.forEach(function(f) { - it('should return the hash for ' + f.txid, function() { + it('should return the hash for ' + f.id, function() { var tx = Transaction.fromHex(f.hex) var actual = tx.getHash().toString('hex') @@ -221,7 +212,7 @@ describe('Transaction', function() { // TODO: // hashForSignature: [Function], - // FIXME: could be better + // FIXME: remove in 2.x.y describe('signInput/validateInput', function() { it('works for multi-sig redeem script', function() { var tx = new Transaction() diff --git a/test/transaction_builder.js b/test/transaction_builder.js new file mode 100644 index 000000000..cf14df6b0 --- /dev/null +++ b/test/transaction_builder.js @@ -0,0 +1,237 @@ +var assert = require('assert') + +var BigInteger = require('bigi') +var ECKey = require('../src/eckey') +var Script = require('../src/script') +var Transaction = require('../src/transaction') +var TransactionBuilder = require('../src/transaction_builder') + +var fixtures = require('./fixtures/transaction_builder') + +function construct(txb, f, sign) { + f.inputs.forEach(function(input) { + var prevTxScript + + if (input.prevTxScript) { + prevTxScript = Script.fromASM(input.prevTxScript) + } + + txb.addInput(input.txId, input.vout, input.sequence, prevTxScript) + }) + + f.outputs.forEach(function(output) { + var script = Script.fromASM(output.script) + + txb.addOutput(script, output.value) + }) + + if (sign === undefined || sign) { + f.inputs.forEach(function(input, index) { + input.signs.forEach(function(sign) { + var privKey = ECKey.fromWIF(sign.privKey) + var redeemScript + + if (sign.redeemScript) { + redeemScript = Script.fromASM(sign.redeemScript) + } + + txb.sign(index, privKey, redeemScript, sign.hashType) + }) + }) + } + + // FIXME: add support for locktime/version in TransactionBuilder API + if (f.version !== undefined) txb.tx.version = f.version + if (f.locktime !== undefined) txb.tx.locktime = f.locktime +} + +describe('TransactionBuilder', function() { + var privAddress, privScript + var prevTx, prevTxHash + var privKey + var txb + + beforeEach(function() { + txb = new TransactionBuilder() + + prevTx = new Transaction() + prevTx.addOutput('1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH', 0) + prevTx.addOutput('1cMh228HTCiwS8ZsaakH8A8wze1JR5ZsP', 1) + prevTxHash = prevTx.getHash() + + privKey = new ECKey(BigInteger.ONE, false) + privAddress = privKey.pub.getAddress() + privScript = privAddress.toOutputScript() + }) + + describe('addInput', function() { + it('accepts a txHash, index [and sequence number]', function() { + var vin = txb.addInput(prevTxHash, 1, 54) + assert.equal(vin, 0) + + var txIn = txb.tx.ins[0] + assert.equal(txIn.hash, prevTxHash) + assert.equal(txIn.index, 1) + assert.equal(txIn.sequence, 54) + assert.equal(txb.inputs[0].prevOutScript, undefined) + }) + + it('accepts a txHash, index [, sequence number and scriptPubKey]', function() { + var vin = txb.addInput(prevTxHash, 1, 54, prevTx.outs[1].script) + assert.equal(vin, 0) + + var txIn = txb.tx.ins[0] + assert.equal(txIn.hash, prevTxHash) + assert.equal(txIn.index, 1) + assert.equal(txIn.sequence, 54) + assert.equal(txb.inputs[0].prevOutScript, prevTx.outs[1].script) + }) + + it('accepts a prevTx, index [and sequence number]', function() { + var vin = txb.addInput(prevTx, 1, 54) + assert.equal(vin, 0) + + var txIn = txb.tx.ins[0] + assert.deepEqual(txIn.hash, prevTxHash) + assert.equal(txIn.index, 1) + assert.equal(txIn.sequence, 54) + assert.equal(txb.inputs[0].prevOutScript, prevTx.outs[1].script) + }) + + it('returns the input index', function() { + assert.equal(txb.addInput(prevTxHash, 0), 0) + assert.equal(txb.addInput(prevTxHash, 1), 1) + }) + + it('throws if SIGHASH_ALL has been used to sign any existing scriptSigs', function() { + txb.addInput(prevTxHash, 0) + txb.sign(0, privKey) + + assert.throws(function() { + txb.addInput(prevTxHash, 0) + }, /No, this would invalidate signatures/) + }) + }) + + describe('addOutput', function() { + it('throws if SIGHASH_ALL has been used to sign any existing scriptSigs', function() { + txb.addInput(prevTxHash, 0) + txb.addOutput(privScript, 2000) + txb.sign(0, privKey) + + assert.throws(function() { + txb.addOutput(privScript, 9000) + }, /No, this would invalidate signatures/) + }) + }) + + describe('sign', function() { + fixtures.invalid.sign.forEach(function(f) { + it('throws on ' + f.exception + ' (' + f.description + ')', function() { + construct(txb, f, false) + + f.inputs.forEach(function(input, index) { + input.signs.forEach(function(sign) { + var privKey = ECKey.fromWIF(sign.privKey) + var redeemScript + + if (sign.redeemScript) { + redeemScript = Script.fromASM(sign.redeemScript) + } + + if (!sign.throws) { + txb.sign(index, privKey, redeemScript, sign.hashType) + + } else { + assert.throws(function() { + txb.sign(index, privKey, redeemScript, sign.hashType) + }, new RegExp(f.exception)) + } + }) + }) + }) + }) + }) + + describe('build', function() { + fixtures.valid.build.forEach(function(f) { + it('builds \"' + f.description + '\"', function() { + construct(txb, f) + + var tx = txb.build() + assert.equal(tx.toHex(), f.txHex) + }) + }) + + fixtures.invalid.build.forEach(function(f) { + describe('for ' + (f.description || f.exception), function() { + beforeEach(function() { + if (f.txHex) { + var tx = Transaction.fromHex(f.txHex) + txb = TransactionBuilder.fromTransaction(tx) + + } else { + construct(txb, f) + } + }) + + it('throws', function() { + assert.throws(function() { + txb.build() + }, new RegExp(f.exception)) + }) + + if (f.alwaysThrows) return + it('doesn\'t throw if building incomplete', function() { + txb.buildIncomplete() + }) + }) + }) + }) + + describe('fromTransaction', function() { + fixtures.valid.build.forEach(function(f) { + it('builds the correct TransactionBuilder for ' + f.description, function() { + var tx = Transaction.fromHex(f.txHex) + var txb = TransactionBuilder.fromTransaction(tx) + + assert.equal(txb.build().toHex(), f.txHex) + }) + }) + + fixtures.invalid.fromTransaction.forEach(function(f) { + it('throws on ' + f.exception, function() { + var tx = Transaction.fromHex(f.txHex) + + assert.throws(function() { + TransactionBuilder.fromTransaction(tx) + }, new RegExp(f.exception)) + }) + }) + + it('works for the out-of-order P2SH multisig case', function() { + var privKeys = [ + "91avARGdfge8E4tZfYLoxeJ5sGBdNJQH4kvjJoQFacbgww7vXtT", + "91avARGdfge8E4tZfYLoxeJ5sGBdNJQH4kvjJoQFacbgwmaKkrx" + ].map(ECKey.fromWIF) + var redeemScript = Script.fromASM("OP_2 0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8 04c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a OP_2 OP_CHECKMULTISIG") + + txb.addInput("4971f016798a167331bcbc67248313fbc444c6e92e4416efd06964425588f5cf", 0) + txb.addOutput("1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH", 10000) + txb.sign(0, privKeys[0], redeemScript) + + var tx = txb.buildIncomplete() + + // in another galaxy... + // ... far, far away + var txb2 = TransactionBuilder.fromTransaction(tx) + + // [you should] verify that Transaction is what you want... + // ... then sign it + txb2.sign(0, privKeys[1], redeemScript) + var tx2 = txb2.build() + + assert.equal(tx2.toHex(), '0100000001cff58855426469d0ef16442ee9c644c4fb13832467bcbc3173168a7916f0714900000000fd1c01004830450221009c92c1ae1767ac04e424da7f6db045d979b08cde86b1ddba48621d59a109d818022004f5bb21ad72255177270abaeb2d7940ac18f1e5ca1f53db4f3fd1045647a8a8014830450221009418caa5bc18da87b188a180125c0cf06dce6092f75b2d3c01a29493466800fd02206ead65e7ca6e0f17eefe6f78457c084eab59af7c9882be1437de2e7116358eb9014c8752410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b84104c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a52aeffffffff0110270000000000001976a914751e76e8199196d454941c45d1b3a323f1433bd688ac00000000') + }) + }) +}) diff --git a/test/wallet.js b/test/wallet.js index 76d830f5b..ccb5f2269 100644 --- a/test/wallet.js +++ b/test/wallet.js @@ -1,4 +1,5 @@ var assert = require('assert') +var bufferutils = require('../src/bufferutils') var crypto = require('../src/crypto') var networks = require('../src/networks') var sinon = require('sinon') @@ -7,6 +8,7 @@ var scripts = require('../src/scripts') var Address = require('../src/address') var HDNode = require('../src/hdnode') var Transaction = require('../src/transaction') +var TransactionBuilder = require('../src/transaction_builder') var Wallet = require('../src/wallet') var fixtureTxes = require('./fixtures/mainnet_tx') @@ -26,13 +28,17 @@ function fakeTxId(i) { } describe('Wallet', function() { - var seed, wallet - beforeEach(function(){ + var seed + beforeEach(function() { seed = crypto.sha256("don't use a string seed like this in real life") - wallet = new Wallet(seed) }) describe('constructor', function() { + var wallet + beforeEach(function() { + wallet = new Wallet(seed) + }) + it('defaults to Bitcoin network', function() { assert.equal(wallet.getMasterKey().network, networks.bitcoin) }) @@ -55,10 +61,10 @@ describe('Wallet', function() { assert.equal(account.depth, 2) }) - describe('when seed is not specified', function(){ - it('generates a seed', function(){ + describe('when seed is not specified', function() { + it('generates a seed', function() { var wallet = new Wallet() - assert.ok(wallet.getMasterKey()) + assert(wallet.getMasterKey()) }) }) @@ -73,8 +79,8 @@ describe('Wallet', function() { }) }) - describe('newMasterKey', function(){ - it('resets accounts', function(){ + describe('newMasterKey', function() { + it('resets accounts', function() { var wallet = new Wallet() var oldAccountZero = wallet.getAccountZero() var oldExternalAccount = wallet.getExternalAccount() @@ -86,7 +92,7 @@ describe('Wallet', function() { assertNotEqual(wallet.getInternalAccount(), oldInternalAccount) }) - it('resets addresses', function(){ + it('resets addresses', function() { var wallet = new Wallet() wallet.generateAddress() wallet.generateChangeAddress() @@ -101,8 +107,8 @@ describe('Wallet', function() { }) }) - describe('generateAddress', function(){ - it('generate receiving addresses', function(){ + describe('generateAddress', function() { + it('generate receiving addresses', function() { var wallet = new Wallet(seed, networks.testnet) var expectedAddresses = [ "n1GyUANZand9Kw6hGSV9837cCC9FFUQzQa", @@ -115,8 +121,13 @@ describe('Wallet', function() { }) }) - describe('generateChangeAddress', function(){ - it('generates change addresses', function(){ + describe('generateChangeAddress', function() { + var wallet + beforeEach(function() { + wallet = new Wallet(seed) + }) + + it('generates change addresses', function() { var wallet = new Wallet(seed, networks.testnet) var expectedAddresses = ["mnXiDR4MKsFxcKJEZjx4353oXvo55iuptn"] @@ -125,8 +136,13 @@ describe('Wallet', function() { }) }) - describe('getPrivateKey', function(){ - it('returns the private key at the given index of external account', function(){ + describe('getPrivateKey', function() { + var wallet + beforeEach(function() { + wallet = new Wallet(seed) + }) + + it('returns the private key at the given index of external account', function() { var wallet = new Wallet(seed, networks.testnet) assertEqual(wallet.getPrivateKey(0), wallet.getExternalAccount().derive(0).privKey) @@ -134,8 +150,13 @@ describe('Wallet', function() { }) }) - describe('getInternalPrivateKey', function(){ - it('returns the private key at the given index of internal account', function(){ + describe('getInternalPrivateKey', function() { + var wallet + beforeEach(function() { + wallet = new Wallet(seed) + }) + + it('returns the private key at the given index of internal account', function() { var wallet = new Wallet(seed, networks.testnet) assertEqual(wallet.getInternalPrivateKey(0), wallet.getInternalAccount().derive(0).privKey) @@ -143,8 +164,13 @@ describe('Wallet', function() { }) }) - describe('getPrivateKeyForAddress', function(){ - it('returns the private key for the given address', function(){ + describe('getPrivateKeyForAddress', function() { + var wallet + beforeEach(function() { + wallet = new Wallet(seed) + }) + + it('returns the private key for the given address', function() { var wallet = new Wallet(seed, networks.testnet) wallet.generateChangeAddress() wallet.generateAddress() @@ -160,107 +186,137 @@ describe('Wallet', function() { ) }) - it('raises an error when address is not found', function(){ + it('raises an error when address is not found', function() { var wallet = new Wallet(seed, networks.testnet) + assert.throws(function() { wallet.getPrivateKeyForAddress("n2fiWrHqD6GM5GiEqkbWAc6aaZQp3ba93X") - }, /Unknown address. Make sure the address is from the keychain and has been generated./) + }, /Unknown address. Make sure the address is from the keychain and has been generated/) }) }) - describe('Unspent Outputs', function(){ - var expectedUtxo, expectedOutputKey - beforeEach(function(){ - expectedUtxo = { - "hash":"6a4062273ac4f9ea4ffca52d9fd102b08f6c32faa0a4d1318e3a7b2e437bb9c7", - "outputIndex": 0, + describe('Unspent Outputs', function() { + var utxo, expectedOutputKey + var wallet + + beforeEach(function() { + utxo = { "address" : "1AZpKpcfCzKDUeTFBQUL4MokQai3m3HMXv", + "confirmations": 1, + "index": 0, + "txId": fakeTxId(6), "value": 20000, - "pending": true + "pending": false } - expectedOutputKey = expectedUtxo.hash + ":" + expectedUtxo.outputIndex }) - function addUtxoToOutput(utxo){ - var key = utxo.hash + ":" + utxo.outputIndex - wallet.outputs[key] = { - from: key, - address: utxo.address, - value: utxo.value, - pending: utxo.pending - } - } + describe('on construction', function() { + beforeEach(function() { + wallet = new Wallet(seed, networks.bitcoin) + wallet.setUnspentOutputs([utxo]) + }) - describe('getBalance', function(){ - var utxo1 + it('matches the expected behaviour', function() { + var output = wallet.unspents[0] - beforeEach(function(){ - utxo1 = cloneObject(expectedUtxo) - utxo1.hash = utxo1.hash.replace('7', 'l') + assert.equal(output.address, utxo.address) + assert.equal(output.value, utxo.value) }) + }) - it('sums over utxo values', function(){ - addUtxoToOutput(expectedUtxo) - addUtxoToOutput(utxo1) + describe('getBalance', function() { + beforeEach(function() { + var utxo1 = cloneObject(utxo) + utxo1.hash = fakeTxId(5) + wallet = new Wallet(seed, networks.bitcoin) + wallet.setUnspentOutputs([utxo, utxo1]) + }) + + it('sums over utxo values', function() { assert.equal(wallet.getBalance(), 40000) }) }) - describe('getUnspentOutputs', function(){ - beforeEach(function(){ - addUtxoToOutput(expectedUtxo) + describe('getUnspentOutputs', function() { + beforeEach(function() { + wallet = new Wallet(seed, networks.bitcoin) + wallet.setUnspentOutputs([utxo]) }) - it('parses wallet outputs to the expect format', function(){ - assert.deepEqual(wallet.getUnspentOutputs(), [expectedUtxo]) + it('parses wallet unspents to the expected format', function() { + var outputs = wallet.getUnspentOutputs() + var output = outputs[0] + + assert.equal(utxo.address, output.address) + assert.equal(utxo.index, output.index) + assert.equal(utxo.value, output.value) + + // FIXME: remove in 2.0.0 + assert.equal(utxo.txId, output.hash) + assert.equal(utxo.pending, output.pending) + + // new in 2.0.0 + assert.equal(utxo.txId, output.txId) + assert.equal(utxo.confirmations, output.confirmations) }) - it("ignores pending spending outputs (outputs with 'to' property)", function(){ - var output = wallet.outputs[expectedOutputKey] - output.to = fakeTxId(0) + ':' + 0 - output.pending = true + it("ignores spent unspents (outputs with 'spent' property)", function() { + var unspent = wallet.unspents[0] + unspent.pending = true + unspent.spent = true assert.deepEqual(wallet.getUnspentOutputs(), []) }) }) + }) - describe('setUnspentOutputs', function(){ - var utxo - beforeEach(function(){ - utxo = cloneObject([expectedUtxo]) - }) + describe('setUnspentOutputs', function() { + var utxo + var expectedOutputKey + var wallet + + beforeEach(function() { + utxo = { + hash: fakeTxId(0), + index: 0, + address: '115qa7iPZqn6as57hxLL8E9VUnhmGQxKWi', + value: 500000 + } - it('matches the expected behaviour', function(){ - wallet.setUnspentOutputs(utxo) - verifyOutputs() - }) + wallet = new Wallet(seed, networks.bitcoin) + }) + + it('matches the expected behaviour', function() { + wallet.setUnspentOutputs([utxo]) - describe('required fields', function(){ - ['outputIndex', 'address', 'hash', 'value'].forEach(function(field){ - it("throws an error when " + field + " is missing", function(){ - delete utxo[0][field] + var output = wallet.unspents[0] + assert.equal(output.value, utxo.value) + assert.equal(output.address, utxo.address) + }) - assert.throws(function() { - wallet.setUnspentOutputs(utxo) - }, new RegExp('Invalid unspent output: key ' + field + ' is missing')) + describe('required fields', function() { + ['index', 'address', 'hash', 'value'].forEach(function(field){ + it("throws an error when " + field + " is missing", function() { + delete utxo[field] + + assert.throws(function() { + wallet.setUnspentOutputs([utxo]) }) }) }) - - function verifyOutputs() { - var output = wallet.outputs[expectedOutputKey] - assert(output) - assert.equal(output.value, utxo[0].value) - assert.equal(output.address, utxo[0].address) - } }) }) - describe('Process transaction', function(){ + describe('Process transaction', function() { + var wallet + beforeEach(function() { + wallet = new Wallet(seed) + }) + var addresses var tx - beforeEach(function(){ + beforeEach(function() { addresses = [ '115qa7iPZqn6as57hxLL8E9VUnhmGQxKWi', '1Bu3bhwRmevHLAy1JrRB6AfcxfgDG2vXRd', @@ -270,57 +326,53 @@ describe('Wallet', function() { tx = Transaction.fromHex(fixtureTx1Hex) }) - describe("processPendingTx", function(){ - it("incoming: sets the pending flag on output", function(){ + describe("processPendingTx", function() { + it("incoming: sets the pending flag on output", function() { wallet.addresses = [addresses[0]] wallet.processPendingTx(tx) verifyOutputAdded(0, true) }) - describe("when tx ins outpoint contains a known txhash:i", function(){ + describe("when tx ins outpoint contains a known txhash:i", function() { var spendTx - beforeEach(function(){ + beforeEach(function() { wallet.addresses = [addresses[0]] wallet.processConfirmedTx(tx) spendTx = Transaction.fromHex(fixtureTx2Hex) }) - it("outgoing: sets the pending flag and 'to' on output", function(){ + it("outgoing: sets the pending flag and 'spent' on output", function() { var txIn = spendTx.ins[0] var txInId = new Buffer(txIn.hash) Array.prototype.reverse.call(txInId) txInId = txInId.toString('hex') - var key = txInId + ':' + txIn.index - assert(!wallet.outputs[key].pending) + var unspent = wallet.unspents[0] + assert(!unspent.pending) wallet.processPendingTx(spendTx) - assert(wallet.outputs[key].pending) - assert.equal(wallet.outputs[key].to, spendTx.getId() + ':' + 0) + assert(unspent.pending) + assert(unspent.spent, true) }) }) }) - describe('processConfirmedTx', function(){ - it('does not fail on scripts with no corresponding Address', function() { + describe('processConfirmedTx', function() { + it('does not throw on scripts with no corresponding Address', function() { var pubKey = wallet.getPrivateKey(0).pub var script = scripts.pubKeyOutput(pubKey) var tx2 = new Transaction() - tx2.addInput(fakeTxId(1), 0) - // FIXME: Transaction doesn't support custom ScriptPubKeys... yet - // So for now, we hijack the script with our own, and undefine the cached address - tx2.addOutput(addresses[0], 10000) - tx2.outs[0].script = script - tx2.outs[0].address = undefined + tx2.addInput(fakeTxHash(1), 0) + tx2.addOutput(script, 10000) wallet.processConfirmedTx(tx2) }) - describe("when tx outs contains an address owned by the wallet, an 'output' gets added to wallet.outputs", function(){ - it("works for receive address", function(){ + describe("when tx outs contains an address owned by the wallet, an 'output' gets added to wallet.unspentMap", function() { + it("works for receive address", function() { var totalOuts = outputCount() wallet.addresses = [addresses[0]] @@ -330,7 +382,7 @@ describe('Wallet', function() { verifyOutputAdded(0, false) }) - it("works for change address", function(){ + it("works for change address", function() { var totalOuts = outputCount() wallet.changeAddresses = [addresses[1]] @@ -340,50 +392,50 @@ describe('Wallet', function() { verifyOutputAdded(1, false) }) - function outputCount(){ - return Object.keys(wallet.outputs).length + function outputCount() { + return Object.keys(wallet.unspentMap).length } }) - describe("when tx ins outpoint contains a known txhash:i", function(){ + describe("when tx ins contains a known txhash:i", function() { var spendTx - beforeEach(function(){ + beforeEach(function() { wallet.addresses = [addresses[0]] // the address fixtureTx2 used as input wallet.processConfirmedTx(tx) spendTx = Transaction.fromHex(fixtureTx2Hex) }) - it("does not add to wallet.outputs", function(){ + it("does not add to wallet.unspentMap", function() { wallet.processConfirmedTx(spendTx) - assert.deepEqual(wallet.outputs, {}) + assert.deepEqual(wallet.unspentMap, {}) }) - it("deletes corresponding 'output'", function(){ + it("deletes corresponding 'unspent'", function() { var txIn = spendTx.ins[0] - var txInId = new Buffer(txIn.hash) - Array.prototype.reverse.call(txInId) - txInId = txInId.toString('hex') + var txInId = bufferutils.reverse(txIn.hash).toString('hex') var expected = txInId + ':' + txIn.index - assert(expected in wallet.outputs) + assert(expected in wallet.unspentMap) wallet.processConfirmedTx(spendTx) - assert(!(expected in wallet.outputs)) + assert(!(expected in wallet.unspentMap)) }) }) + }) - it("does nothing when none of the involved addresses belong to the wallet", function(){ - wallet.processConfirmedTx(tx) - assert.deepEqual(wallet.outputs, {}) - }) + it("does nothing when none of the involved addresses belong to the wallet", function() { + wallet.processConfirmedTx(tx) + assert.deepEqual(wallet.unspentMap, {}) }) + function verifyOutputAdded(index, pending) { var txOut = tx.outs[index] + var key = tx.getId() + ":" + index - var output = wallet.outputs[key] - assert.equal(output.from, key) + var output = wallet.unspentMap[key] + assert.deepEqual(output.txHash, tx.getHash()) assert.equal(output.value, txOut.value) assert.equal(output.pending, pending) @@ -392,88 +444,95 @@ describe('Wallet', function() { } }) - describe('createTx', function(){ - var to, value + describe('createTx', function() { + var wallet var address1, address2 + var to, value - beforeEach(function(){ - to = '15mMHKL96tWAUtqF3tbVf99Z8arcmnJrr3' + beforeEach(function() { + to = 'mt7MyTVVEWnbwpF5hBn6fgnJcv95Syk2ue' value = 500000 - // generate 2 addresses - address1 = wallet.generateAddress() - address2 = wallet.generateAddress() + address1 = "n1GyUANZand9Kw6hGSV9837cCC9FFUQzQa" + address2 = "n2fiWrHqD6GM5GiEqkbWAc6aaZQp3ba93X" - // set up 3 utxo - utxo = [ + // set up 3 utxos + var utxos = [ { - "hash": fakeTxId(1), - "outputIndex": 0, - "address" : address1, + "txId": fakeTxId(1), + "index": 0, + "address": address1, "value": 400000 // not enough for value }, { - "hash": fakeTxId(2), - "outputIndex": 1, - "address" : address1, + "txId": fakeTxId(2), + "index": 1, + "address": address1, "value": 500000 // enough for only value }, { - "hash": fakeTxId(3), - "outputIndex": 0, + "txId": fakeTxId(3), + "index": 0, "address" : address2, "value": 510000 // enough for value and fee } ] - wallet.setUnspentOutputs(utxo) + + wallet = new Wallet(seed, networks.testnet) + wallet.setUnspentOutputs(utxos) + wallet.generateAddress() + wallet.generateAddress() }) - describe('transaction fee', function(){ - it('allows fee to be specified', function(){ + describe('transaction fee', function() { + it('allows fee to be specified', function() { var fee = 30000 - var tx = wallet.createTx(to, value, fee) + var tx = wallet.createTx(to, value, { fixedFee: fee }) assert.equal(getFee(wallet, tx), fee) }) - it('allows fee to be set to zero', function(){ + it('allows fee to be set to zero', function() { value = 510000 var fee = 0 - var tx = wallet.createTx(to, value, fee) + var tx = wallet.createTx(to, value, { fixedFee: fee }) assert.equal(getFee(wallet, tx), fee) }) - it('does not overestimate fees when network has dustSoftThreshold', function(){ - var wallet = new Wallet(seed, networks.litecoin) - var address = wallet.generateAddress() - wallet.setUnspentOutputs([{ - hash: fakeTxId(0), - outputIndex: 0, - address: address, + it('does not overestimate fees when network has dustSoftThreshold', function() { + var utxo = { + txId: fakeTxId(0), + index: 0, + address: "LeyySKbQrRRwodKEj1W4a8y3YQupPLw5os", value: 500000 - }]) + } + + var wallet = new Wallet(seed, networks.litecoin) + wallet.setUnspentOutputs([utxo]) + wallet.generateAddress() value = 200000 - var tx = wallet.createTx(address, value) + var tx = wallet.createTx(utxo.address, value) assert.equal(getFee(wallet, tx), 100000) }) function getFee(wallet, tx) { - var inputValue = tx.ins.reduce(function(memo, input){ - var id = Array.prototype.reverse.call(input.hash).toString('hex') - return memo + wallet.outputs[id + ':' + input.index].value + var inputValue = tx.ins.reduce(function(accum, input) { + var txId = bufferutils.reverse(input.hash).toString('hex') + + return accum + wallet.unspentMap[txId + ':' + input.index].value }, 0) - return tx.outs.reduce(function(memo, output){ - return memo - output.value + return tx.outs.reduce(function(accum, output) { + return accum - output.value }, inputValue) } }) - describe('choosing utxo', function(){ - it('takes fees into account', function(){ + describe('choosing utxo', function() { + it('takes fees into account', function() { var tx = wallet.createTx(to, value) assert.equal(tx.ins.length, 1) @@ -481,69 +540,44 @@ describe('Wallet', function() { assert.equal(tx.ins[0].index, 0) }) - it('ignores pending outputs', function(){ - utxo.push( - { - "hash": fakeTxId(4), - "outputIndex": 0, - "address" : address2, - "value": 530000, - "pending": true - } - ) - wallet.setUnspentOutputs(utxo) + it('uses confirmed outputs', function() { + var tx2 = new Transaction() + tx2.addInput(fakeTxId(4), 0) + tx2.addOutput(address2, 530000) + wallet.processConfirmedTx(tx2) var tx = wallet.createTx(to, value) assert.equal(tx.ins.length, 1) - assert.deepEqual(tx.ins[0].hash, fakeTxHash(3)) + assert.deepEqual(tx.ins[0].hash, tx2.getHash()) assert.equal(tx.ins[0].index, 0) }) - }) - - describe('works for testnet', function(){ - it('should create transaction', function(){ - var wallet = new Wallet(seed, networks.testnet) - var address = wallet.generateAddress() - wallet.setUnspentOutputs([{ - hash: fakeTxId(0), - outputIndex: 0, - address: address, - value: value - }]) - - var to = 'mt7MyTVVEWnbwpF5hBn6fgnJcv95Syk2ue' - var toValue = value - 10000 + it('ignores pending outputs', function() { + var tx2 = new Transaction() + tx2.addInput(fakeTxId(4), 0) + tx2.addOutput(address2, 530000) - var tx = wallet.createTx(to, toValue) - assert.equal(tx.outs.length, 1) + wallet.processPendingTx(tx2) + var tx = wallet.createTx(to, value) - var outAddress = Address.fromOutputScript(tx.outs[0].script, networks.testnet) - assert.equal(outAddress.toString(), to) - assert.equal(tx.outs[0].value, toValue) + assert.equal(tx.ins.length, 1) + assert.deepEqual(tx.ins[0].hash, fakeTxHash(3)) + assert.equal(tx.ins[0].index, 0) }) }) - describe('changeAddress', function(){ - it('should allow custom changeAddress', function(){ - var wallet = new Wallet(seed, networks.testnet) - var address = wallet.generateAddress() - - wallet.setUnspentOutputs([{ - hash: fakeTxId(0), - outputIndex: 0, - address: address, - value: value - }]) - assert.equal(wallet.getBalance(), value) - + describe('changeAddress', function() { + it('should allow custom changeAddress', function() { var changeAddress = 'mfrFjnKZUvTcvdAK2fUX5D8v1Epu5H8JCk' - var to = 'mt7MyTVVEWnbwpF5hBn6fgnJcv95Syk2ue' - var toValue = value / 2 + var fromValue = 510000 + var toValue = fromValue / 2 var fee = 1e3 - var tx = wallet.createTx(to, toValue, fee, changeAddress) + var tx = wallet.createTx(to, toValue, { + fixedFee: fee, + changeAddress: changeAddress + }) assert.equal(tx.outs.length, 2) var outAddress0 = Address.fromOutputScript(tx.outs[0].script, networks.testnet) @@ -553,77 +587,85 @@ describe('Wallet', function() { assert.equal(tx.outs[0].value, toValue) assert.equal(outAddress1.toString(), changeAddress) - assert.equal(tx.outs[1].value, value - (toValue + fee)) + assert.equal(tx.outs[1].value, fromValue - (toValue + fee)) }) }) - describe('transaction outputs', function(){ - it('includes the specified address and amount', function(){ + describe('transaction outputs', function() { + it('includes the specified address and amount', function() { var tx = wallet.createTx(to, value) assert.equal(tx.outs.length, 1) var out = tx.outs[0] - var outAddress = Address.fromOutputScript(out.script) + var outAddress = Address.fromOutputScript(out.script, networks.testnet) assert.equal(outAddress.toString(), to) assert.equal(out.value, value) }) - describe('change', function(){ - it('uses the last change address if there is any', function(){ + describe('change', function() { + it('uses the last change address if there is any', function() { var fee = 0 wallet.generateChangeAddress() wallet.generateChangeAddress() - var tx = wallet.createTx(to, value, fee) + var tx = wallet.createTx(to, value, { fixedFee: fee }) assert.equal(tx.outs.length, 2) var out = tx.outs[1] - var outAddress = Address.fromOutputScript(out.script) + var outAddress = Address.fromOutputScript(out.script, networks.testnet) assert.equal(outAddress.toString(), wallet.changeAddresses[1]) assert.equal(out.value, 10000) }) - it('generates a change address if there is not any', function(){ + it('generates a change address if there is not any', function() { var fee = 0 assert.equal(wallet.changeAddresses.length, 0) - var tx = wallet.createTx(to, value, fee) + var tx = wallet.createTx(to, value, { fixedFee: fee }) assert.equal(wallet.changeAddresses.length, 1) var out = tx.outs[1] - var outAddress = Address.fromOutputScript(out.script) + var outAddress = Address.fromOutputScript(out.script, networks.testnet) assert.equal(outAddress.toString(), wallet.changeAddresses[0]) assert.equal(out.value, 10000) }) - it('skips change if it is not above dust threshold', function(){ - var fee = 14570 - var tx = wallet.createTx(to, value) - assert.equal(tx.outs.length, 1) + it('skips change if it is not above dust threshold', function() { + var tx1 = wallet.createTx(to, value - 546) + assert.equal(tx1.outs.length, 1) + + var tx2 = wallet.createTx(to, value - 547) + assert.equal(tx2.outs.length, 2) }) }) }) - describe('signing', function(){ - afterEach(function(){ - Transaction.prototype.sign.restore() + describe('signing', function() { + afterEach(function() { + TransactionBuilder.prototype.sign.restore() }) - it('signes the inputs with respective keys', function(){ + it('signs the inputs with respective keys', function() { var fee = 30000 - sinon.stub(Transaction.prototype, "sign") + sinon.spy(TransactionBuilder.prototype, "sign") + + wallet.createTx(to, value, { fixedFee: fee }) + + var priv1 = wallet.getPrivateKeyForAddress(address1) + var priv2 = wallet.getPrivateKeyForAddress(address2) - var tx = wallet.createTx(to, value, fee) + // FIXME: boo (required) side effects + priv1.pub.Q.affineX, priv2.pub.Q.affineX - assert(Transaction.prototype.sign.calledWith(0, wallet.getPrivateKeyForAddress(address2))) - assert(Transaction.prototype.sign.calledWith(1, wallet.getPrivateKeyForAddress(address1))) + assert(TransactionBuilder.prototype.sign.calledWith(0, priv2)) + assert(TransactionBuilder.prototype.sign.calledWith(1, priv1)) }) }) - describe('when value is below dust threshold', function(){ - it('throws an error', function(){ + describe('when value is below dust threshold', function() { + it('throws an error', function() { var value = 546 assert.throws(function() { @@ -632,8 +674,8 @@ describe('Wallet', function() { }) }) - describe('when there is not enough money', function(){ - it('throws an error', function(){ + describe('when there is not enough money', function() { + it('throws an error', function() { var value = 1400001 assert.throws(function() {