From 1915e6081f87de7c6b26f57dc4d51f717f33f7af Mon Sep 17 00:00:00 2001 From: Manuel Araoz Date: Wed, 24 Dec 2014 11:53:18 -0300 Subject: [PATCH 01/60] Bump package version to 0.8.5 --- bower.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bower.json b/bower.json index 42f726d..3f19385 100644 --- a/bower.json +++ b/bower.json @@ -1,7 +1,7 @@ { "name": "bitcore", "main": "browser/bitcore.min.js", - "version": "0.8.4", + "version": "0.8.5", "homepage": "http://bitcore.io", "authors": [ "BitPay, Inc." diff --git a/package.json b/package.json index f83dddc..f36534d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "bitcore", - "version": "0.8.4", + "version": "0.8.5", "description": "A pure and powerful JavaScript Bitcoin library.", "author": "BitPay ", "main": "index.js", From ea3ccb19c14437264b2a76ac2d0cd05499a50155 Mon Sep 17 00:00:00 2001 From: Esteban Ordano Date: Wed, 24 Dec 2014 13:19:25 -0300 Subject: [PATCH 02/60] readme: Update images, gh-pages branch was deleted --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index d923bfc..3cbd2b9 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,9 @@ simpleTx.sign(privateKey); The complete docs are hosted here: [bitcore documentation](http://bitcore.io/guide/). There's also a [bitcore API reference](http://bitcore.io/api/) available generated from the JSDocs of the project, where you'll find low-level details on each bitcore utility. -[![Read the Developer Guide](http://bitpay.github.io/bitcore/images/read-the-developer-guide-btn.png)](http://bitcore.io/guide/) [![Read the API Reference](http://bitpay.github.io/bitcore/images/read-the-api-reference-btn.png)](http://bitcore.io/api/) +[Read the Developer Guide](http://bitcore.io/guide/) + +[Read the API Reference](http://bitcore.io/api/) To get community assistance and ask for help with implementation questions, please use our [community forums](http://bitpaylabs.com/c/bitcore). From 3a7905a63539595f2f00143405f0fba0414ab821 Mon Sep 17 00:00:00 2001 From: Esteban Ordano Date: Wed, 24 Dec 2014 13:50:14 -0300 Subject: [PATCH 03/60] fix(bower.json): Add better ignores --- bower.json | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/bower.json b/bower.json index 3f19385..7fd28d7 100644 --- a/bower.json +++ b/bower.json @@ -19,8 +19,12 @@ "license": "MIT", "ignore": [ "**/.*", - "node_modules", - "bower_components", - "tests" + "CONTRIBUTING.md", + "gulpfile.js", + "lib", + "index.js", + "karma.conf.js", + "npm-shrinkwrap.json", + "test" ] } From cdfcc265e49a3f13418e994936f0699b1fe3fa9d Mon Sep 17 00:00:00 2001 From: Esteban Ordano Date: Wed, 24 Dec 2014 12:56:35 -0300 Subject: [PATCH 04/60] contributing: Add T4, test data in JSON files --- CONTRIBUTING.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d547cb3..dc62777 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -193,6 +193,10 @@ DON'T: var PublicKey = require('../lib/publickey'); ``` +### T4 - Testing: Data for tests included in a JSON file + +If possible, data for tests should be included in a different JSON file from the test. This improves interoperability with other libraries and keeps tests cleaner. + ## Pull Request Workflow Our workflow is based on GitHub's pull requests. We use feature branches, prepended with: `test`, `feature`, `fix`, `refactor`, or `remove` according to the change the branch introduces. Some examples for such branches are: From affd0248386dba2ff9cf916867587c459ef2dd93 Mon Sep 17 00:00:00 2001 From: Braydon Fuller Date: Wed, 24 Dec 2014 12:55:02 -0500 Subject: [PATCH 05/60] Fixed spelling mistakes, typos, and other documentation adjustments. --- docs/guide/address.md | 2 +- docs/guide/block.md | 6 +++--- docs/guide/crypto.md | 2 +- docs/guide/ecies.md | 2 +- docs/guide/hierarchical.md | 4 ++-- docs/guide/networks.md | 2 +- docs/guide/paymentprotocol.md | 2 +- docs/guide/peer.md | 2 +- docs/guide/pool.md | 4 +--- docs/guide/script.md | 2 +- docs/guide/transaction.md | 4 ++-- docs/guide/unit.md | 4 ++-- docs/guide/uri.md | 2 +- 13 files changed, 18 insertions(+), 20 deletions(-) diff --git a/docs/guide/address.md b/docs/guide/address.md index 8663b4c..9718790 100644 --- a/docs/guide/address.md +++ b/docs/guide/address.md @@ -5,7 +5,7 @@ description: A simple interface to generate and validate a bitcoin address. ## Description -Represents a bitcoin Address. Addresses are the most popular way to make bitcoin transactions. See [the official Bitcoin Wiki](https://en.bitcoin.it/wiki/Address) for technical background information. +Represents a bitcoin address. Addresses are the most popular way to make bitcoin transactions. See [the official Bitcoin Wiki](https://en.bitcoin.it/wiki/Address) for technical background information. ## Instantiate an Address diff --git a/docs/guide/block.md b/docs/guide/block.md index 6d88692..764d8f6 100644 --- a/docs/guide/block.md +++ b/docs/guide/block.md @@ -5,14 +5,14 @@ description: A simple interface to parse and validate a bitcoin blocks. ## Description -A Block instance represents the information of a block in the bitcoin network. Given a hexa or base64 string representation of the serialization of a block with its transactions, you can instantiate a Block instance. Methods are provided to calculate and check the merkle root hash (if enough data is provided), but transactions won't necessarily be valid spends, and this class won't validate them. A binary representation as a `Buffer` instance is also valid input for a Block's constructor. +A Block instance represents the information of a block in the bitcoin network. Given a hexadecimal string representation of the serialization of a block with its transactions, you can instantiate a Block instance. Methods are provided to calculate and check the merkle root hash (if enough data is provided), but transactions won't necessarily be valid spends, and this class won't validate them. A binary representation as a `Buffer` instance is also valid input for a Block's constructor. ```javascript // instantiate a new block instance var block = new Block(hexaEncodedBlock); -// will verify that the correspending block transactions match the header +// will verify that the corresponding block transactions match the header assert(block.validMerkleRoot()); // blocks have several properties @@ -27,7 +27,7 @@ For detailed technical information about a block please visit [Blocks](https://e ## Block Header -Each instance of Block has a BlockHeader *(which can be instantiated seperately)*. The header has validation methods, to verify that the block. +Each instance of Block has a BlockHeader *(which can be instantiated separately)*. The header has validation methods, to verify that the block. ```javascript diff --git a/docs/guide/crypto.md b/docs/guide/crypto.md index e5499c8..14925e7 100644 --- a/docs/guide/crypto.md +++ b/docs/guide/crypto.md @@ -25,4 +25,4 @@ The `bitcore.Crypto.Hash` namespace contains a set of hashes and utilities. Thes ## ECDSA -`bitcore.Crypto.ECDSA` contains a pure JavaScript implementation of the elliptic curve DSA signature scheme. +`bitcore.Crypto.ECDSA` contains a pure JavaScript implementation of the elliptic curve DSA signature scheme based on [elliptic.js](https://github.com/indutny/elliptic). diff --git a/docs/guide/ecies.md b/docs/guide/ecies.md index c63f931..c95879b 100644 --- a/docs/guide/ecies.md +++ b/docs/guide/ecies.md @@ -9,7 +9,7 @@ Bitcore implements [Elliptic Curve Integrated Encryption Scheme (ECIES)](http:// For more information refer to the [bitcore-ecies](https://github.com/bitpay/bitcore-ecies) github repo. -## Instalation +## Installation ECIES is implemented as a separate module and you must add it to your dependencies: diff --git a/docs/guide/hierarchical.md b/docs/guide/hierarchical.md index a5edca9..fa62370 100644 --- a/docs/guide/hierarchical.md +++ b/docs/guide/hierarchical.md @@ -3,9 +3,9 @@ description: Lets you create and derive extended public and private keys accordi --- # HDKeys -## Hierarichically Derived Keys +## Hierarchically Derived Keys -Bitcore provides full support for [BIP32](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki), allowing for many key management schemas that benefit from this property. Please be sure to read and understand the basic concepts and the warnings on that BIP before using these classes. +Bitcore provides full support for [BIP32](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki), allowing for many key management schemas that benefit from this property. Please be sure to read and understand the basic concepts and the warnings on that BIP before using these classes. ## HDPrivateKey diff --git a/docs/guide/networks.md b/docs/guide/networks.md index b35e4c9..905d805 100644 --- a/docs/guide/networks.md +++ b/docs/guide/networks.md @@ -5,7 +5,7 @@ description: A simple interface to handle livenet and testnet bitcoin networks. ## Description -Bitcore provides support for the main bitcoin network as well as for `testnet3`, the current test blockchain. We encourage the use of `Networks.livenet` and `Networks.testnet` as constants. Note that the library sometimes may check for equality against this object. Avoid creating a deep copy of this object and using that. +Bitcore provides support for the main bitcoin network as well as for `testnet3`, the current test blockchain. We encourage the use of `Networks.livenet` and `Networks.testnet` as constants. Note that the library sometimes may check for equality against this object. Please avoid creating a deep copy of this object. The `Network` namespace has a function, `get(...)` that returns an instance of a `Network` or `undefined`. The only argument to this function is some kind of identifier of the network: either its name, a reference to a Network object, or a number used as a magic constant to identify the network (for example, the value `0` that gives bitcoin addresses the distinctive `'1'` at its beginning on livenet, is a `0x6F` for testnet). diff --git a/docs/guide/paymentprotocol.md b/docs/guide/paymentprotocol.md index c9c6545..1412a1a 100644 --- a/docs/guide/paymentprotocol.md +++ b/docs/guide/paymentprotocol.md @@ -100,7 +100,7 @@ var merchant_data = details.get('merchant_data'); ## Send a Payment -After the request is verified a payment can be sent to the merchant, from the customer's wallet: +After the request is verified a payment can be sent to the merchant from the customer's wallet: ```javascript diff --git a/docs/guide/peer.md b/docs/guide/peer.md index 53ca127..ccd73a2 100644 --- a/docs/guide/peer.md +++ b/docs/guide/peer.md @@ -1,5 +1,5 @@ title: Peer -description: The Peer class privides a simple interface for connecting to a node in the bitcoin network. +description: The Peer class provides a simple interface for connecting to a node in the bitcoin network. --- # Peer diff --git a/docs/guide/pool.md b/docs/guide/pool.md index 34f1f9f..cbcb4df 100644 --- a/docs/guide/pool.md +++ b/docs/guide/pool.md @@ -3,8 +3,6 @@ description: A simple interface to create and maintain a set of connections to b --- # Pool -## Pool - A pool maintains a connection of [Peers](peer.md). A pool will discover peers via DNS seeds, as well as when peer addresses are announced through the network. The quickest way to get connected is to run the following: @@ -30,4 +28,4 @@ pool.disconnect() ``` -For more information about Peer events, please read the [Peer](peer.md) documentation. Peer events are relayed to the pool, a peer event `inv` in the pool would be `peerinv`. When a peer is disconnected the pool will try to connect to the list of known addresses to maintain connection. +For more information about Peer events please read the [Peer](peer.md) documentation. Peer events are relayed to the pool, a peer event `inv` in the pool would be `peerinv`. When a peer is disconnected the pool will try to connect to the list of known addresses to maintain connection. diff --git a/docs/guide/script.md b/docs/guide/script.md index b88fb3d..5c6facd 100644 --- a/docs/guide/script.md +++ b/docs/guide/script.md @@ -63,7 +63,7 @@ assert(script.toString() === 'OP_2 33 0x022df8750480ad5b26950b25c7ba79d3e37d75f6 Pay to script hash outputs are scripts that contain the hash of another script, called `redeemScript`. To spend bitcoins sent in a p2sh output, the spending transaction must provide a script matching the script hash and data which makes the script evaluate to true. This allows to defer revealing the spending conditions to the moment of spending. It also makes it possible for the receiver to set the conditions to spend those bitcoins. -Most multisig transactions today use p2sh outputs where the redeemScript is a multisig output. +Most multisig transactions today use p2sh outputs where the `redeemScript` is a multisig output. ```javascript // create a p2sh multisig output diff --git a/docs/guide/transaction.md b/docs/guide/transaction.md index 1fb97ef..2607ede 100644 --- a/docs/guide/transaction.md +++ b/docs/guide/transaction.md @@ -7,7 +7,7 @@ description: A robust interface to create, parse and validate bitcoin transactio Bitcore provides a very simple API for creating transactions. We expect this API to be accessible for developers without knowing the working internals of bitcoin in deep detail. What follows is a small introduction to transactions with some basic knowledge required to use this API. -A Transaction contains a set of inputs and a set of outputs. Each input contains a reference to another transaction's output, and a signature that allows the value referenced in that ouput to be used in this transaction. +A Transaction contains a set of inputs and a set of outputs. Each input contains a reference to another transaction's output, and a signature that allows the value referenced in that output to be used in this transaction. Note also that an output can be used only once. That's why there's a concept of "change address" in the bitcoin ecosystem: if an output of 10 BTC is available for me to spend, but I only need to transmit 1 BTC, I'll create a transaction with two outputs, one with 1 BTC that I want to spend, and the other with 9 BTC to a change address, so I can spend this 9 BTC with another private key that I own. @@ -17,7 +17,7 @@ Let's take a look at some very simple transactions: ```javascript var transaction = new Transaction() - .from(utxos) // Feed information about what unspend outputs one can use + .from(utxos) // Feed information about what unspent outputs one can use .to(address, amount) // Add an output with the given amount of satoshis .change(address) // Sets up a change address where the rest of the funds will go .sign(privkeySet) // Signs all the inputs it can diff --git a/docs/guide/unit.md b/docs/guide/unit.md index 825ad20..b09640f 100644 --- a/docs/guide/unit.md +++ b/docs/guide/unit.md @@ -5,7 +5,7 @@ description: Utility to easily convert between bitcoin units. ## Description -Unit is an utility for handling and converting bitcoin units. We strongly recommend to always use satoshis to represent amount inside your application and only convert them to other units in the front-end. +Unit is a utility for handling and converting bitcoin units. We strongly recommend to always use satoshis to represent amount inside your application and only convert them to other units in the front-end. ## Supported units @@ -40,7 +40,7 @@ unit = Unit.fromSatoshis(amount); ## Conversion -Once you have a unit instance, you can check its representantion in all the available units. For your convinience the classes expose three ways to acomplish this. Using the `.to(unitCode)` method, using a fixed unit like `.toSatoshis()` or by using the accessors. +Once you have a unit instance, you can check its representation in all the available units. For your convenience the classes expose three ways to accomplish this. Using the `.to(unitCode)` method, using a fixed unit like `.toSatoshis()` or by using the accessors. ```javascript var unit; diff --git a/docs/guide/uri.md b/docs/guide/uri.md index a1b51a3..e679e12 100644 --- a/docs/guide/uri.md +++ b/docs/guide/uri.md @@ -18,7 +18,7 @@ bitcoin:12A1MyfXbW6RhdRAZEqofac5jCQQjwEPBu?amount=1.2&message=Payment&label=Sato The main use that we expect you'll have for the `URI` class in bitcore is validating and parsing bitcoin URIs. A `URI` instance exposes the address as a bitcore `Address` object and the amount in Satoshis, if present. -The code for validating uris looks like this: +The code for validating URIs looks like this: ```javascript var uriString = 'bitcoin:12A1MyfXbW6RhdRAZEqofac5jCQQjwEPBu?amount=1.2'; var valid = URI.isValid(uriString); From 47cb6f377dbbbab9f5d422a44c51734c253b2064 Mon Sep 17 00:00:00 2001 From: Braydon Fuller Date: Wed, 24 Dec 2014 19:08:43 -0500 Subject: [PATCH 06/60] Fixed capitalization in headers --- CONTRIBUTING.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index dc62777..58d5846 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -51,19 +51,19 @@ Follow this Node.js Style Guide: https://github.com/felixge/node-style-guide#nod Avoid constants in the code as much as possible. Magic strings are also magic numbers. -### G2 - General: Internal Objects should be Instances +### G2 - General: Internal Objects Should be Instances If a class has a `publicKey` member, for instance, that should be a `PublicKey` instance. -### G3 - General: Internal amounts must be integers representing Satoshis +### G3 - General: Internal Amounts Must be Integers Representing Satoshis Avoid representation errors by always dealing with satoshis. For conversion for frontends, use the `Unit` class. -### G4 - General: Internal network references must be Network instances +### G4 - General: Internal Network References Must be Network Instances A special case for [G2](#g2---general-internal-objects-should-be-instances) all network references must be `Network` instances (see `lib/network.js`), but when returned to the user, its `.name` property should be used. -### G5 - General: Objects should display nicely in the console +### G5 - General: Objects Should Display Nicely in the Console Write a `.inspect()` method so an instance can be easily debugged in the console. @@ -103,7 +103,7 @@ Usage: * Whenever a new class is created, add a generic error for that class in `lib/errors/spec.js`. * Specific errors for that class should subclass that error. Take a look at the structure in `lib/errors/spec.js`, it should be clear how subclasses are generated from that file. -### E2 - Errors: Provide a `getValidationError` static method for classes +### E2 - Errors: Provide a `getValidationError` Static Method for Classes ### I1 - Interface: Make Code that Fails Early @@ -156,7 +156,7 @@ function ImmutableClass(arg) { } ``` -### I5 - Interface: No new keyword for Constructors +### I5 - Interface: No New Keyword for Constructors Constructors should not require to be called with `new`. This rule is not heavily enforced, but is a "nice to have". @@ -179,7 +179,7 @@ Don't write long tests, write helper functions to make them be as short and conc Inputs for tests should not be generated randomly. Also, the type and structure of outputs should be checked. -### T3 - Testing: Require 'bitcore' and look up classes from there +### T3 - Testing: Require 'bitcore' and Look up Classes from There This helps to make tests more useful as examples, and more independent of where they are placed. This also helps prevent forgetting to include all submodules in the bitcore object. @@ -193,9 +193,9 @@ DON'T: var PublicKey = require('../lib/publickey'); ``` -### T4 - Testing: Data for tests included in a JSON file +### T4 - Testing: Data for Tests Included in a JSON File -If possible, data for tests should be included in a different JSON file from the test. This improves interoperability with other libraries and keeps tests cleaner. +If possible, data for tests should be included in a JSON file in the `test/data` directory. This improves interoperability with other libraries and keeps tests cleaner. ## Pull Request Workflow From e7839d9ba2ec215ae835abef87b69bdaf325ddc1 Mon Sep 17 00:00:00 2001 From: Yemel Jardi Date: Thu, 25 Dec 2014 19:04:03 -0300 Subject: [PATCH 07/60] Add from/to Fiat methods to Unit class --- lib/errors/spec.js | 3 +++ lib/unit.js | 54 ++++++++++++++++++++++++++++++++++++++++------ test/unit.js | 34 +++++++++++++++++++++++++++++ 3 files changed, 84 insertions(+), 7 deletions(-) diff --git a/lib/errors/spec.js b/lib/errors/spec.js index 5b74937..6f45f95 100644 --- a/lib/errors/spec.js +++ b/lib/errors/spec.js @@ -40,6 +40,9 @@ module.exports = [{ errors: [{ 'name': 'UnknownCode', 'message': format('Unrecognized unit code: {0}') + },{ + 'name': 'InvalidRate', + 'message': format('Invalid exchange rate: {0}') }] }, { name: 'Transaction', diff --git a/lib/unit.js b/lib/unit.js index 6a2bb86..7accd82 100644 --- a/lib/unit.js +++ b/lib/unit.js @@ -1,5 +1,7 @@ 'use strict'; +var _ = require('lodash'); + var errors = require('./errors'); var JSUtil = require('./util/js'); @@ -15,19 +17,23 @@ var UNITS = { * Utility for handling and converting bitcoins units. The supported units are * BTC, mBTC, bits (also named uBTC) and satoshis. A unit instance can be created with an * amount and a unit code, or alternatively using static methods like {fromBTC}. + * It also allows to be created from a fiat amount and the exchange rate, or + * alternatively using the {fromFiat} static method. * You can consult for different representation of a unit instance using it's * {to} method, the fixed unit methods like {toSatoshis} or alternatively using - * the unit accessors. + * the unit accessors. It also can be converted to a fiat amount by providing the + * corresponding BTC/fiat exchange rate. * * @example * ```javascript * var sats = Unit.fromBTC(1.3).toSatoshis(); * var mili = Unit.fromBits(1.3).to(Unit.mBTC); + * var bits = Unit.fromFiat(1.3, 350).bits; * var btc = new Unit(1.3, Unit.bits).BTC; * ``` * * @param {Number} amount - The amount to be represented - * @param {String} code - The unit of the amount + * @param {String|Number} code - The unit of the amount or the exchange rate * @returns {Unit} A new instance of an Unit * @constructor */ @@ -36,8 +42,14 @@ function Unit(amount, code) { return new Unit(amount, code); } - this._amount = amount; - this._code = code; + // convert fiat to BTC + if (_.isNumber(code)) { + if (code <= 0) { + throw new errors.Unit.InvalidRate(code); + } + amount = amount / code; + code = Unit.BTC; + } this._value = this._from(amount, code); @@ -109,6 +121,17 @@ Unit.fromSatoshis = function(amount) { return new Unit(amount, Unit.satoshis); }; +/** + * Returns a Unit instance created from a fiat amount and exchange rate. + * + * @param {Number} amount - The amount in fiat + * @param {Number} rate - The exchange rate BTC/fiat + * @returns {Unit} A Unit instance + */ +Unit.fromFiat = function(amount, rate) { + return new Unit(amount, rate); +}; + Unit.prototype._from = function(amount, code) { if (!UNITS[code]) { throw new errors.Unit.UnknownCode(code); @@ -119,10 +142,17 @@ Unit.prototype._from = function(amount, code) { /** * Returns the value represented in the specified unit * - * @param {string} code - The unit code + * @param {String|Number} code - The unit code or exchange rate * @returns {Number} The converted value */ Unit.prototype.to = function(code) { + if (_.isNumber(code)) { + if (code <= 0) { + throw new errors.Unit.InvalidRate(code); + } + return parseFloat((this.BTC * code).toFixed(2)); + } + if (!UNITS[code]) { throw new errors.Unit.UnknownCode(code); } @@ -167,6 +197,16 @@ Unit.prototype.toSatoshis = function() { return this.to(Unit.satoshis); }; +/** + * Returns the value represented in fiat + * + * @param {string} rate - The exchange rate between BTC/currency + * @returns {Number} The value converted to satoshis + */ +Unit.prototype.toFiat = function(rate) { + return this.to(rate); +}; + /** * Returns a the string representation of the value in satoshis * @@ -183,8 +223,8 @@ Unit.prototype.toString = function() { */ Unit.prototype.toObject = function toObject() { return { - amount: this._amount, - code: this._code + amount: this.BTC, + code: Unit.BTC }; }; diff --git a/test/unit.js b/test/unit.js index ebd4204..f5b4e31 100644 --- a/test/unit.js +++ b/test/unit.js @@ -15,10 +15,20 @@ describe('Unit', function() { }).to.not.throw(); }); + it('can be created from a number and exchange rate', function() { + expect(function() { + return new Unit(1.2, 350); + }).to.not.throw(); + }); + it('no "new" is required for creating an instance', function() { expect(function() { return Unit(1.2, 'BTC'); }).to.not.throw(); + + expect(function() { + return Unit(1.2, 350); + }).to.not.throw(); }); it('has property accesors "BTC", "mBTC", "uBTC", "bits", and "satoshis"', function() { @@ -44,6 +54,9 @@ describe('Unit', function() { unit = Unit.fromSatoshis('8999'); unit.satoshis.should.equal(8999); + + unit = Unit.fromFiat('43', 350); + unit.BTC.should.equal(0.12285714); }); it('should have constructor helpers', function() { @@ -60,6 +73,9 @@ describe('Unit', function() { unit = Unit.fromSatoshis(8999); unit.satoshis.should.equal(8999); + + unit = Unit.fromFiat(43, 350); + unit.BTC.should.equal(0.12285714); }); it('converts to satoshis correctly', function() { @@ -124,6 +140,15 @@ describe('Unit', function() { unit.toSatoshis().should.equal(unit.satoshis); }); + it('can convert to fiat', function() { + var unit = new Unit(1.3, 350); + unit.toFiat(350).should.equal(1.3); + unit.to(350).should.equal(1.3); + + unit = Unit.fromBTC(0.0123); + unit.toFiat(10).should.equal(0.12); + }); + it('toString works as expected', function() { var unit = new Unit(1.3, 'BTC'); should.exist(unit.toString); @@ -156,4 +181,13 @@ describe('Unit', function() { }).to.throw(errors.Unit.UnknownCode); }); + it('fails when the exchange rate is invalid', function() { + expect(function() { + return new Unit(100, -123); + }).to.throw(errors.Unit.InvalidRate); + expect(function() { + return new Unit(100, 'BTC').toFiat(-123); + }).to.throw(errors.Unit.InvalidRate); + }); + }); From 168d87185ce5a3d7a9e418ebe0f5cda5e29726ad Mon Sep 17 00:00:00 2001 From: Yemel Jardi Date: Thu, 25 Dec 2014 19:14:06 -0300 Subject: [PATCH 08/60] Update bitcore guide --- docs/guide/unit.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/docs/guide/unit.md b/docs/guide/unit.md index b09640f..10de6a5 100644 --- a/docs/guide/unit.md +++ b/docs/guide/unit.md @@ -61,3 +61,19 @@ value = Unit.fromBTC(amount).mBTC; value = Unit.fromBTC(amount).bits; value = Unit.fromBTC(amount).satoshis; ``` + +## Using a fiat currency + +The unit class also provides a convenient alternative to create an instance from a fiat amount and the corresponding BTC/fiat exchange rate. Any unit instance can be converted to a fiat amount by providing the current exchange rate. Check the example below: + +```javascript +var unit, fiat; +var amount = 100; +var exchangeRate = 350; + +unit = new Unit(amount, exchangeRate); +unit = Unit.fromFiat(amount, exchangeRate); + +fiat = Unit.fromBits(amount).toFiat(exchangeRate); +fiat = Unit.fromBits(amount).to(exchangeRate); +``` From d922a625b90918e6b7a0548730072f6c17f0eea4 Mon Sep 17 00:00:00 2001 From: Yemel Jardi Date: Sun, 28 Dec 2014 17:40:11 -0300 Subject: [PATCH 09/60] Rename toFiat() to atRate() --- docs/guide/unit.md | 2 +- lib/unit.js | 2 +- test/unit.js | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/guide/unit.md b/docs/guide/unit.md index 10de6a5..d13c2ef 100644 --- a/docs/guide/unit.md +++ b/docs/guide/unit.md @@ -74,6 +74,6 @@ var exchangeRate = 350; unit = new Unit(amount, exchangeRate); unit = Unit.fromFiat(amount, exchangeRate); -fiat = Unit.fromBits(amount).toFiat(exchangeRate); +fiat = Unit.fromBits(amount).atRate(exchangeRate); fiat = Unit.fromBits(amount).to(exchangeRate); ``` diff --git a/lib/unit.js b/lib/unit.js index 7accd82..22bdc9d 100644 --- a/lib/unit.js +++ b/lib/unit.js @@ -203,7 +203,7 @@ Unit.prototype.toSatoshis = function() { * @param {string} rate - The exchange rate between BTC/currency * @returns {Number} The value converted to satoshis */ -Unit.prototype.toFiat = function(rate) { +Unit.prototype.atRate = function(rate) { return this.to(rate); }; diff --git a/test/unit.js b/test/unit.js index f5b4e31..97e441e 100644 --- a/test/unit.js +++ b/test/unit.js @@ -142,11 +142,11 @@ describe('Unit', function() { it('can convert to fiat', function() { var unit = new Unit(1.3, 350); - unit.toFiat(350).should.equal(1.3); + unit.atRate(350).should.equal(1.3); unit.to(350).should.equal(1.3); unit = Unit.fromBTC(0.0123); - unit.toFiat(10).should.equal(0.12); + unit.atRate(10).should.equal(0.12); }); it('toString works as expected', function() { @@ -186,7 +186,7 @@ describe('Unit', function() { return new Unit(100, -123); }).to.throw(errors.Unit.InvalidRate); expect(function() { - return new Unit(100, 'BTC').toFiat(-123); + return new Unit(100, 'BTC').atRate(-123); }).to.throw(errors.Unit.InvalidRate); }); From 50894bba6bfeefc9532b90a9d2f5ab44228269d3 Mon Sep 17 00:00:00 2001 From: Braydon Fuller Date: Mon, 29 Dec 2014 14:06:29 -0500 Subject: [PATCH 10/60] Added documentation to the contributing file, and adjusted formatting of section headings. --- CONTRIBUTING.md | 56 ++++++++++++++++++++++++++++++++----------------- 1 file changed, 37 insertions(+), 19 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 58d5846..72e5899 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -43,31 +43,33 @@ Consistency on the way classes are used is paramount to allow an easier understa The design guidelines have quite a high abstraction level. These style guidelines are more concrete and easier to apply, and also more opinionated. The design guidelines mentioned above are the way we think about general software development and we believe they should be present in any software project. -### G0 - General: Default to Felixge's Style Guide +### General + +#### G0 - Default to Felixge's Style Guide Follow this Node.js Style Guide: https://github.com/felixge/node-style-guide#nodejs-style-guide -### G1 - General: No Magic Numbers +#### G1 - No Magic Numbers Avoid constants in the code as much as possible. Magic strings are also magic numbers. -### G2 - General: Internal Objects Should be Instances +#### G2 - Internal Objects Should be Instances If a class has a `publicKey` member, for instance, that should be a `PublicKey` instance. -### G3 - General: Internal Amounts Must be Integers Representing Satoshis +#### G3 - Internal Amounts Must be Integers Representing Satoshis Avoid representation errors by always dealing with satoshis. For conversion for frontends, use the `Unit` class. -### G4 - General: Internal Network References Must be Network Instances +#### G4 - Internal Network References Must be Network Instances A special case for [G2](#g2---general-internal-objects-should-be-instances) all network references must be `Network` instances (see `lib/network.js`), but when returned to the user, its `.name` property should be used. -### G5 - General: Objects Should Display Nicely in the Console +#### G5 - Objects Should Display Nicely in the Console Write a `.inspect()` method so an instance can be easily debugged in the console. -### G6 - General: Naming Utility Namespaces +#### G6 - Naming Utility Namespaces Name them in CamelCase, as they are namespaces. @@ -80,7 +82,7 @@ DON'T: var bufferUtil = require('./util/buffer'); ``` -### G7 - General: Standard Methods +#### G7 - Standard Methods When possible, bitcore objects should have standard methods on an instance prototype: * `toObject` - A plain JavaScript object that can be JSON stringified @@ -93,7 +95,9 @@ These should have a matching static method that can be used for instantiation: * `fromString` - Should be able to instantiate with output from `toString` * `fromBuffer` - Should likewise be able to instantiate from output from `toBuffer` -### E1 - Errors: Use bitcore.Errors +### Errors + +#### E1 - Use bitcore.Errors We've designed a structure for Errors to follow and are slowly migrating to it. @@ -103,9 +107,11 @@ Usage: * Whenever a new class is created, add a generic error for that class in `lib/errors/spec.js`. * Specific errors for that class should subclass that error. Take a look at the structure in `lib/errors/spec.js`, it should be clear how subclasses are generated from that file. -### E2 - Errors: Provide a `getValidationError` Static Method for Classes +#### E2 - Provide a `getValidationError` Static Method for Classes -### I1 - Interface: Make Code that Fails Early +### Interface + +#### I1 - Code that Fails Early In order to deal with JavaScript's weak typing and confusing errors, we ask our code to fail as soon as possible when an unexpected input was provided. @@ -118,11 +124,11 @@ $.checkArgumentType(something, PrivateKey, 'something'); // The third argument i $.checkArgumentType(something, PrivateKey); // but it's optional (will show up as "(unknown argument)") ``` -### I2 - Interface: Permissive Constructors +#### I2 - Permissive Constructors Most classes have static methods named `fromBuffer`, `fromString`, `fromJSON`. Whenever one of those methods is provided, the constructor for that class should also be able to detect the type of the arguments and call the appropriate method. -### I3 - Interface: Method Chaining +#### I3 - Method Chaining For classes that have a mutable state, most of the methods that can be chained *SHOULD* be chained, allowing for interfaces that read well, like: @@ -134,7 +140,7 @@ var transaction = new Transaction() .sign(privkey); ``` -### I4 - Interface: Copy Constructors +#### I4 - Copy Constructors Constructors, when provided an instance of the same class, should: * Return the same object, if the instances of this class are immutable @@ -156,7 +162,7 @@ function ImmutableClass(arg) { } ``` -### I5 - Interface: No New Keyword for Constructors +#### I5 - No New Keyword for Constructors Constructors should not require to be called with `new`. This rule is not heavily enforced, but is a "nice to have". @@ -169,17 +175,19 @@ function NoNewRequired(args) { } ``` -### T1 - Testing: Tests Must be Written Elegantly +### Testing + +#### T1 - Tests Must be Written Elegantly Style guidelines are not relaxed for tests. Tests are a good way to show how to use the library, and maintaining them is extremely necessary. Don't write long tests, write helper functions to make them be as short and concise as possible (they should take just a few lines each), and use good variable names. -### T2 - Testing: Tests Must not be Random +#### T2 - Tests Must not be Random Inputs for tests should not be generated randomly. Also, the type and structure of outputs should be checked. -### T3 - Testing: Require 'bitcore' and Look up Classes from There +#### T3 - Require 'bitcore' and Look up Classes from There This helps to make tests more useful as examples, and more independent of where they are placed. This also helps prevent forgetting to include all submodules in the bitcore object. @@ -193,10 +201,20 @@ DON'T: var PublicKey = require('../lib/publickey'); ``` -### T4 - Testing: Data for Tests Included in a JSON File +#### T4 - Data for Tests Included in a JSON File If possible, data for tests should be included in a JSON file in the `test/data` directory. This improves interoperability with other libraries and keeps tests cleaner. +### Documentation + +#### D1 - Guide and API Reference + +All modules should include a developer guide and API reference. The API reference documentation is generated using JSDOC. Each function that exposes a public API should include a description, @return and @param, as appropriate. The general documentation guide for the module should be located in the `docs/guide` directory and is written in GitHub Flavored Markdown. + +#### D1 - Proofread + +Please proofread documentation to avoid unintentional spelling and grammatical mistakes before submitting a pull request. + ## Pull Request Workflow Our workflow is based on GitHub's pull requests. We use feature branches, prepended with: `test`, `feature`, `fix`, `refactor`, or `remove` according to the change the branch introduces. Some examples for such branches are: From c17f8897e7f00c51718d973196efa8bce24eac82 Mon Sep 17 00:00:00 2001 From: Braydon Fuller Date: Mon, 29 Dec 2014 11:20:22 -0500 Subject: [PATCH 11/60] Docs: Added documentation about building a browser bundle. --- docs/guide/browser.md | 64 +++++++++++++++++++++++++++++++++++++++++++ docs/guide/index.md | 3 ++ 2 files changed, 67 insertions(+) create mode 100644 docs/guide/browser.md diff --git a/docs/guide/browser.md b/docs/guide/browser.md new file mode 100644 index 0000000..56f04be --- /dev/null +++ b/docs/guide/browser.md @@ -0,0 +1,64 @@ +title: Browser Builds +description: Guide to writing modules and optimizing browser bundles. +--- + +# Browser Builds + +When developing a module that will need to work in a browser and does not use the entire Bitcore namespace, it's recommended to narrow the scope of the requires to the particular modules that are needed. It will produce a smaller browser bundle as it will only include the JavaScript that is nessessary. Below is a quick tutorial that will use three modules. + +## Tutorial + +**Step 1**: Require Bitcore Modules + +Here we require specific Bitcore modules that will be used in a `index.js` file: + +```javascript + +var PrivateKey = require('bitcore/lib/privatekey'); +var PublicKey = require('bitcore/lib/publickey'); +var Address = require('bitcore/lib/address'); + +// the rest of the module here + +``` + +**Step 2**: Browserifying + +Next we will generate a browser bundle using [browserify](https://www.npmjs.com/package/browserify) by running the command: + +```bash +browserify index.js -o index.browser.js +``` + +This will output a file `index.browser.js` at around 700KB *(the entire Bitcore namespace is around 2MB)*. + +**Step 3**: Uglifying + +This can be further optimized by using [uglifyjs](https://www.npmjs.com/package/uglify-js), and running the command: + +```bash +uglifyjs index.browser.js --compress --mangle -o index.browser.min.js +``` + +The resulting file `index.browser.min.js` in this case should be less than 300KB. + +## Modules + +Here is a list of some of the common modules: + +```javascript +var Address = require('bitcore/lib/address'); +var Block = require('bitcore/lib/block'); +var BlockHeader = require('bitcore/lib/blockheader'); +var HDPrivateKey = require('bitcore/lib/hdprivatekey'); +var HDPublicKey = require('bitcore/lib/hdpublickey'); +var PaymentProtocol = require('bitcore/lib/paymentprotocol'); +var PrivateKey = require('bitcore/lib/privatekey'); +var PublicKey = require('bitcore/lib/publickey'); +var Script = require('bitcore/lib/script'); +var Transaction = require('bitcore/lib/transaction'); +var URI = require('bitcore/lib/uri'); +var Unit = require('bitcore/lib/unit'); +``` + +For more informatation about each of the modules please see the [Bitcore Documentation](index.md). \ No newline at end of file diff --git a/docs/guide/index.md b/docs/guide/index.md index b75cfc8..838f567 100644 --- a/docs/guide/index.md +++ b/docs/guide/index.md @@ -35,6 +35,9 @@ To get started, just `npm install bitcore` or `bower install bitcore`. * [Encoding](encoding.md) * [ECIES](ecies.md) +## Module Development +* [Browser Builds](browser.md) + # Examples ## Create a Private Key From dc92bdd474eb789f478c4ef42602c7993dfa659d Mon Sep 17 00:00:00 2001 From: Esteban Ordano Date: Tue, 30 Dec 2014 14:29:49 -0300 Subject: [PATCH 12/60] Add getData functionality to script --- lib/script/script.js | 16 ++++++++++++++++ test/script/script.js | 24 ++++++++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/lib/script/script.js b/lib/script/script.js index 72ba781..8e557ef 100644 --- a/lib/script/script.js +++ b/lib/script/script.js @@ -332,6 +332,22 @@ Script.prototype.isDataOut = function() { this.chunks[1].length === this.chunks.len)); }; +/** + * Retrieve the associated data for this script. + * In the case of a pay to public key hash or P2SH, return the hash. + * In the case of a standard OP_RETURN, return the data + * @returns {Buffer} + */ +Script.prototype.getData = function() { + if (this.isDataOut() || this.isScriptHashOut()) { + return new Buffer(this.chunks[1].buf); + } + if (this.isPublicKeyHashOut()) { + return new Buffer(this.chunks[2].buf); + } + throw new Error('Unrecognized script type to get data from'); +}; + /** * @returns true if the script is only composed of data pushing * opcodes or small int opcodes (OP_0, OP_1, ..., OP_16) diff --git a/test/script/script.js b/test/script/script.js index 192ab3e..7fc66d0 100644 --- a/test/script/script.js +++ b/test/script/script.js @@ -1,7 +1,10 @@ 'use strict'; var should = require('chai').should(); +var expect = require('chai').expect; var bitcore = require('../..'); + +var BufferUtil = bitcore.util.buffer; var Script = bitcore.Script; var Opcode = bitcore.Opcode; var PublicKey = bitcore.PublicKey; @@ -566,4 +569,25 @@ describe('Script', function() { }); + describe('getData returns associated data', function() { + it('for a P2PKH address', function() { + var address = Address.fromString('1NaTVwXDDUJaXDQajoa9MqHhz4uTxtgK14'); + var script = Script.buildPublicKeyHashOut(address); + expect(BufferUtil.equal(script.getData(), address.hashBuffer)).to.be.true(); + }); + it('for a P2SH address', function() { + var address = Address.fromString('3GhtMmAbWrUf6Y8vDxn9ETB14R6V7Br3mt'); + var script = new Script(address); + expect(BufferUtil.equal(script.getData(), address.hashBuffer)).to.be.true(); + }); + it('for a standard opreturn output', function() { + expect(BufferUtil.equal(Script('OP_RETURN 1 0xFF').getData(), new Buffer([255]))).to.be.true(); + }); + it('fails if content is not recognized', function() { + expect(function() { + return Script('1 0xFF').getData(); + }).to.throw(); + }); + }); + }); From d688222769364202f4f827bd4d3f87f025cdb0ec Mon Sep 17 00:00:00 2001 From: Esteban Ordano Date: Tue, 30 Dec 2014 14:47:06 -0300 Subject: [PATCH 13/60] Add toAddress(network) functionality to scripts --- lib/address.js | 12 ++++++++++-- lib/script/script.js | 16 +++++++++++++++ test/address.js | 45 ++++++++++++++++++++++++++++--------------- test/script/script.js | 35 ++++++++++++++++++++++++++++++--- 4 files changed, 87 insertions(+), 21 deletions(-) diff --git a/lib/address.js b/lib/address.js index f9177ee..abc0aca 100644 --- a/lib/address.js +++ b/lib/address.js @@ -234,9 +234,17 @@ Address._transformScript = function(script, network){ if (!script.constructor || (script.constructor.name && script.constructor.name !== 'Script')) { throw new TypeError('Address must be an instance of Script.'); } + if (script.isScriptHashOut()) { + info.hashBuffer = script.getData(); + info.type = Address.PayToScriptHash; + } else if (script.isPublicKeyHashOut()) { + info.hashBuffer = script.getData(); + info.type = Address.PayToPublicKeyHash; + } else { + info.hashBuffer = Hash.sha256ripemd160(script.toBuffer()); + info.type = Address.PayToScriptHash; + } info.network = network || Networks.defaultNetwork; - info.hashBuffer = Hash.sha256ripemd160(script.toBuffer()); - info.type = Address.PayToScriptHash; return info; }; diff --git a/lib/script/script.js b/lib/script/script.js index 8e557ef..aadd2db 100644 --- a/lib/script/script.js +++ b/lib/script/script.js @@ -8,6 +8,7 @@ var Hash = require('../crypto/hash'); var Opcode = require('../opcode'); var PublicKey = require('../publickey'); var Signature = require('../crypto/signature'); +var Networks = require('../networks'); var $ = require('../util/preconditions'); var _ = require('lodash'); @@ -700,6 +701,21 @@ Script.fromAddress = function(address) { throw new errors.Script.UnrecognizedAddress(address); }; +/** + * @return Address the associated address for this script + */ +Script.prototype.toAddress = function(network) { + network = Networks.get(network); + $.checkArgument(network, 'Must provide a network'); + if (this.isPublicKeyHashOut()) { + return new Address(this.getData(), network, Address.PayToPublicKeyHash); + } + if (this.isScriptHashOut()) { + return new Address(this.getData(), network, Address.PayToScriptHash); + } + throw new Error('The script type needs to be PayToPublicKeyHash or PayToScriptHash'); +}; + /** * Analagous to bitcoind's FindAndDelete. Find and delete equivalent chunks, * typically used with push data chunks. Note that this will find and delete diff --git a/test/address.js b/test/address.js index 0e8a1fb..b75c7ae 100644 --- a/test/address.js +++ b/test/address.js @@ -320,23 +320,36 @@ describe('Address', function() { b.toString().should.equal('16JXnhxjJUhxfyx4y6H4sFcxrgt8kQ8ewX'); }); - it('should make this address from a script', function() { - var s = Script.fromString('OP_CHECKMULTISIG'); - var buf = s.toBuffer(); - var a = Address.fromScript(s); - a.toString().should.equal('3BYmEwgV2vANrmfRymr1mFnHXgLjD6gAWm'); - var b = new Address(s); - b.toString().should.equal('3BYmEwgV2vANrmfRymr1mFnHXgLjD6gAWm'); - var c = Address.fromScriptHash(bitcore.crypto.Hash.sha256ripemd160(buf)); - c.toString().should.equal('3BYmEwgV2vANrmfRymr1mFnHXgLjD6gAWm'); - }); + describe('from a script', function() { + it('should make this address from a script', function() { + var s = Script.fromString('OP_CHECKMULTISIG'); + var buf = s.toBuffer(); + var a = Address.fromScript(s); + a.toString().should.equal('3BYmEwgV2vANrmfRymr1mFnHXgLjD6gAWm'); + var b = new Address(s); + b.toString().should.equal('3BYmEwgV2vANrmfRymr1mFnHXgLjD6gAWm'); + var c = Address.fromScriptHash(bitcore.crypto.Hash.sha256ripemd160(buf)); + c.toString().should.equal('3BYmEwgV2vANrmfRymr1mFnHXgLjD6gAWm'); + }); - it('should make this address from other script', function() { - var s = Script.fromString('OP_CHECKSIG OP_HASH160'); - var a = Address.fromScript(s); - a.toString().should.equal('347iRqVwks5r493N1rsLN4k9J7Ljg488W7'); - var b = new Address(s); - b.toString().should.equal('347iRqVwks5r493N1rsLN4k9J7Ljg488W7'); + it('should make this address from other script', function() { + var s = Script.fromString('OP_CHECKSIG OP_HASH160'); + var a = Address.fromScript(s); + a.toString().should.equal('347iRqVwks5r493N1rsLN4k9J7Ljg488W7'); + var b = new Address(s); + b.toString().should.equal('347iRqVwks5r493N1rsLN4k9J7Ljg488W7'); + }); + + it('returns the same address if the script is a pay to public key hash out', function() { + var address = '16JXnhxjJUhxfyx4y6H4sFcxrgt8kQ8ewX'; + var script = Script.buildPublicKeyHashOut(new Address(address)); + Address(script, Networks.livenet).toString().should.equal(address); + }); + it('returns the same address if the script is a pay to script hash out', function() { + var address = '3BYmEwgV2vANrmfRymr1mFnHXgLjD6gAWm'; + var script = Script.buildScriptHashOut(new Address(address)); + Address(script, Networks.livenet).toString().should.equal(address); + }); }); it('should derive from this known address string livenet', function() { diff --git a/test/script/script.js b/test/script/script.js index 7fc66d0..5ea7176 100644 --- a/test/script/script.js +++ b/test/script/script.js @@ -6,6 +6,7 @@ var bitcore = require('../..'); var BufferUtil = bitcore.util.buffer; var Script = bitcore.Script; +var Networks = bitcore.Networks; var Opcode = bitcore.Opcode; var PublicKey = bitcore.PublicKey; var Address = bitcore.Address; @@ -584,9 +585,37 @@ describe('Script', function() { expect(BufferUtil.equal(Script('OP_RETURN 1 0xFF').getData(), new Buffer([255]))).to.be.true(); }); it('fails if content is not recognized', function() { - expect(function() { - return Script('1 0xFF').getData(); - }).to.throw(); + var failed = false; + try { + Script('1 0xFF').getData(); + } catch (e) { + failed = true; + } + failed.should.equal(true); + }); + }); + + describe('toAddress', function() { + it('for a P2PKH address', function() { + var stringAddress = '1NaTVwXDDUJaXDQajoa9MqHhz4uTxtgK14'; + var address = new Address(stringAddress); + var script = new Script(address); + script.toAddress(Networks.livenet).toString().should.equal(stringAddress); + }); + it('for a P2SH address', function() { + var stringAddress = '3GhtMmAbWrUf6Y8vDxn9ETB14R6V7Br3mt'; + var address = new Address(stringAddress); + var script = new Script(address); + script.toAddress(Networks.livenet).toString().should.equal(stringAddress); + }); + it('fails if content is not recognized', function() { + var failed = false; + try { + Script().toAddress(); + } catch (e) { + failed = true; + } + failed.should.equal(true); }); }); From c1a1571535f988c22cd204f370915c7ab76241ad Mon Sep 17 00:00:00 2001 From: Esteban Ordano Date: Tue, 30 Dec 2014 15:27:05 -0300 Subject: [PATCH 14/60] Fix JSDoc for script --- lib/script/script.js | 44 ++++++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/lib/script/script.js b/lib/script/script.js index aadd2db..55b43c1 100644 --- a/lib/script/script.js +++ b/lib/script/script.js @@ -214,7 +214,7 @@ Script.prototype.inspect = function() { // script classification methods /** - * @returns true if this is a pay to pubkey hash output script + * @returns {boolean} if this is a pay to pubkey hash output script */ Script.prototype.isPublicKeyHashOut = function() { return !!(this.chunks.length === 5 && @@ -226,7 +226,7 @@ Script.prototype.isPublicKeyHashOut = function() { }; /** - * @returns true if this is a pay to public key hash input script + * @returns {boolean} if this is a pay to public key hash input script */ Script.prototype.isPublicKeyHashIn = function() { return this.chunks.length === 2 && @@ -242,7 +242,7 @@ Script.prototype.getPublicKeyHash = function() { }; /** - * @returns true if this is a public key output script + * @returns {boolean} if this is a public key output script */ Script.prototype.isPublicKeyOut = function() { return this.chunks.length === 2 && @@ -252,7 +252,7 @@ Script.prototype.isPublicKeyOut = function() { }; /** - * @returns true if this is a pay to public key input script + * @returns {boolean} if this is a pay to public key input script */ Script.prototype.isPublicKeyIn = function() { return this.chunks.length === 1 && @@ -262,7 +262,7 @@ Script.prototype.isPublicKeyIn = function() { /** - * @returns true if this is a p2sh output script + * @returns {boolean} if this is a p2sh output script */ Script.prototype.isScriptHashOut = function() { var buf = this.toBuffer(); @@ -273,7 +273,7 @@ Script.prototype.isScriptHashOut = function() { }; /** - * @returns true if this is a p2sh input script + * @returns {boolean} if this is a p2sh input script * Note that these are frequently indistinguishable from pubkeyhashin */ Script.prototype.isScriptHashIn = function() { @@ -294,7 +294,7 @@ Script.prototype.isScriptHashIn = function() { }; /** - * @returns true if this is a mutlsig output script + * @returns {boolean} if this is a mutlsig output script */ Script.prototype.isMultisigOut = function() { return (this.chunks.length > 3 && @@ -308,7 +308,7 @@ Script.prototype.isMultisigOut = function() { /** - * @returns true if this is a multisig input script + * @returns {boolean} if this is a multisig input script */ Script.prototype.isMultisigIn = function() { return this.chunks.length >= 2 && @@ -321,7 +321,7 @@ Script.prototype.isMultisigIn = function() { }; /** - * @returns true if this is an OP_RETURN data script + * @returns {boolean} if this is an OP_RETURN data script */ Script.prototype.isDataOut = function() { return this.chunks.length >= 1 && @@ -350,7 +350,7 @@ Script.prototype.getData = function() { }; /** - * @returns true if the script is only composed of data pushing + * @returns {boolean} if the script is only composed of data pushing * opcodes or small int opcodes (OP_0, OP_1, ..., OP_16) */ Script.prototype.isPushOnly = function() { @@ -398,7 +398,7 @@ Script.prototype.classify = function() { /** - * @returns true if script is one of the known types + * @returns {boolean} if script is one of the known types */ Script.prototype.isStandard = function() { // TODO: Add BIP62 compliance @@ -531,7 +531,7 @@ Script.prototype.removeCodeseparators = function() { // high level script builder methods /** - * @returns a new Multisig output script for given public keys, + * @returns {Script} a new Multisig output script for given public keys, * requiring m of those public keys to spend * @param {PublicKey[]} publicKeys - list of all public keys controlling the output * @param {number} threshold - amount of required signatures to spend the output @@ -569,7 +569,7 @@ Script.buildMultisigOut = function(publicKeys, threshold, opts) { * @param {boolean=} opts.noSorting don't sort the given public keys before creating the script (false by default) * @param {Script=} opts.cachedMultisig don't recalculate the redeemScript * - * @returns Script + * @returns {Script} */ Script.buildP2SHMultisigIn = function(pubkeys, threshold, signatures, opts) { $.checkArgument(_.isArray(pubkeys)); @@ -586,7 +586,7 @@ Script.buildP2SHMultisigIn = function(pubkeys, threshold, signatures, opts) { }; /** - * @returns a new pay to public key hash output for the given + * @returns {Script} a new pay to public key hash output for the given * address or public key * @param {(Address|PublicKey)} to - destination address or public key */ @@ -608,7 +608,7 @@ Script.buildPublicKeyHashOut = function(to) { }; /** - * @returns a new pay to public key output for the given + * @returns {Script} a new pay to public key output for the given * public key */ Script.buildPublicKeyOut = function(pubkey) { @@ -620,7 +620,7 @@ Script.buildPublicKeyOut = function(pubkey) { }; /** - * @returns a new OP_RETURN script with data + * @returns {Script} a new OP_RETURN script with data * @param {(string|Buffer)} to - the data to embed in the output */ Script.buildDataOut = function(data) { @@ -639,7 +639,7 @@ Script.buildDataOut = function(data) { /** * @param {Script|Address} script - the redeemScript for the new p2sh output. * It can also be a p2sh address - * @returns Script new pay to script hash script for given script + * @returns {Script} new pay to script hash script for given script */ Script.buildScriptHashOut = function(script) { $.checkArgument(script instanceof Script || @@ -675,21 +675,21 @@ Script.buildPublicKeyHashIn = function(publicKey, signature, sigtype) { }; /** - * @returns Script an empty script + * @returns {Script} an empty script */ Script.empty = function() { return new Script(); }; /** - * @returns Script a new pay to script hash script that pays to this script + * @returns {Script} a new pay to script hash script that pays to this script */ Script.prototype.toScriptHashOut = function() { return Script.buildScriptHashOut(this); }; /** - * @return Script a script built from the address + * @return {Script} a script built from the address */ Script.fromAddress = function(address) { address = Address(address); @@ -702,7 +702,7 @@ Script.fromAddress = function(address) { }; /** - * @return Address the associated address for this script + * @return {Address} the associated address for this script */ Script.prototype.toAddress = function(network) { network = Networks.get(network); @@ -741,8 +741,8 @@ Script.prototype.findAndDelete = function(script) { }; /** - * @returns true if the chunk {i} is the smallest way to push that particular data. * Comes from bitcoind's script interpreter CheckMinimalPush function + * @returns {boolean} if the chunk {i} is the smallest way to push that particular data. */ Script.prototype.checkMinimalPush = function(i) { var chunk = this.chunks[i]; From ffca4cfb6f2afdfff09ec89259e058cdf434f53c Mon Sep 17 00:00:00 2001 From: Esteban Ordano Date: Tue, 30 Dec 2014 15:33:52 -0300 Subject: [PATCH 15/60] Fixes Script test case for coverage and style --- lib/script/script.js | 6 +++--- test/script/script.js | 20 ++++++-------------- 2 files changed, 9 insertions(+), 17 deletions(-) diff --git a/lib/script/script.js b/lib/script/script.js index 55b43c1..f72bad1 100644 --- a/lib/script/script.js +++ b/lib/script/script.js @@ -709,11 +709,11 @@ Script.prototype.toAddress = function(network) { $.checkArgument(network, 'Must provide a network'); if (this.isPublicKeyHashOut()) { return new Address(this.getData(), network, Address.PayToPublicKeyHash); - } - if (this.isScriptHashOut()) { + } else if (this.isScriptHashOut()) { return new Address(this.getData(), network, Address.PayToScriptHash); + } else { + throw new Error('The script type needs to be PayToPublicKeyHash or PayToScriptHash'); } - throw new Error('The script type needs to be PayToPublicKeyHash or PayToScriptHash'); }; /** diff --git a/test/script/script.js b/test/script/script.js index 5ea7176..48dbfb9 100644 --- a/test/script/script.js +++ b/test/script/script.js @@ -585,13 +585,9 @@ describe('Script', function() { expect(BufferUtil.equal(Script('OP_RETURN 1 0xFF').getData(), new Buffer([255]))).to.be.true(); }); it('fails if content is not recognized', function() { - var failed = false; - try { - Script('1 0xFF').getData(); - } catch (e) { - failed = true; - } - failed.should.equal(true); + expect(function() { + return Script('1 0xFF').getData(); + }).to.throw(); }); }); @@ -609,13 +605,9 @@ describe('Script', function() { script.toAddress(Networks.livenet).toString().should.equal(stringAddress); }); it('fails if content is not recognized', function() { - var failed = false; - try { - Script().toAddress(); - } catch (e) { - failed = true; - } - failed.should.equal(true); + expect(function() { + return Script().toAddress(Networks.livenet); + }).to.throw(); }); }); From f74c7f3929f7055bc559ae9253788d110febb815 Mon Sep 17 00:00:00 2001 From: Esteban Ordano Date: Tue, 30 Dec 2014 16:16:34 -0300 Subject: [PATCH 16/60] Simplify script to address method --- lib/script/script.js | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/lib/script/script.js b/lib/script/script.js index f72bad1..f7820cb 100644 --- a/lib/script/script.js +++ b/lib/script/script.js @@ -707,13 +707,10 @@ Script.fromAddress = function(address) { Script.prototype.toAddress = function(network) { network = Networks.get(network); $.checkArgument(network, 'Must provide a network'); - if (this.isPublicKeyHashOut()) { - return new Address(this.getData(), network, Address.PayToPublicKeyHash); - } else if (this.isScriptHashOut()) { - return new Address(this.getData(), network, Address.PayToScriptHash); - } else { - throw new Error('The script type needs to be PayToPublicKeyHash or PayToScriptHash'); + if (this.isPublicKeyHashOut() || this.isScriptHashOut()) { + return new Address(this, network); } + throw new Error('The script type needs to be PayToPublicKeyHash or PayToScriptHash'); }; /** From d5f76e8517ff17ee50e2cd08afd1975bdaa56993 Mon Sep 17 00:00:00 2001 From: Yemel Jardi Date: Tue, 30 Dec 2014 18:44:27 -0300 Subject: [PATCH 17/60] Fix creation of a randomn testnet private key --- lib/privatekey.js | 5 ++++- test/privatekey.js | 18 ++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/lib/privatekey.js b/lib/privatekey.js index c0569fe..794860c 100644 --- a/lib/privatekey.js +++ b/lib/privatekey.js @@ -97,7 +97,7 @@ PrivateKey.prototype._classifyArguments = function(data, network) { }; // detect type of data - if (_.isUndefined(data)){ + if (_.isUndefined(data) || _.isNull(data)){ info.bn = PrivateKey._getRandomBN(); } else if (data instanceof BN) { info.bn = data; @@ -105,6 +105,9 @@ PrivateKey.prototype._classifyArguments = function(data, network) { info = PrivateKey._transformBuffer(data, network); } else if (PrivateKey._isJSON(data)){ info = PrivateKey._transformJSON(data); + } else if (!network && Networks.get(data)) { + info.bn = PrivateKey._getRandomBN(); + info.network = Networks.get(data); } else if (typeof(data) === 'string'){ if (JSUtil.isHexa(data)) { info.bn = BN(new Buffer(data, 'hex')); diff --git a/test/privatekey.js b/test/privatekey.js index 6ecda31..6aaaa1d 100644 --- a/test/privatekey.js +++ b/test/privatekey.js @@ -31,6 +31,24 @@ describe('PrivateKey', function() { should.exist(b.bn); }); + it('should create a new random testnet private key with only one argument', function() { + var a = new PrivateKey(Networks.testnet); + should.exist(a); + should.exist(a.bn); + var b = PrivateKey(Networks.testnet); + should.exist(b); + should.exist(b.bn); + }); + + it('should create a new random testnet private key with an empty data', function() { + var a = new PrivateKey(null, Networks.testnet); + should.exist(a); + should.exist(a.bn); + var b = PrivateKey(null, Networks.testnet); + should.exist(b); + should.exist(b.bn); + }); + it('should create a private key from WIF string', function() { var a = new PrivateKey('L3T1s1TYP9oyhHpXgkyLoJFGniEgkv2Jhi138d7R2yJ9F4QdDU2m'); should.exist(a); From dfc32e6712bb1c1e7bcfbb55a9a8fe7257f551d1 Mon Sep 17 00:00:00 2001 From: Yemel Jardi Date: Tue, 30 Dec 2014 19:52:47 -0300 Subject: [PATCH 18/60] Fix typo and remove unnecessary test --- test/privatekey.js | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/test/privatekey.js b/test/privatekey.js index 6aaaa1d..7cae44b 100644 --- a/test/privatekey.js +++ b/test/privatekey.js @@ -35,18 +35,12 @@ describe('PrivateKey', function() { var a = new PrivateKey(Networks.testnet); should.exist(a); should.exist(a.bn); - var b = PrivateKey(Networks.testnet); - should.exist(b); - should.exist(b.bn); }); - it('should create a new random testnet private key with an empty data', function() { + it('should create a new random testnet private key with empty data', function() { var a = new PrivateKey(null, Networks.testnet); should.exist(a); should.exist(a.bn); - var b = PrivateKey(null, Networks.testnet); - should.exist(b); - should.exist(b.bn); }); it('should create a private key from WIF string', function() { From dbf47ef78fcf123b6549d6e4dc004d0962c774d6 Mon Sep 17 00:00:00 2001 From: Esteban Ordano Date: Wed, 31 Dec 2014 02:10:51 -0300 Subject: [PATCH 19/60] transaction: better errors for signature methods Closes #868 --- lib/errors/spec.js | 3 +++ lib/transaction/transaction.js | 14 ++++++++++++++ test/transaction/transaction.js | 16 ++++++++++++++++ 3 files changed, 33 insertions(+) diff --git a/lib/errors/spec.js b/lib/errors/spec.js index 6f45f95..a3e42bc 100644 --- a/lib/errors/spec.js +++ b/lib/errors/spec.js @@ -58,6 +58,9 @@ module.exports = [{ }, { name: 'NeedMoreInfo', message: format('{0}') + }, { + name: 'UnableToVerifySignature', + message: format('Unable to verify signature: {0}') }, { name: 'FeeError', message: format('Fees are not correctly set {0}'), diff --git a/lib/transaction/transaction.js b/lib/transaction/transaction.js index 588b685..6b4ca35 100644 --- a/lib/transaction/transaction.js +++ b/lib/transaction/transaction.js @@ -657,6 +657,14 @@ Transaction.prototype.applySignature = function(signature) { }; Transaction.prototype.isFullySigned = function() { + _.each(this.inputs, function(input) { + if (input.isFullySigned === Input.prototype.isFullySigned) { + throw new errors.Transaction.UnableToVerifySignature( + 'Unrecognized script kind, or not enough information to execute script.' + + 'This usually happens when creating a transaction from a serialized transaction' + ); + } + }); return _.all(_.map(this.inputs, function(input) { return input.isFullySigned(); })); @@ -664,6 +672,12 @@ Transaction.prototype.isFullySigned = function() { Transaction.prototype.isValidSignature = function(signature) { var self = this; + if (this.inputs[signature.inputIndex].isValidSignature === Input.prototype.isValidSignature) { + throw new errors.Transaction.UnableToVerifySignature( + 'Unrecognized script kind, or not enough information to execute script.' + + 'This usually happens when creating a transaction from a serialized transaction' + ); + } return this.inputs[signature.inputIndex].isValidSignature(self, signature); }; diff --git a/test/transaction/transaction.js b/test/transaction/transaction.js index b9ae1e2..742be1e 100644 --- a/test/transaction/transaction.js +++ b/test/transaction/transaction.js @@ -197,6 +197,22 @@ describe('Transaction', function() { satoshis: 1e8 }; + describe('not enough information errors', function() { + it('fails when Inputs are not subclassed and isFullySigned is called', function() { + var tx = new Transaction(tx_1_hex); + expect(function() { + return tx.isFullySigned(); + }).to.throw(errors.Transaction.UnableToVerifySignature); + }); + + it('fails when Inputs are not subclassed and verifySignature is called', function() { + var tx = new Transaction(tx_1_hex); + expect(function() { + return tx.isValidSignature({inputIndex: 0}); + }).to.throw(errors.Transaction.UnableToVerifySignature); + }); + }); + describe('checked serialize', function() { it('fails if no change address was set', function() { var transaction = new Transaction() From 5ef9975ac5a9446110f2bebdc26827ffc16bceb0 Mon Sep 17 00:00:00 2001 From: Esteban Ordano Date: Wed, 31 Dec 2014 02:19:11 -0300 Subject: [PATCH 20/60] Honor publicKey.network on create multisig address Closes #870 --- lib/address.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/address.js b/lib/address.js index abc0aca..96d1a06 100644 --- a/lib/address.js +++ b/lib/address.js @@ -262,6 +262,7 @@ Address._transformScript = function(script, network){ */ Address.createMultisig = function(publicKeys, threshold, network) { var Script = require('./script'); + network = network || publicKeys[0].network; return new Address(Script.buildMultisigOut(publicKeys, threshold), network || Networks.defaultNetwork); }; From 901d836231be118f36ac5220fb15769ddce16d36 Mon Sep 17 00:00:00 2001 From: Esteban Ordano Date: Wed, 31 Dec 2014 02:15:59 -0300 Subject: [PATCH 21/60] Add default network to PublicKey Closes #871 --- lib/publickey.js | 2 +- test/publickey.js | 12 +++++++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/lib/publickey.js b/lib/publickey.js index e8fc58f..182b7aa 100644 --- a/lib/publickey.js +++ b/lib/publickey.js @@ -64,7 +64,7 @@ var PublicKey = function PublicKey(data, extra) { Object.defineProperty(this, 'network', { configurable: false, - value: info.network + value: info.network || Network.defaultNetwork }); return this; diff --git a/test/publickey.js b/test/publickey.js index 7220751..de4011d 100644 --- a/test/publickey.js +++ b/test/publickey.js @@ -8,8 +8,8 @@ var Point = bitcore.crypto.Point; var BN = bitcore.crypto.BN; var PublicKey = bitcore.PublicKey; var PrivateKey = bitcore.PrivateKey; +var Networks = bitcore.Networks; -// DER uncompressed format /* jshint maxlen: 200 */ describe('PublicKey', function() { @@ -65,6 +65,12 @@ describe('PublicKey', function() { publicKey.should.equal(publicKey2); }); + it('sets the network to defaultNetwork if none provided', function() { + var publicKeyHex = '031ff0fe0f7b15ffaa85ff9f4744d539139c252a49710fb053bb9f2b933173ff9a'; + var publicKey = new PublicKey(publicKeyHex); + publicKey.network.should.equal(Networks.defaultNetwork); + }); + it('from a hex encoded DER string', function() { var pk = new PublicKey('041ff0fe0f7b15ffaa85ff9f4744d539139c252a49710fb053bb9f2b933173ff9a7baad41d04514751e6851f5304fd243751703bed21b914f6be218c0fa354a341'); should.exist(pk.point); @@ -317,12 +323,12 @@ describe('PublicKey', function() { describe('#inspect', function() { it('should output known uncompressed pubkey for console', function() { var pubkey = PublicKey.fromString('041ff0fe0f7b15ffaa85ff9f4744d539139c252a49710fb053bb9f2b933173ff9a7baad41d04514751e6851f5304fd243751703bed21b914f6be218c0fa354a341'); - pubkey.inspect().should.equal(''); + pubkey.inspect().should.equal(''); }); it('should output known compressed pubkey for console', function() { var pubkey = PublicKey.fromString('031ff0fe0f7b15ffaa85ff9f4744d539139c252a49710fb053bb9f2b933173ff9a'); - pubkey.inspect().should.equal(''); + pubkey.inspect().should.equal(''); }); it('should output known compressed pubkey with network for console', function() { From 95bfb188490ae519ef18a224a92d6e49d38493eb Mon Sep 17 00:00:00 2001 From: bip32JP Date: Fri, 2 Jan 2015 15:32:33 +0900 Subject: [PATCH 22/60] Fix the error loop. I added a similar badrs function to python-ecdsa and compared the results. The 1 badrs (aka forcing it to loop once) gave me a different value. It turns out you missed one of the `v = hmac_k(v)` steps during the loop. Adding one extra `v = hmac_k(v)` in each loop makes it match up with python-ecdsa perfectly (I even tried up to badrs = 30 and it was fine. --- lib/crypto/ecdsa.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/crypto/ecdsa.js b/lib/crypto/ecdsa.js index 9ab220d..42fa774 100644 --- a/lib/crypto/ecdsa.js +++ b/lib/crypto/ecdsa.js @@ -100,6 +100,7 @@ ECDSA.prototype.deterministicK = function(badrs) { for (var i = 0; i < badrs || !(T.lt(N) && T.gt(0)); i++) { k = Hash.sha256hmac(Buffer.concat([v, new Buffer([0x00])]), k); v = Hash.sha256hmac(v, k); + v = Hash.sha256hmac(v, k); T = BN.fromBuffer(v); } From bc6f592daa4b06d745cb09e2067eb63f708c501a Mon Sep 17 00:00:00 2001 From: bip32JP Date: Fri, 2 Jan 2015 15:33:46 +0900 Subject: [PATCH 23/60] fix tests fix tests to match python-ecdsa --- test/crypto/ecdsa.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/crypto/ecdsa.js b/test/crypto/ecdsa.js index 79cd53b..bce76cd 100644 --- a/test/crypto/ecdsa.js +++ b/test/crypto/ecdsa.js @@ -103,7 +103,7 @@ describe('ECDSA', function() { ecdsa.k.toBuffer().toString('hex') .should.not.equal('fcce1de7a9bcd6b2d3defade6afa1913fb9229e3b7ddf4749b55c4848b2a196e'); ecdsa.k.toBuffer().toString('hex') - .should.equal('6f4dcca6fa7a137ae9d110311905013b3c053c732ad18611ec2752bb3dcef9d8'); + .should.equal('727fbcb59eb48b1d7d46f95a04991fc512eb9dbf9105628e3aec87428df28fd8'); }); it('should compute this test vector correctly', function() { // test fixture from bitcoinjs From 2de77f6f5c7f1e5a8041158742563ffb051085d3 Mon Sep 17 00:00:00 2001 From: Braydon Fuller Date: Fri, 2 Jan 2015 12:38:55 -0500 Subject: [PATCH 24/60] Use the same version as specified in the elliptic package.json --- npm-shrinkwrap.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 1f09939..df0e256 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -43,9 +43,9 @@ "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-0.15.2.tgz" }, "hash.js": { - "version": "0.3.2", - "from": "hash.js@0.3.2", - "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-0.3.2.tgz" + "version": "0.2.0", + "from": "hash.js@0.2.0", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-0.2.0.tgz" }, "inherits": { "version": "2.0.1", From 6ace41834f47950b7503bcc22cb0b2f095750382 Mon Sep 17 00:00:00 2001 From: Braydon Fuller Date: Fri, 2 Jan 2015 13:11:26 -0500 Subject: [PATCH 25/60] Upgraded elliptic and bn to the latest versions --- npm-shrinkwrap.json | 29 +++++++++++++++++------------ package.json | 4 ++-- 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index df0e256..62cd952 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -13,9 +13,9 @@ "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-0.4.1.tgz" }, "bn.js": { - "version": "0.15.2", - "from": "bn.js@0.15.2", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-0.15.2.tgz" + "version": "0.16.0", + "from": "bn.js@0.16.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-0.16.0.tgz" }, "bs58": { "version": "2.0.0", @@ -33,19 +33,24 @@ "resolved": "https://registry.npmjs.org/buffers/-/buffers-0.1.1.tgz" }, "elliptic": { - "version": "0.15.12", - "from": "elliptic@0.15.12", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-0.15.12.tgz", + "version": "0.16.0", + "from": "elliptic@0.16.0", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-0.16.0.tgz", "dependencies": { "bn.js": { - "version": "0.15.2", - "from": "bn.js@0.15.2", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-0.15.2.tgz" + "version": "0.16.0", + "from": "bn.js@0.16.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-0.16.0.tgz" + }, + "brorand": { + "version": "1.0.1", + "from": "brorand@1.0.1", + "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.0.1.tgz" }, "hash.js": { - "version": "0.2.0", - "from": "hash.js@0.2.0", - "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-0.2.0.tgz" + "version": "0.3.2", + "from": "hash.js@0.3.2", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-0.3.2.tgz" }, "inherits": { "version": "2.0.1", diff --git a/package.json b/package.json index f36534d..a0aa3df 100644 --- a/package.json +++ b/package.json @@ -71,11 +71,11 @@ }, "dependencies": { "asn1.js": "=0.4.1", - "bn.js": "=0.15.2", + "bn.js": "=0.16.0", "bs58": "=2.0.0", "bufferput": "^0.1.2", "buffers": "^0.1.1", - "elliptic": "=0.15.14", + "elliptic": "=0.16.0", "hash.js": "=0.3.2", "inherits": "=2.0.1", "jsrsasign": "=0.0.3", From 9deec5e1c2925b577ba70c932aaa41305f25fa40 Mon Sep 17 00:00:00 2001 From: Yemel Jardi Date: Fri, 2 Jan 2015 15:38:28 -0300 Subject: [PATCH 26/60] Use defaultNetwork in Script.toAddress --- lib/script/script.js | 4 ++-- test/script/script.js | 6 ++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/script/script.js b/lib/script/script.js index f7820cb..428a39f 100644 --- a/lib/script/script.js +++ b/lib/script/script.js @@ -702,11 +702,11 @@ Script.fromAddress = function(address) { }; /** + * @param {Network} [network] * @return {Address} the associated address for this script */ Script.prototype.toAddress = function(network) { - network = Networks.get(network); - $.checkArgument(network, 'Must provide a network'); + network = Networks.get(network) || Networks.defaultNetwork; if (this.isPublicKeyHashOut() || this.isScriptHashOut()) { return new Address(this, network); } diff --git a/test/script/script.js b/test/script/script.js index 48dbfb9..29b7208 100644 --- a/test/script/script.js +++ b/test/script/script.js @@ -592,6 +592,12 @@ describe('Script', function() { }); describe('toAddress', function() { + it('uses default network', function() { + var stringAddress = '1NaTVwXDDUJaXDQajoa9MqHhz4uTxtgK14'; + var address = new Address(stringAddress); + var script = new Script(address); + script.toAddress().toString().should.equal(stringAddress); + }); it('for a P2PKH address', function() { var stringAddress = '1NaTVwXDDUJaXDQajoa9MqHhz4uTxtgK14'; var address = new Address(stringAddress); From 476f009b4d5fb983b020368ca74e97d38b969b6e Mon Sep 17 00:00:00 2001 From: Esteban Ordano Date: Tue, 30 Dec 2014 22:12:24 -0300 Subject: [PATCH 27/60] Add Insight and UTXO class --- index.js | 4 ++ lib/explorers/insight.js | 80 ++++++++++++++++++++++++++++++++++ lib/transaction/transaction.js | 80 +++++----------------------------- lib/util/js.js | 1 + lib/utxo.js | 72 ++++++++++++++++++++++++++++++ npm-shrinkwrap.json | 12 ++++- package.json | 5 +++ 7 files changed, 185 insertions(+), 69 deletions(-) create mode 100644 lib/explorers/insight.js create mode 100644 lib/utxo.js diff --git a/index.js b/index.js index b3b14ca..aeb1df0 100644 --- a/index.js +++ b/index.js @@ -53,5 +53,9 @@ bitcore.deps.bs58 = require('bs58'); bitcore.deps.Buffer = Buffer; bitcore.deps.elliptic = require('elliptic'); +// blockchain explorers +bitcore.explorers = {}; +bitcore.explorers.Insight = require('./lib/explorers/insight'); + // Internal usage, exposed for testing/advanced tweaking bitcore._HDKeyCache = require('./lib/hdkeycache'); diff --git a/lib/explorers/insight.js b/lib/explorers/insight.js new file mode 100644 index 0000000..be8313a --- /dev/null +++ b/lib/explorers/insight.js @@ -0,0 +1,80 @@ +'use strict'; + +var Networks = require('../networks'); +var JSUtil = require('../util/js'); +var $ = require('../util/preconditions'); +var _ = require('lodash'); +var Address = require('../address'); +var Transaction = require('../transaction'); +var UTXO = require('../utxo'); + +var request = require('request'); + +// var insight = new Insight(Networks.livenet); + +function Insight(url, network) { + if (!url && !network) { + return new Insight(Networks.defaultNetwork); + } + if (Networks.get(url)) { + network = Networks.get(url); + if (network === Networks.livenet) { + url = 'https://insight.bitpay.com'; + } else { + url = 'https://test-insight.bitpay.com'; + } + } + JSUtil.defineImmutable(this, { + url: url, + network: Networks.get(network) || Networks.defaultNetwork + }); + return this; +} + +Insight.prototype.getUnspentUtxos = function(addresses, callback) { + $.checkArgument(_.isFunction(callback)); + if (!_.isArray(addresses)) { + addresses = [addresses]; + } + addresses = _.map(addresses, function(address) { return new Address(address); }); + + this.requestPost('/api/addrs/utxo', { + addrs: _.map(addresses, function(address) { return address.toString(); }).join(',') + }, function(err, res, unspent) { + if (err || res.statusCode !== 200) { + return callback(err || res); + } + unspent = _.map(unspent, UTXO); + + return callback(null, unspent); + }); +}; + +Insight.prototype.broadcast = function(transaction, callback) { + $.checkArgument(JSUtil.isHexa(transaction) || transaction instanceof Transaction); + $.checkArgument(_.isFunction(callback)); + if (transaction instanceof Transaction) { + transaction = transaction.serialize(); + } + + this.requestPost('/api/tx/send', { + rawtx: transaction + }, function(err, res, body) { + if (err || res.statusCode !== 200) { + return callback(err || body); + } + return callback(null, body ? body.txid : null); + }); +}; + +Insight.prototype.requestPost = function(path, data, callback) { + $.checkArgument(_.isString(path)); + $.checkArgument(_.isFunction(callback)); + request({ + method: 'POST', + url: this.url + path, + json: data + }, callback); +}; + +module.exports = Insight; diff --git a/lib/transaction/transaction.js b/lib/transaction/transaction.js index 6b4ca35..1af6b7f 100644 --- a/lib/transaction/transaction.js +++ b/lib/transaction/transaction.js @@ -15,7 +15,7 @@ var Signature = require('../crypto/signature'); var Sighash = require('./sighash'); var Address = require('../address'); -var Unit = require('../unit'); +var UTXO = require('../utxo'); var Input = require('./input'); var PublicKeyHashInput = Input.PublicKeyHash; var MultiSigScriptHashInput = Input.MultiSigScriptHash; @@ -293,68 +293,23 @@ Transaction.prototype._newTransaction = function() { * @param {number=} threshold */ Transaction.prototype.from = function(utxo, pubkeys, threshold) { + if (_.isArray(utxo)) { + var self = this; + _.each(utxo, function(utxo) { + self.from(utxo, pubkeys, threshold); + }); + return this; + } if (pubkeys && threshold) { - this._fromMultiSigP2SH(utxo, pubkeys, threshold); + this._fromMultisigUtxo(utxo, pubkeys, threshold); } else { this._fromNonP2SH(utxo); } return this; }; -Transaction.prototype._fromMultiSigP2SH = function(utxo, pubkeys, threshold) { - if (Transaction._isNewUtxo(utxo)) { - this._fromMultisigNewUtxo(utxo, pubkeys, threshold); - } else if (Transaction._isOldUtxo(utxo)) { - this._fromMultisigOldUtxo(utxo, pubkeys, threshold); - } else { - throw new Transaction.Errors.UnrecognizedUtxoFormat(utxo); - } -}; - Transaction.prototype._fromNonP2SH = function(utxo) { - var self = this; - if (_.isArray(utxo)) { - _.each(utxo, function(single) { - self._fromNonP2SH(single); - }); - return; - } - if (Transaction._isNewUtxo(utxo)) { - this._fromNewUtxo(utxo); - } else if (Transaction._isOldUtxo(utxo)) { - this._fromOldUtxo(utxo); - } else { - throw new Transaction.Errors.UnrecognizedUtxoFormat(utxo); - } -}; - -Transaction._isNewUtxo = function(utxo) { - var isDefined = function(param) { - return !_.isUndefined(param); - }; - return _.all(_.map([utxo.txId, utxo.outputIndex, utxo.satoshis, utxo.script], isDefined)); -}; - -Transaction._isOldUtxo = function(utxo) { - var isDefined = function(param) { - return !_.isUndefined(param); - }; - return _.all(_.map([utxo.txid, utxo.vout, utxo.scriptPubKey, utxo.amount], isDefined)); -}; - -Transaction.prototype._fromOldUtxo = function(utxo) { - return this._fromNewUtxo({ - address: utxo.address && new Address(utxo.address), - txId: utxo.txid, - outputIndex: utxo.vout, - script: util.isHexa(utxo.script) ? new buffer.Buffer(utxo.scriptPubKey, 'hex') : utxo.scriptPubKey, - satoshis: Unit.fromBTC(utxo.amount).satoshis - }); -}; - -Transaction.prototype._fromNewUtxo = function(utxo) { - utxo.address = utxo.address && new Address(utxo.address); - utxo.script = new Script(util.isHexa(utxo.script) ? new buffer.Buffer(utxo.script, 'hex') : utxo.script); + utxo = new UTXO(utxo); this.inputs.push(new PublicKeyHashInput({ output: new Output({ script: utxo.script, @@ -368,19 +323,8 @@ Transaction.prototype._fromNewUtxo = function(utxo) { this._inputAmount += utxo.satoshis; }; -Transaction.prototype._fromMultisigOldUtxo = function(utxo, pubkeys, threshold) { - return this._fromMultisigNewUtxo({ - address: utxo.address && new Address(utxo.address), - txId: utxo.txid, - outputIndex: utxo.vout, - script: new buffer.Buffer(utxo.scriptPubKey, 'hex'), - satoshis: Unit.fromBTC(utxo.amount).satoshis - }, pubkeys, threshold); -}; - -Transaction.prototype._fromMultisigNewUtxo = function(utxo, pubkeys, threshold) { - utxo.address = utxo.address && new Address(utxo.address); - utxo.script = new Script(util.isHexa(utxo.script) ? new buffer.Buffer(utxo.script, 'hex') : utxo.script); +Transaction.prototype._fromMultisigUtxo = function(utxo, pubkeys, threshold) { + utxo = new UTXO(utxo); this.addInput(new MultiSigScriptHashInput({ output: new Output({ script: utxo.script, diff --git a/lib/util/js.js b/lib/util/js.js index 85c1284..05c9562 100644 --- a/lib/util/js.js +++ b/lib/util/js.js @@ -55,6 +55,7 @@ module.exports = { Object.keys(values).forEach(function(key){ Object.defineProperty(target, key, { configurable: false, + enumerable: true, value: values[key] }); }); diff --git a/lib/utxo.js b/lib/utxo.js new file mode 100644 index 0000000..1456db7 --- /dev/null +++ b/lib/utxo.js @@ -0,0 +1,72 @@ +'use strict'; + +var _ = require('lodash'); +var $ = require('./util/preconditions'); +var JSUtil = require('./util/js'); + +var Script = require('./script'); +var Address = require('./address'); +var Unit = require('./unit'); + +function UTXO(data) { + /* jshint maxcomplexity: 20 */ + /* jshint maxstatements: 20 */ + if (!(this instanceof UTXO)) { + return new UTXO(data); + } + $.checkArgument(_.isObject(data), 'Must provide an object from where to extract data'); + var address = data.address ? new Address(data.address) : undefined; + var txId = data.txid ? data.txid : data.txId; + if (!txId || !JSUtil.isHexaString(txId) || txId.length > 64) { + // TODO: Use the errors library + throw new Error('Invalid TXID in object', data); + } + var outputIndex = _.isUndefined(data.vout) ? data.outputIndex : data.vout; + if (!_.isNumber(outputIndex)) { + throw new Error('Invalid outputIndex, received ' + outputIndex); + } + $.checkArgument(data.scriptPubKey || data.script, 'Must provide the scriptPubKey for that output!'); + var script = new Script(data.scriptPubKey || data.script); + $.checkArgument(data.amount || data.satoshis, 'Must provide the scriptPubKey for that output!'); + var amount = data.amount ? new Unit.fromBTC(data.amount).toSatoshis() : data.satoshis; + $.checkArgument(_.isNumber(amount), 'Amount must be a number'); + JSUtil.defineImmutable(this, { + address: address, + txId: txId, + outputIndex: outputIndex, + script: script, + satoshis: amount + }); +} + +UTXO.prototype.inspect = function() { + return ''; +}; + +UTXO.prototype.toString = function() { + return this.txId + ':' + this.outputIndex; +}; + +UTXO.fromJSON = UTXO.fromObject = function(data) { + if (_.isString(data)) { + data = JSON.parse(data); + } + return new UTXO(data); +}; + +UTXO.prototype.toJSON = function() { + return JSON.stringify(this.toObject()); +}; + +UTXO.prototype.toObject = function() { + return { + address: this.address.toObject(), + txid: this.txId, + vout: this.outputIndex, + scriptPubKey: this.script.toObject(), + amount: Unit.fromSatoshis(this.satoshis).toBTC() + }; +}; + +module.exports = UTXO; diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 62cd952..02adf12 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -1,6 +1,6 @@ { "name": "bitcore", - "version": "0.8.0", + "version": "0.8.5", "dependencies": { "aes": { "version": "0.1.0", @@ -17,6 +17,11 @@ "from": "bn.js@0.16.0", "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-0.16.0.tgz" }, + "browser-request": { + "version": "0.3.3", + "from": "browser-request@*", + "resolved": "https://registry.npmjs.org/browser-request/-/browser-request-0.3.3.tgz" + }, "bs58": { "version": "2.0.0", "from": "bs58@2.0.0", @@ -89,6 +94,11 @@ "from": "protobufjs@3.0.0", "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-3.0.0.tgz" }, + "request": { + "version": "2.51.0", + "from": "request@*", + "resolved": "https://registry.npmjs.org/request/-/request-2.51.0.tgz" + }, "sha512": { "version": "0.0.1", "from": "sha512@=0.0.1", diff --git a/package.json b/package.json index a0aa3df..d94b9e8 100644 --- a/package.json +++ b/package.json @@ -69,9 +69,13 @@ "type": "git", "url": "https://github.com/bitpay/bitcore.git" }, + "browser": { + "request": "browser-request" + }, "dependencies": { "asn1.js": "=0.4.1", "bn.js": "=0.16.0", + "browser-request": "^0.3.3", "bs58": "=2.0.0", "bufferput": "^0.1.2", "buffers": "^0.1.1", @@ -81,6 +85,7 @@ "jsrsasign": "=0.0.3", "lodash": "=2.4.1", "protobufjs": "=3.0.0", + "request": "^2.51.0", "sha512": "=0.0.1", "socks5-client": "^0.3.6" }, From 9362927b43cf598e87bcccda06bf5ab9e42a2b69 Mon Sep 17 00:00:00 2001 From: Esteban Ordano Date: Wed, 31 Dec 2014 00:53:21 -0300 Subject: [PATCH 28/60] Add test coverage for Insight --- test/explorers/insight.js | 103 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 test/explorers/insight.js diff --git a/test/explorers/insight.js b/test/explorers/insight.js new file mode 100644 index 0000000..5dff460 --- /dev/null +++ b/test/explorers/insight.js @@ -0,0 +1,103 @@ +'use strict'; + +var sinon = require('sinon'); +var should = require('chai').should(); +var expect = require('chai').expect; +var bitcore = require('../..'); + +var Insight = bitcore.explorers.Insight; +var Address = bitcore.Address; +var Transaction = bitcore.Transaction; +var Networks = bitcore.Networks; + +describe.only('Insight', function() { + + describe('instantiation', function() { + it('can be created without any parameters', function() { + var insight = new Insight(); + insight.url.should.equal('https://insight.bitpay.com'); + insight.network.should.equal(Networks.livenet); + }); + it('can be created providing just a network', function() { + var insight = new Insight(Networks.testnet); + insight.url.should.equal('https://test-insight.bitpay.com'); + insight.network.should.equal(Networks.testnet); + }); + it('can be created with a custom url', function() { + var url = 'https://localhost:1234'; + var insight = new Insight(url); + insight.url.should.equal(url); + }); + it('can be created with a custom url and network', function() { + var url = 'https://localhost:1234'; + var insight = new Insight(url, Networks.testnet); + insight.url.should.equal(url); + insight.network.should.equal(Networks.testnet); + }); + it('defaults to defaultNetwork on a custom url', function() { + var insight = new Insight('https://localhost:1234'); + insight.network.should.equal(Networks.defaultNetwork); + }); + }); + + describe('getting unspent utxos', function() { + var insight = new Insight(); + var address = '371mZyMp4t6uVtcEr4DAAbTZyby9Lvia72'; + beforeEach(function() { + insight.requestPost = sinon.stub(); + insight.requestPost.onFirstCall().callsArgWith(2, null, {statusCode: 200}); + }); + it('can receive an address', function(callback) { + insight.getUnspentUtxos(new Address(address), callback); + }); + it('can receive a address as a string', function(callback) { + insight.getUnspentUtxos(address, callback); + }); + it('can receive an array of addresses', function(callback) { + insight.getUnspentUtxos([address, new Address(address)], callback); + }); + it('errors if server is not available', function(callback) { + insight.requestPost.onFirstCall().callsArgWith(2, 'Unable to connect'); + insight.getUnspentUtxos(address, function(error) { + expect(error).to.equal('Unable to connect'); + callback(); + }); + }); + it('errors if server returns errorcode', function(callback) { + insight.requestPost.onFirstCall().callsArgWith(2, null, {statusCode: 400}); + insight.getUnspentUtxos(address, function(error) { + expect(error).to.deep.equal({statusCode: 400}); + callback(); + }); + }); + }); + + describe('broadcasting a transaction', function() { + var insight = new Insight(); + var tx = require('../data/tx_creation.json')[0][7]; + beforeEach(function() { + insight.requestPost = sinon.stub(); + insight.requestPost.onFirstCall().callsArgWith(2, null, {statusCode: 200}); + }); + it('accepts a raw transaction', function(callback) { + insight.broadcast(tx, callback); + }); + it('accepts a transaction model', function(callback) { + insight.broadcast(new Transaction(tx), callback); + }); + it('errors if server is not available', function(callback) { + insight.requestPost.onFirstCall().callsArgWith(2, 'Unable to connect'); + insight.broadcast(tx, function(error) { + expect(error).to.equal('Unable to connect'); + callback(); + }); + }); + it('errors if server returns errorcode', function(callback) { + insight.requestPost.onFirstCall().callsArgWith(2, null, {statusCode: 400}, 'error'); + insight.broadcast(tx, function(error) { + expect(error).to.equal('error'); + callback(); + }); + }); + }); +}); From 1cf108ab473b104e28b400e58c17aaaea0df7ba3 Mon Sep 17 00:00:00 2001 From: Esteban Ordano Date: Wed, 31 Dec 2014 01:21:59 -0300 Subject: [PATCH 29/60] Add tests for UTXO --- index.js | 1 + lib/utxo.js | 6 ++-- test/explorers/insight.js | 2 +- test/utxo.js | 75 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 80 insertions(+), 4 deletions(-) create mode 100644 test/utxo.js diff --git a/index.js b/index.js index aeb1df0..0194dee 100644 --- a/index.js +++ b/index.js @@ -45,6 +45,7 @@ bitcore.Script = require('./lib/script'); bitcore.Transaction = require('./lib/transaction'); bitcore.URI = require('./lib/uri'); bitcore.Unit = require('./lib/unit'); +bitcore.UTXO = require('./lib/utxo'); // dependencies, subject to change bitcore.deps = {}; diff --git a/lib/utxo.js b/lib/utxo.js index 1456db7..f77ddf3 100644 --- a/lib/utxo.js +++ b/lib/utxo.js @@ -49,7 +49,7 @@ UTXO.prototype.toString = function() { }; UTXO.fromJSON = UTXO.fromObject = function(data) { - if (_.isString(data)) { + if (JSUtil.isValidJSON(data)) { data = JSON.parse(data); } return new UTXO(data); @@ -61,10 +61,10 @@ UTXO.prototype.toJSON = function() { UTXO.prototype.toObject = function() { return { - address: this.address.toObject(), + address: this.address.toString(), txid: this.txId, vout: this.outputIndex, - scriptPubKey: this.script.toObject(), + scriptPubKey: this.script.toBuffer().toString('hex'), amount: Unit.fromSatoshis(this.satoshis).toBTC() }; }; diff --git a/test/explorers/insight.js b/test/explorers/insight.js index 5dff460..41b6745 100644 --- a/test/explorers/insight.js +++ b/test/explorers/insight.js @@ -10,7 +10,7 @@ var Address = bitcore.Address; var Transaction = bitcore.Transaction; var Networks = bitcore.Networks; -describe.only('Insight', function() { +describe('Insight', function() { describe('instantiation', function() { it('can be created without any parameters', function() { diff --git a/test/utxo.js b/test/utxo.js new file mode 100644 index 0000000..a5bd94e --- /dev/null +++ b/test/utxo.js @@ -0,0 +1,75 @@ +'use strict'; + +var _ = require('lodash'); +var chai = require('chai'); +var should = chai.should(); +var expect = chai.expect; + +var bitcore = require('..'); +var UTXO = bitcore.UTXO; + +describe('UTXO', function() { + + var sampleData1 = { + 'address': 'mszYqVnqKoQx4jcTdJXxwKAissE3Jbrrc1', + 'txId': 'a477af6b2667c29670467e4e0728b685ee07b240235771862318e29ddbe58458', + 'outputIndex': 0, + 'script': 'OP_DUP OP_HASH160 20 0x88d9931ea73d60eaf7e5671efc0552b912911f2a OP_EQUALVERIFY OP_CHECKSIG', + 'satoshis': 1020000 + }; + var sampleData2 = { + 'txid': 'e42447187db5a29d6db161661e4bc66d61c3e499690fe5ea47f87b79ca573986', + 'vout': 1, + 'address': 'mgBCJAsvzgT2qNNeXsoECg2uPKrUsZ76up', + 'scriptPubKey': '76a914073b7eae2823efa349e3b9155b8a735526463a0f88ac', + 'amount': 0.01080000 + }; + + it('roundtrip from raw data', function() { + expect(UTXO(sampleData2).toObject()).to.deep.equal(sampleData2); + }); + + it('can be created without "new" operand', function() { + expect(UTXO(sampleData1) instanceof UTXO).to.equal(true); + }); + + it('fails if no tx id is provided', function() { + expect(function() { + return new UTXO({}); + }).to.throw(); + }); + + it('fails if vout is not a number', function() { + var sample = _.cloneDeep(sampleData2); + sample.vout = '1'; + expect(function() { + return new UTXO(sample); + }).to.throw(); + }); + + it('displays nicely on the console', function() { + var expected = ''; + expect(new UTXO(sampleData1).inspect()).to.equal(expected); + }); + + it('toString returns txid:vout', function() { + var expected = 'a477af6b2667c29670467e4e0728b685ee07b240235771862318e29ddbe58458:0'; + expect(new UTXO(sampleData1).toString()).to.equal(expected); + }); + + it('to/from JSON roundtrip', function() { + var utxo = new UTXO(sampleData2); + expect( + JSON.parse( + UTXO.fromJSON( + UTXO.fromObject( + UTXO.fromJSON( + utxo.toJSON() + ).toObject() + ).toJSON() + ).toJSON() + ) + ).to.deep.equal(sampleData2); + }); +}); From 11975bc0df5e2670c944f2ed1bec7c5e3aff7e97 Mon Sep 17 00:00:00 2001 From: Esteban Ordano Date: Wed, 31 Dec 2014 01:29:36 -0300 Subject: [PATCH 30/60] Rename UTXO to Transaction.UnspentOutput --- index.js | 1 - lib/explorers/insight.js | 4 +-- lib/transaction/index.js | 1 + lib/transaction/transaction.js | 6 ++-- lib/{utxo.js => transaction/unspentoutput.js} | 32 +++++++++---------- test/utxo.js | 26 +++++++-------- 6 files changed, 35 insertions(+), 35 deletions(-) rename lib/{utxo.js => transaction/unspentoutput.js} (71%) diff --git a/index.js b/index.js index 0194dee..aeb1df0 100644 --- a/index.js +++ b/index.js @@ -45,7 +45,6 @@ bitcore.Script = require('./lib/script'); bitcore.Transaction = require('./lib/transaction'); bitcore.URI = require('./lib/uri'); bitcore.Unit = require('./lib/unit'); -bitcore.UTXO = require('./lib/utxo'); // dependencies, subject to change bitcore.deps = {}; diff --git a/lib/explorers/insight.js b/lib/explorers/insight.js index be8313a..87da50f 100644 --- a/lib/explorers/insight.js +++ b/lib/explorers/insight.js @@ -6,7 +6,7 @@ var $ = require('../util/preconditions'); var _ = require('lodash'); var Address = require('../address'); var Transaction = require('../transaction'); -var UTXO = require('../utxo'); +var UnspentOutput = Transaction.UnspentOutput; var request = require('request'); @@ -44,7 +44,7 @@ Insight.prototype.getUnspentUtxos = function(addresses, callback) { if (err || res.statusCode !== 200) { return callback(err || res); } - unspent = _.map(unspent, UTXO); + unspent = _.map(unspent, UnspentOutput); return callback(null, unspent); }); diff --git a/lib/transaction/index.js b/lib/transaction/index.js index 55db6e0..e826635 100644 --- a/lib/transaction/index.js +++ b/lib/transaction/index.js @@ -2,3 +2,4 @@ module.exports = require('./transaction'); module.exports.Input = require('./input'); module.exports.Output = require('./output'); +module.exports.UnspentOutput = require('./unspentoutput'); diff --git a/lib/transaction/transaction.js b/lib/transaction/transaction.js index 1af6b7f..5370e25 100644 --- a/lib/transaction/transaction.js +++ b/lib/transaction/transaction.js @@ -15,7 +15,7 @@ var Signature = require('../crypto/signature'); var Sighash = require('./sighash'); var Address = require('../address'); -var UTXO = require('../utxo'); +var UnspentOutput = require('./unspentoutput'); var Input = require('./input'); var PublicKeyHashInput = Input.PublicKeyHash; var MultiSigScriptHashInput = Input.MultiSigScriptHash; @@ -309,7 +309,7 @@ Transaction.prototype.from = function(utxo, pubkeys, threshold) { }; Transaction.prototype._fromNonP2SH = function(utxo) { - utxo = new UTXO(utxo); + utxo = new UnspentOutput(utxo); this.inputs.push(new PublicKeyHashInput({ output: new Output({ script: utxo.script, @@ -324,7 +324,7 @@ Transaction.prototype._fromNonP2SH = function(utxo) { }; Transaction.prototype._fromMultisigUtxo = function(utxo, pubkeys, threshold) { - utxo = new UTXO(utxo); + utxo = new UnspentOutput(utxo); this.addInput(new MultiSigScriptHashInput({ output: new Output({ script: utxo.script, diff --git a/lib/utxo.js b/lib/transaction/unspentoutput.js similarity index 71% rename from lib/utxo.js rename to lib/transaction/unspentoutput.js index f77ddf3..3eb7af8 100644 --- a/lib/utxo.js +++ b/lib/transaction/unspentoutput.js @@ -1,18 +1,18 @@ 'use strict'; var _ = require('lodash'); -var $ = require('./util/preconditions'); -var JSUtil = require('./util/js'); +var $ = require('../util/preconditions'); +var JSUtil = require('../util/js'); -var Script = require('./script'); -var Address = require('./address'); -var Unit = require('./unit'); +var Script = require('../script'); +var Address = require('../address'); +var Unit = require('../unit'); -function UTXO(data) { +function UnspentOutput(data) { /* jshint maxcomplexity: 20 */ /* jshint maxstatements: 20 */ - if (!(this instanceof UTXO)) { - return new UTXO(data); + if (!(this instanceof UnspentOutput)) { + return new UnspentOutput(data); } $.checkArgument(_.isObject(data), 'Must provide an object from where to extract data'); var address = data.address ? new Address(data.address) : undefined; @@ -39,27 +39,27 @@ function UTXO(data) { }); } -UTXO.prototype.inspect = function() { - return ''; }; -UTXO.prototype.toString = function() { +UnspentOutput.prototype.toString = function() { return this.txId + ':' + this.outputIndex; }; -UTXO.fromJSON = UTXO.fromObject = function(data) { +UnspentOutput.fromJSON = UnspentOutput.fromObject = function(data) { if (JSUtil.isValidJSON(data)) { data = JSON.parse(data); } - return new UTXO(data); + return new UnspentOutput(data); }; -UTXO.prototype.toJSON = function() { +UnspentOutput.prototype.toJSON = function() { return JSON.stringify(this.toObject()); }; -UTXO.prototype.toObject = function() { +UnspentOutput.prototype.toObject = function() { return { address: this.address.toString(), txid: this.txId, @@ -69,4 +69,4 @@ UTXO.prototype.toObject = function() { }; }; -module.exports = UTXO; +module.exports = UnspentOutput; diff --git a/test/utxo.js b/test/utxo.js index a5bd94e..7c178c3 100644 --- a/test/utxo.js +++ b/test/utxo.js @@ -6,9 +6,9 @@ var should = chai.should(); var expect = chai.expect; var bitcore = require('..'); -var UTXO = bitcore.UTXO; +var UnspentOutput = bitcore.Transaction.UnspentOutput; -describe('UTXO', function() { +describe('UnspentOutput', function() { var sampleData1 = { 'address': 'mszYqVnqKoQx4jcTdJXxwKAissE3Jbrrc1', @@ -26,16 +26,16 @@ describe('UTXO', function() { }; it('roundtrip from raw data', function() { - expect(UTXO(sampleData2).toObject()).to.deep.equal(sampleData2); + expect(UnspentOutput(sampleData2).toObject()).to.deep.equal(sampleData2); }); it('can be created without "new" operand', function() { - expect(UTXO(sampleData1) instanceof UTXO).to.equal(true); + expect(UnspentOutput(sampleData1) instanceof UnspentOutput).to.equal(true); }); it('fails if no tx id is provided', function() { expect(function() { - return new UTXO({}); + return new UnspentOutput({}); }).to.throw(); }); @@ -43,28 +43,28 @@ describe('UTXO', function() { var sample = _.cloneDeep(sampleData2); sample.vout = '1'; expect(function() { - return new UTXO(sample); + return new UnspentOutput(sample); }).to.throw(); }); it('displays nicely on the console', function() { - var expected = ''; - expect(new UTXO(sampleData1).inspect()).to.equal(expected); + expect(new UnspentOutput(sampleData1).inspect()).to.equal(expected); }); it('toString returns txid:vout', function() { var expected = 'a477af6b2667c29670467e4e0728b685ee07b240235771862318e29ddbe58458:0'; - expect(new UTXO(sampleData1).toString()).to.equal(expected); + expect(new UnspentOutput(sampleData1).toString()).to.equal(expected); }); it('to/from JSON roundtrip', function() { - var utxo = new UTXO(sampleData2); + var utxo = new UnspentOutput(sampleData2); expect( JSON.parse( - UTXO.fromJSON( - UTXO.fromObject( - UTXO.fromJSON( + UnspentOutput.fromJSON( + UnspentOutput.fromObject( + UnspentOutput.fromJSON( utxo.toJSON() ).toObject() ).toJSON() From 7cf826249bb8be306ab00e67a84a76f0d9bf3a77 Mon Sep 17 00:00:00 2001 From: Esteban Ordano Date: Fri, 2 Jan 2015 10:09:46 -0300 Subject: [PATCH 31/60] Add jsdocs and documentation to UnspenOutput and Insight --- docs/guide/block.md | 2 -- docs/guide/index.md | 3 +++ docs/guide/insight.md | 34 ++++++++++++++++++++++++++++ docs/guide/unspentoutput.md | 39 ++++++++++++++++++++++++++++++++ lib/explorers/insight.js | 38 +++++++++++++++++++++++++++++-- lib/transaction/unspentoutput.js | 37 ++++++++++++++++++++++++++++++ 6 files changed, 149 insertions(+), 4 deletions(-) create mode 100644 docs/guide/insight.md create mode 100644 docs/guide/unspentoutput.md diff --git a/docs/guide/block.md b/docs/guide/block.md index 764d8f6..2bcec98 100644 --- a/docs/guide/block.md +++ b/docs/guide/block.md @@ -8,7 +8,6 @@ description: A simple interface to parse and validate a bitcoin blocks. A Block instance represents the information of a block in the bitcoin network. Given a hexadecimal string representation of the serialization of a block with its transactions, you can instantiate a Block instance. Methods are provided to calculate and check the merkle root hash (if enough data is provided), but transactions won't necessarily be valid spends, and this class won't validate them. A binary representation as a `Buffer` instance is also valid input for a Block's constructor. ```javascript - // instantiate a new block instance var block = new Block(hexaEncodedBlock); @@ -30,7 +29,6 @@ For detailed technical information about a block please visit [Blocks](https://e Each instance of Block has a BlockHeader *(which can be instantiated separately)*. The header has validation methods, to verify that the block. ```javascript - // will verify that the nonce demonstrates enough proof of work assert(block.header.validProofOfWork()); diff --git a/docs/guide/index.md b/docs/guide/index.md index 838f567..ee6ae57 100644 --- a/docs/guide/index.md +++ b/docs/guide/index.md @@ -30,6 +30,9 @@ To get started, just `npm install bitcore` or `bower install bitcore`. * [Managing a pool of peers](pool.md) * [Connecting to a bitcoind instance through JSON-RPC](jsonrpc.md) +## Blockchain Explorers +* [Insight](insight.md) + ## Extra * [Crypto](crypto.md) * [Encoding](encoding.md) diff --git a/docs/guide/insight.md b/docs/guide/insight.md new file mode 100644 index 0000000..fdefb03 --- /dev/null +++ b/docs/guide/insight.md @@ -0,0 +1,34 @@ +title: Insight Explorer +description: Allows users to fetch information about the state of the blockchain from a trusted Insight server. +--- +# Insight + +## Description + +`bitcore.explorers.Insight` is a simple agent to perform queries to the blockchain. There are currently two methods (the API will grow as features are requested): `getUnspentUtxos` and `broadcast`. + +### Retrieving Unspent UTXOs for an Address (or set of) + +```javascript +var insight = new Insight(); +insight.getUnspentUtxos('1Bitcoin...', function(err, utxos) { + if (err) { + // Handle errors... + } else { + // Maybe use the UTXOs to create a transaction + } +}); +``` + +### Broadcasting a Transaction + +```javascript +var insight = new Insight(); +insight.broadcast(tx, function(err, returnedTxId) { + if (err) { + // Handle errors... + } else { + // Mark the transaction as broadcasted + } +}); +``` diff --git a/docs/guide/unspentoutput.md b/docs/guide/unspentoutput.md new file mode 100644 index 0000000..dce425e --- /dev/null +++ b/docs/guide/unspentoutput.md @@ -0,0 +1,39 @@ +title: UnspentOutput +description: A stateless model to represent an unspent output and associated information. +--- +# UnspentOutput + +## Description + +`bitcore.Transaction.UnspentOutput` is a class with stateless instances that provides information about an unspent output: +- Transaction ID and output index +- The "scriptPubKey", the script included in the output +- Amount of satoshis associated +- Address, if available + +## Parameters + +The constructor is quite permissive with the input arguments. It can take outputs straight out of bitcoind's getunspent RPC call. Some of the names are not very informative for new users, so the UnspentOutput constructor also understands these aliases: +- `scriptPubKey`: just `script` is also accepted +- `amount`: expected value is in BTC, but also `satoshis` is accepted +- `vout`: this is the index of the output in the transaction, renamed to `outputIndex` +- `txid`: `txId` + +## Example + +```javascript +var utxo = new UnspentOutput({ + "txid" : "a0a08e397203df68392ee95b3f08b0b3b3e2401410a38d46ae0874f74846f2e9", + "vout" : 0, + "address" : "mgJT8iegL4f9NCgQFeFyfvnSw1Yj4M5Woi", + "scriptPubKey" : "76a914089acaba6af8b2b4fb4bed3b747ab1e4e60b496588ac", + "amount" : 0.00070000 +}); +var utxo = new UnspentOutput({ + "txId" : "a0a08e397203df68392ee95b3f08b0b3b3e2401410a38d46ae0874f74846f2e9", + "outputIndex" : 0, + "address" : "mgJT8iegL4f9NCgQFeFyfvnSw1Yj4M5Woi", + "script" : "76a914089acaba6af8b2b4fb4bed3b747ab1e4e60b496588ac", + "satoshis" : 70000 +}); +``` diff --git a/lib/explorers/insight.js b/lib/explorers/insight.js index 87da50f..1a21d11 100644 --- a/lib/explorers/insight.js +++ b/lib/explorers/insight.js @@ -10,8 +10,13 @@ var UnspentOutput = Transaction.UnspentOutput; var request = require('request'); -// var insight = new Insight(Networks.livenet); - +/** + * Allows the retrieval of information regarding the state of the blockchain + * (and broadcasting of transactions) from/to a trusted Insight server. + * @param {string=} url the url of the Insight server + * @param {Network=} network whether to use livenet or testnet + * @constructor + */ function Insight(url, network) { if (!url && !network) { return new Insight(Networks.defaultNetwork); @@ -31,6 +36,17 @@ function Insight(url, network) { return this; } +/** + * @callback Insight.GetUnspentUtxosCallback + * @param {Error} err + * @param {Array.UnspentOutput} utxos + */ + +/** + * Retrieve a list of unspent outputs associated with an address or set of addresses + * @param {Address|string|Array.Address|Array.string} addresses + * @param {GetUnspentUtxosCallback} callback + */ Insight.prototype.getUnspentUtxos = function(addresses, callback) { $.checkArgument(_.isFunction(callback)); if (!_.isArray(addresses)) { @@ -50,6 +66,17 @@ Insight.prototype.getUnspentUtxos = function(addresses, callback) { }); }; +/** + * @callback Insight.BroadcastCallback + * @param {Error} err + * @param {string} txid + */ + +/** + * Broadcast a transaction to the bitcoin network + * @param {transaction|string} transaction + * @param {BroadcastCallback} callback + */ Insight.prototype.broadcast = function(transaction, callback) { $.checkArgument(JSUtil.isHexa(transaction) || transaction instanceof Transaction); $.checkArgument(_.isFunction(callback)); @@ -67,6 +94,13 @@ Insight.prototype.broadcast = function(transaction, callback) { }); }; +/** + * Internal function to make a post request to the server + * @param {string} path + * @param {?} data + * @param {function} callback + * @private + */ Insight.prototype.requestPost = function(path, data, callback) { $.checkArgument(_.isString(path)); $.checkArgument(_.isFunction(callback)); diff --git a/lib/transaction/unspentoutput.js b/lib/transaction/unspentoutput.js index 3eb7af8..f8aee61 100644 --- a/lib/transaction/unspentoutput.js +++ b/lib/transaction/unspentoutput.js @@ -8,6 +8,22 @@ var Script = require('../script'); var Address = require('../address'); var Unit = require('../unit'); +/** + * Represents an unspent output information: its script, associated amount and address, + * transaction id and output index. + * + * @constructor + * @param {object} data + * @param {string} data.txid the previous transaction id + * @param {string=} data.txId alias for `txid` + * @param {number} data.vout the index in the transaction + * @param {number=} data.outputIndex alias for `vout` + * @param {string|Script} data.scriptPubKey the script that must be resolved to release the funds + * @param {string|Script=} data.script alias for `scriptPubKey` + * @param {number} data.amount amount of bitcoins associated + * @param {number=} data.satoshis alias for `amount`, but expressed in satoshis (1 BTC = 1e8 satoshis) + * @param {string|Address=} data.address the associated address to the script, if provided + */ function UnspentOutput(data) { /* jshint maxcomplexity: 20 */ /* jshint maxstatements: 20 */ @@ -39,15 +55,28 @@ function UnspentOutput(data) { }); } +/** + * Provide an informative output when displaying this object in the console + * @returns string + */ UnspentOutput.prototype.inspect = function() { return ''; }; +/** + * String representation: just "txid:index" + * @returns string + */ UnspentOutput.prototype.toString = function() { return this.txId + ':' + this.outputIndex; }; +/** + * Deserialize an UnspentOutput from an object or JSON string + * @param {object|string} data + * @return UnspentOutput + */ UnspentOutput.fromJSON = UnspentOutput.fromObject = function(data) { if (JSUtil.isValidJSON(data)) { data = JSON.parse(data); @@ -55,10 +84,18 @@ UnspentOutput.fromJSON = UnspentOutput.fromObject = function(data) { return new UnspentOutput(data); }; +/** + * Retrieve a string representation of this object + * @return {string} + */ UnspentOutput.prototype.toJSON = function() { return JSON.stringify(this.toObject()); }; +/** + * Returns a plain object (no prototype or methods) with the associated infor for this output + * @return {object} + */ UnspentOutput.prototype.toObject = function() { return { address: this.address.toString(), From 7fe17e1699bb849941aac6dc0ec51b531685fa01 Mon Sep 17 00:00:00 2001 From: Esteban Ordano Date: Fri, 2 Jan 2015 16:09:50 -0300 Subject: [PATCH 32/60] Move explorers inside transport --- docs/guide/insight.md | 2 +- index.js | 8 ++------ lib/transport/explorers/index.js | 3 +++ lib/{ => transport}/explorers/insight.js | 11 ++++++----- lib/transport/index.js | 1 + test/{ => transport}/explorers/insight.js | 6 +++--- 6 files changed, 16 insertions(+), 15 deletions(-) create mode 100644 lib/transport/explorers/index.js rename lib/{ => transport}/explorers/insight.js (93%) rename test/{ => transport}/explorers/insight.js (96%) diff --git a/docs/guide/insight.md b/docs/guide/insight.md index fdefb03..da12e7e 100644 --- a/docs/guide/insight.md +++ b/docs/guide/insight.md @@ -5,7 +5,7 @@ description: Allows users to fetch information about the state of the blockchain ## Description -`bitcore.explorers.Insight` is a simple agent to perform queries to the blockchain. There are currently two methods (the API will grow as features are requested): `getUnspentUtxos` and `broadcast`. +`bitcore.transport.explorers.Insight` is a simple agent to perform queries to the blockchain. There are currently two methods (the API will grow as features are requested): `getUnspentUtxos` and `broadcast`. ### Retrieving Unspent UTXOs for an Address (or set of) diff --git a/index.js b/index.js index aeb1df0..e72952c 100644 --- a/index.js +++ b/index.js @@ -24,9 +24,6 @@ bitcore.util.buffer = require('./lib/util/buffer'); bitcore.util.js = require('./lib/util/js'); bitcore.util.preconditions = require('./lib/util/preconditions'); -// transport -bitcore.transport = require('./lib/transport'); - // errors thrown by the library bitcore.errors = require('./lib/errors'); @@ -53,9 +50,8 @@ bitcore.deps.bs58 = require('bs58'); bitcore.deps.Buffer = Buffer; bitcore.deps.elliptic = require('elliptic'); -// blockchain explorers -bitcore.explorers = {}; -bitcore.explorers.Insight = require('./lib/explorers/insight'); +// transport +bitcore.transport = require('./lib/transport'); // Internal usage, exposed for testing/advanced tweaking bitcore._HDKeyCache = require('./lib/hdkeycache'); diff --git a/lib/transport/explorers/index.js b/lib/transport/explorers/index.js new file mode 100644 index 0000000..2483955 --- /dev/null +++ b/lib/transport/explorers/index.js @@ -0,0 +1,3 @@ +module.exports = { + Insight: require('./insight') +}; diff --git a/lib/explorers/insight.js b/lib/transport/explorers/insight.js similarity index 93% rename from lib/explorers/insight.js rename to lib/transport/explorers/insight.js index 1a21d11..71a41c5 100644 --- a/lib/explorers/insight.js +++ b/lib/transport/explorers/insight.js @@ -1,11 +1,12 @@ 'use strict'; -var Networks = require('../networks'); -var JSUtil = require('../util/js'); -var $ = require('../util/preconditions'); +var $ = require('../../util/preconditions'); var _ = require('lodash'); -var Address = require('../address'); -var Transaction = require('../transaction'); + +var Address = require('../../address'); +var JSUtil = require('../../util/js'); +var Networks = require('../../networks'); +var Transaction = require('../../transaction'); var UnspentOutput = Transaction.UnspentOutput; var request = require('request'); diff --git a/lib/transport/index.js b/lib/transport/index.js index f219f81..185732e 100644 --- a/lib/transport/index.js +++ b/lib/transport/index.js @@ -2,6 +2,7 @@ * @namespace Transport */ module.exports = { + explorers: require('./explorers'), Messages: require('./messages'), Peer: require('./peer'), Pool: require('./pool'), diff --git a/test/explorers/insight.js b/test/transport/explorers/insight.js similarity index 96% rename from test/explorers/insight.js rename to test/transport/explorers/insight.js index 41b6745..5c04780 100644 --- a/test/explorers/insight.js +++ b/test/transport/explorers/insight.js @@ -3,9 +3,9 @@ var sinon = require('sinon'); var should = require('chai').should(); var expect = require('chai').expect; -var bitcore = require('../..'); +var bitcore = require('../../..'); -var Insight = bitcore.explorers.Insight; +var Insight = bitcore.transport.explorers.Insight; var Address = bitcore.Address; var Transaction = bitcore.Transaction; var Networks = bitcore.Networks; @@ -74,7 +74,7 @@ describe('Insight', function() { describe('broadcasting a transaction', function() { var insight = new Insight(); - var tx = require('../data/tx_creation.json')[0][7]; + var tx = require('../../data/tx_creation.json')[0][7]; beforeEach(function() { insight.requestPost = sinon.stub(); insight.requestPost.onFirstCall().callsArgWith(2, null, {statusCode: 200}); From 2aa5c65945f80d0400624e218fff3a99d7bf04cc Mon Sep 17 00:00:00 2001 From: Yemel Jardi Date: Fri, 2 Jan 2015 16:46:37 -0300 Subject: [PATCH 33/60] Add validations to derivation path --- lib/hdprivatekey.js | 24 ++++++++++++++++++++++++ lib/hdpublickey.js | 39 ++++++++++++++++++++++++++++++--------- test/hdprivatekey.js | 18 ++++++++++++++++++ test/hdpublickey.js | 20 +++++++++++++++++++- 4 files changed, 91 insertions(+), 10 deletions(-) diff --git a/lib/hdprivatekey.js b/lib/hdprivatekey.js index ad53e1c..8d2d222 100644 --- a/lib/hdprivatekey.js +++ b/lib/hdprivatekey.js @@ -62,6 +62,22 @@ function HDPrivateKey(arg) { } } +/** + * Verifies that a given path is valid. + * + * @param {string|number} arg + * @param {boolean?} hardened + * @return {boolean} + */ +HDPrivateKey.prototype.isValidPath = function(arg, hardened) { + try { + this.derive(arg, hardened); + return true; + } catch (err) { + return false; + } +}; + /** * Get a derivated child based on a string or number. * @@ -101,9 +117,15 @@ HDPrivateKey.prototype._deriveWithNumber = function(index, hardened) { if (index >= HDPrivateKey.Hardened) { hardened = true; } + if (index < HDPrivateKey.Hardened && hardened) { index += HDPrivateKey.Hardened; } + + if (index < 0 || index >= HDPrivateKey.MaxIndex) { + throw new hdErrors.InvalidPath(index); + } + var cached = HDKeyCache.get(this.xprivkey, index, hardened); if (cached) { return cached; @@ -443,6 +465,8 @@ HDPrivateKey.DefaultFingerprint = 0; HDPrivateKey.DefaultChildIndex = 0; HDPrivateKey.DefaultNetwork = Network.livenet; HDPrivateKey.Hardened = 0x80000000; +HDPrivateKey.MaxIndex = 2 * HDPrivateKey.Hardened; + HDPrivateKey.RootElementAlias = ['m', 'M', 'm\'', 'M\'']; HDPrivateKey.VersionSize = 4; diff --git a/lib/hdpublickey.js b/lib/hdpublickey.js index 158aa05..979faf9 100644 --- a/lib/hdpublickey.js +++ b/lib/hdpublickey.js @@ -65,6 +65,22 @@ function HDPublicKey(arg) { } } + +/** + * Verifies that a given path is valid. + * + * @param {string|number} arg + * @return {boolean} + */ +HDPublicKey.prototype.isValidPath = function(arg) { + try { + this.derive(arg); + return true; + } catch (err) { + return false; + } +}; + /** * Get a derivated child based on a string or number. * @@ -86,11 +102,10 @@ function HDPublicKey(arg) { * ``` * * @param {string|number} arg - * @param {boolean?} hardened */ -HDPublicKey.prototype.derive = function (arg, hardened) { +HDPublicKey.prototype.derive = function (arg) { if (_.isNumber(arg)) { - return this._deriveWithNumber(arg, hardened); + return this._deriveWithNumber(arg); } else if (_.isString(arg)) { return this._deriveFromString(arg); } else { @@ -98,11 +113,14 @@ HDPublicKey.prototype.derive = function (arg, hardened) { } }; -HDPublicKey.prototype._deriveWithNumber = function (index, hardened) { - if (hardened || index >= HDPublicKey.Hardened) { +HDPublicKey.prototype._deriveWithNumber = function (index) { + if (index >= HDPublicKey.Hardened) { throw new hdErrors.InvalidIndexCantDeriveHardened(); } - var cached = HDKeyCache.get(this.xpubkey, index, hardened); + if (index < 0) { + throw new hdErrors.InvalidPath(index); + } + var cached = HDKeyCache.get(this.xpubkey, index, false); if (cached) { return cached; } @@ -123,12 +141,16 @@ HDPublicKey.prototype._deriveWithNumber = function (index, hardened) { chainCode: chainCode, publicKey: publicKey }); - HDKeyCache.set(this.xpubkey, index, hardened, derived); + HDKeyCache.set(this.xpubkey, index, false, derived); return derived; }; HDPublicKey.prototype._deriveFromString = function (path) { /* jshint maxcomplexity: 8 */ + if (_.contains(path, "'")) { + throw new hdErrors.InvalidIndexCantDeriveHardened(); + } + var steps = path.split('/'); // Special cases: @@ -143,8 +165,7 @@ HDPublicKey.prototype._deriveFromString = function (path) { var result = this; for (var step in steps) { var index = parseInt(steps[step]); - var hardened = steps[step] !== index.toString(); - result = result._deriveWithNumber(index, hardened); + result = result._deriveWithNumber(index); } return result; }; diff --git a/test/hdprivatekey.js b/test/hdprivatekey.js index 6ba3b4d..aec7563 100644 --- a/test/hdprivatekey.js +++ b/test/hdprivatekey.js @@ -190,6 +190,24 @@ describe('HDPrivate key interface', function() { derivedByNumber.xprivkey.should.equal(derivedByString.xprivkey); }); + it('validates correct paths', function() { + var privateKey = new HDPrivateKey(xprivkey); + var valid = privateKey.isValidPath('m/0\'/1/2\''); + valid.should.equal(true); + + var valid = privateKey.isValidPath(123, true); + valid.should.equal(true); + }); + + it('validates illigal paths', function() { + var privateKey = new HDPrivateKey(xprivkey); + var valid = privateKey.isValidPath('m/-1/12'); + valid.should.equal(false); + + var valid = privateKey.isValidPath(HDPrivateKey.MaxHardened); + valid.should.equal(false); + }); + describe('conversion to plain object/json', function() { var plainObject = { 'network':'livenet', diff --git a/test/hdpublickey.js b/test/hdpublickey.js index 721fca8..be25b7b 100644 --- a/test/hdpublickey.js +++ b/test/hdpublickey.js @@ -216,10 +216,28 @@ describe('HDPublicKey interface', function() { it('can\'t derive hardened keys', function() { expectFail(function() { - return new HDPublicKey(xpubkey).derive(HDPublicKey.Hardened + 1); + return new HDPublicKey(xpubkey).derive(HDPublicKey.Hardened); }, hdErrors.InvalidDerivationArgument); }); + it('validates correct paths', function() { + var publicKey = new HDPublicKey(xpubkey); + var valid = publicKey.isValidPath('m/123/12'); + valid.should.equal(true); + + var valid = publicKey.isValidPath(123, true); + valid.should.equal(true); + }); + + it('validates illigal paths', function() { + var publicKey = new HDPublicKey(xpubkey); + var valid = publicKey.isValidPath('m/-1/12'); + valid.should.equal(false); + + var valid = publicKey.isValidPath(HDPublicKey.Hardened); + valid.should.equal(false); + }); + it('should use the cache', function() { var pubkey = new HDPublicKey(xpubkey); var derived1 = pubkey.derive(0); From f56ddb2a0a840bef1c9d609703ec355f83b8436d Mon Sep 17 00:00:00 2001 From: Esteban Ordano Date: Fri, 2 Jan 2015 16:50:14 -0300 Subject: [PATCH 34/60] Rename properly utxo to unspentoutput --- test/{utxo.js => transaction/unspentoutput.js} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename test/{utxo.js => transaction/unspentoutput.js} (98%) diff --git a/test/utxo.js b/test/transaction/unspentoutput.js similarity index 98% rename from test/utxo.js rename to test/transaction/unspentoutput.js index 7c178c3..fde2881 100644 --- a/test/utxo.js +++ b/test/transaction/unspentoutput.js @@ -5,7 +5,7 @@ var chai = require('chai'); var should = chai.should(); var expect = chai.expect; -var bitcore = require('..'); +var bitcore = require('../..'); var UnspentOutput = bitcore.Transaction.UnspentOutput; describe('UnspentOutput', function() { From 2284eec829cfa7933abe0924806cea0160ee9a03 Mon Sep 17 00:00:00 2001 From: Esteban Ordano Date: Fri, 2 Jan 2015 17:03:52 -0300 Subject: [PATCH 35/60] Fix comments on the Insight and UTXO docs --- docs/guide/index.md | 4 +--- docs/guide/insight.md | 4 ++-- docs/guide/unspentoutput.md | 2 +- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/docs/guide/index.md b/docs/guide/index.md index ee6ae57..0f2cc56 100644 --- a/docs/guide/index.md +++ b/docs/guide/index.md @@ -29,9 +29,7 @@ To get started, just `npm install bitcore` or `bower install bitcore`. * [Interface to the Bitcoin P2P network](peer.md) * [Managing a pool of peers](pool.md) * [Connecting to a bitcoind instance through JSON-RPC](jsonrpc.md) - -## Blockchain Explorers -* [Insight](insight.md) +* [Connecting to a Insight instance to retrieve informetion](insight.md) ## Extra * [Crypto](crypto.md) diff --git a/docs/guide/insight.md b/docs/guide/insight.md index da12e7e..6f11eb2 100644 --- a/docs/guide/insight.md +++ b/docs/guide/insight.md @@ -1,11 +1,11 @@ title: Insight Explorer -description: Allows users to fetch information about the state of the blockchain from a trusted Insight server. +description: Provides an interface to fetch information about the state of the blockchain from a trusted Insight server. --- # Insight ## Description -`bitcore.transport.explorers.Insight` is a simple agent to perform queries to the blockchain. There are currently two methods (the API will grow as features are requested): `getUnspentUtxos` and `broadcast`. +`bitcore.transport.explorers.Insight` is a simple agent to perform queries to the blockchain. There are currently two methods (the API will grow as features are requested): `getUnspentUtxos` and `broadcast`. The default servers are `https://insight.bitpay.com` and `https://test-insight.bitpay.com`, hosted by BitPay Inc. ### Retrieving Unspent UTXOs for an Address (or set of) diff --git a/docs/guide/unspentoutput.md b/docs/guide/unspentoutput.md index dce425e..20b9b05 100644 --- a/docs/guide/unspentoutput.md +++ b/docs/guide/unspentoutput.md @@ -15,7 +15,7 @@ description: A stateless model to represent an unspent output and associated inf The constructor is quite permissive with the input arguments. It can take outputs straight out of bitcoind's getunspent RPC call. Some of the names are not very informative for new users, so the UnspentOutput constructor also understands these aliases: - `scriptPubKey`: just `script` is also accepted -- `amount`: expected value is in BTC, but also `satoshis` is accepted +- `amount`: expected value in BTC. If the `satoshis` alias is used, make sure to use satoshis instead of BTC. - `vout`: this is the index of the output in the transaction, renamed to `outputIndex` - `txid`: `txId` From 48cea84435ae2b41d25a752dcd7986382d5daf96 Mon Sep 17 00:00:00 2001 From: Esteban Ordano Date: Fri, 2 Jan 2015 17:35:29 -0300 Subject: [PATCH 36/60] Fix links to insight --- docs/guide/insight.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/guide/insight.md b/docs/guide/insight.md index 6f11eb2..9892846 100644 --- a/docs/guide/insight.md +++ b/docs/guide/insight.md @@ -5,7 +5,9 @@ description: Provides an interface to fetch information about the state of the b ## Description -`bitcore.transport.explorers.Insight` is a simple agent to perform queries to the blockchain. There are currently two methods (the API will grow as features are requested): `getUnspentUtxos` and `broadcast`. The default servers are `https://insight.bitpay.com` and `https://test-insight.bitpay.com`, hosted by BitPay Inc. +`bitcore.transport.explorers.Insight` is a simple agent to perform queries to an Insight blockchain explorer. The default servers are `https://insight.bitpay.com` and `https://test-insight.bitpay.com`, hosted by BitPay Inc. You can (and we strongly suggest you do) run your own insight server. For more information, head to https://github.com/bitpay/insight-api + +There are currently two methods implemented (the API will grow as features are requested): `getUnspentUtxos` and `broadcast`. ### Retrieving Unspent UTXOs for an Address (or set of) From 98cfd646e7a96adcbd8b0fb0bad26aac240087d1 Mon Sep 17 00:00:00 2001 From: Yemel Jardi Date: Fri, 2 Jan 2015 18:01:21 -0300 Subject: [PATCH 37/60] Allow creating a new random testnet HDPrivate key --- lib/hdprivatekey.js | 38 ++++++++++++++++++-------------------- test/hdprivatekey.js | 6 ++++++ 2 files changed, 24 insertions(+), 20 deletions(-) diff --git a/lib/hdprivatekey.js b/lib/hdprivatekey.js index ad53e1c..c3f85a5 100644 --- a/lib/hdprivatekey.js +++ b/lib/hdprivatekey.js @@ -41,25 +41,25 @@ function HDPrivateKey(arg) { if (!(this instanceof HDPrivateKey)) { return new HDPrivateKey(arg); } - if (arg) { - if (_.isString(arg) || BufferUtil.isBuffer(arg)) { - if (HDPrivateKey.isValidSerialized(arg)) { - this._buildFromSerialized(arg); - } else if (JSUtil.isValidJSON(arg)) { - this._buildFromJSON(arg); - } else { - throw HDPrivateKey.getSerializedError(arg); - } - } else { - if (_.isObject(arg)) { - this._buildFromObject(arg); - } else { - throw new hdErrors.UnrecognizedArgument(arg); - } - } - } else { + if (!arg) { return this._generateRandomly(); } + + if (Network.get(arg)) { + return this._generateRandomly(arg); + } else if (_.isString(arg) || BufferUtil.isBuffer(arg)) { + if (HDPrivateKey.isValidSerialized(arg)) { + this._buildFromSerialized(arg); + } else if (JSUtil.isValidJSON(arg)) { + this._buildFromJSON(arg); + } else { + throw HDPrivateKey.getSerializedError(arg); + } + } else if (_.isObject(arg)) { + this._buildFromObject(arg); + } else { + throw new hdErrors.UnrecognizedArgument(arg); + } } /** @@ -266,7 +266,6 @@ HDPrivateKey.prototype._generateRandomly = function(network) { */ HDPrivateKey.fromSeed = function(hexa, network) { /* jshint maxcomplexity: 8 */ - if (JSUtil.isHexaString(hexa)) { hexa = BufferUtil.hexToBuffer(hexa); } @@ -282,7 +281,7 @@ HDPrivateKey.fromSeed = function(hexa, network) { var hash = Hash.sha512hmac(hexa, new buffer.Buffer('Bitcoin seed')); return new HDPrivateKey({ - network: Network.get(network) || Network.livenet, + network: Network.get(network) || Network.defaultNetwork, depth: 0, parentFingerPrint: 0, childIndex: 0, @@ -441,7 +440,6 @@ HDPrivateKey.prototype.toJSON = function toJSON() { HDPrivateKey.DefaultDepth = 0; HDPrivateKey.DefaultFingerprint = 0; HDPrivateKey.DefaultChildIndex = 0; -HDPrivateKey.DefaultNetwork = Network.livenet; HDPrivateKey.Hardened = 0x80000000; HDPrivateKey.RootElementAlias = ['m', 'M', 'm\'', 'M\'']; diff --git a/test/hdprivatekey.js b/test/hdprivatekey.js index 6ba3b4d..d8779c7 100644 --- a/test/hdprivatekey.js +++ b/test/hdprivatekey.js @@ -50,6 +50,12 @@ describe('HDPrivate key interface', function() { should.exist(new HDPrivateKey().xprivkey); }); + it('should make a new private key from random for testnet', function() { + var key = new HDPrivateKey('testnet'); + should.exist(key.xprivkey); + key.network.name.should.equal('testnet'); + }); + it('should not be able to change read-only properties', function() { var hdkey = new HDPrivateKey(); expect(function() { From bb3064d336cbe8d429b01e187443dbd17756ffbd Mon Sep 17 00:00:00 2001 From: Yemel Jardi Date: Fri, 2 Jan 2015 18:03:40 -0300 Subject: [PATCH 38/60] fix typo --- test/hdprivatekey.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/hdprivatekey.js b/test/hdprivatekey.js index aec7563..4e7688b 100644 --- a/test/hdprivatekey.js +++ b/test/hdprivatekey.js @@ -199,7 +199,7 @@ describe('HDPrivate key interface', function() { valid.should.equal(true); }); - it('validates illigal paths', function() { + it('validates illegal paths', function() { var privateKey = new HDPrivateKey(xprivkey); var valid = privateKey.isValidPath('m/-1/12'); valid.should.equal(false); From ff844e9935d36e6f9491ad164549ed5a52677732 Mon Sep 17 00:00:00 2001 From: Yemel Jardi Date: Fri, 2 Jan 2015 18:15:17 -0300 Subject: [PATCH 39/60] Change PrivateKey.toString from WIF to hex format --- lib/privatekey.js | 11 ++++++++++- lib/publickey.js | 3 +-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/lib/privatekey.js b/lib/privatekey.js index 794860c..15e0eaa 100644 --- a/lib/privatekey.js +++ b/lib/privatekey.js @@ -283,12 +283,21 @@ PrivateKey.isValid = function(data, network){ return !PrivateKey.getValidationError(data, network); }; +/** + * Will output the PublicKey encoded as hex string + * + * @returns {String} + */ +PrivateKey.prototype.toString = function() { + return this.toBuffer().toString('hex'); +} + /** * Will output the PrivateKey to a WIF string * * @returns {String} A WIP representation of the private key */ -PrivateKey.prototype.toString = PrivateKey.prototype.toWIF = function() { +PrivateKey.prototype.toWIF = function() { var network = this.network; var compressed = this.compressed; diff --git a/lib/publickey.js b/lib/publickey.js index 182b7aa..3ce6f8e 100644 --- a/lib/publickey.js +++ b/lib/publickey.js @@ -409,8 +409,7 @@ PublicKey.prototype.toAddress = function(network) { * @returns {String} A DER hex encoded string */ PublicKey.prototype.toString = function() { - var compressed = _.isUndefined(this.compressed) || this.compressed; - return this.toDER(compressed).toString('hex'); + return this.toDER().toString('hex'); }; /** From 9f8e2c721fbf176313c2596d4af31f3c2a64012a Mon Sep 17 00:00:00 2001 From: Yemel Jardi Date: Fri, 2 Jan 2015 18:25:08 -0300 Subject: [PATCH 40/60] fix typo --- lib/privatekey.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/privatekey.js b/lib/privatekey.js index 15e0eaa..ac49996 100644 --- a/lib/privatekey.js +++ b/lib/privatekey.js @@ -284,7 +284,7 @@ PrivateKey.isValid = function(data, network){ }; /** - * Will output the PublicKey encoded as hex string + * Will output the PrivateKey encoded as hex string * * @returns {String} */ From 5e292490e8a9d4782664ca430c3a7d9e579acfdf Mon Sep 17 00:00:00 2001 From: Yemel Jardi Date: Fri, 2 Jan 2015 18:25:23 -0300 Subject: [PATCH 41/60] update tests --- test/privatekey.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/test/privatekey.js b/test/privatekey.js index 7cae44b..cdb26d4 100644 --- a/test/privatekey.js +++ b/test/privatekey.js @@ -135,7 +135,7 @@ describe('PrivateKey', function() { it('should create a livenet private key', function() { var privkey = new PrivateKey(BN.fromBuffer(buf), 'livenet'); - privkey.toString().should.equal(wifLivenet); + privkey.toWIF().should.equal(wifLivenet); }); it('should create a default network private key', function() { @@ -197,7 +197,7 @@ describe('PrivateKey', function() { it('should output this address correctly', function() { var privkey = PrivateKey.fromWIF(wifLivenetUncompressed); - privkey.toString().should.equal(wifLivenetUncompressed); + privkey.toWIF().should.equal(wifLivenetUncompressed); }); }); @@ -221,20 +221,20 @@ describe('PrivateKey', function() { it('should output known livenet address for console', function() { var privkey = PrivateKey.fromWIF('L3T1s1TYP9oyhHpXgkyLoJFGniEgkv2Jhi138d7R2yJ9F4QdDU2m'); privkey.inspect().should.equal( - '' + '' ); }); it('should output known testnet address for console', function() { var privkey = PrivateKey.fromWIF('cR4qogdN9UxLZJXCNFNwDRRZNeLRWuds9TTSuLNweFVjiaE4gPaq'); privkey.inspect().should.equal( - '' + '' ); }); it('outputs "uncompressed" for uncompressed imported WIFs', function() { var privkey = PrivateKey.fromWIF(wifLivenetUncompressed); - privkey.inspect().should.equal(''); + privkey.inspect().should.equal(''); }); }); @@ -261,7 +261,7 @@ describe('PrivateKey', function() { describe('#toBuffer', function() { it('should output known buffer', function() { var privkey = new PrivateKey(BN.fromBuffer(buf), 'livenet'); - privkey.toBuffer().toString('hex').should.equal(buf.toString('hex')); + privkey.toString().should.equal(buf.toString('hex')); }); }); @@ -316,7 +316,7 @@ describe('PrivateKey', function() { it('should parse this uncompressed livenet address correctly', function() { var privkey = PrivateKey.fromString(wifLivenetUncompressed); - privkey.toString().should.equal(wifLivenetUncompressed); + privkey.toString().should.equal("96c132224121b509b7d0a16245e957d9192609c5637c6228311287b1be21627a"); }); }); From 5449ad7bff9c52ba7c30ad5a392b80208c1048b5 Mon Sep 17 00:00:00 2001 From: Braydon Fuller Date: Fri, 2 Jan 2015 19:15:20 -0500 Subject: [PATCH 42/60] Add test case that incorrectly handles hexa string as json --- test/privatekey.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test/privatekey.js b/test/privatekey.js index 7cae44b..3212bd4 100644 --- a/test/privatekey.js +++ b/test/privatekey.js @@ -16,6 +16,7 @@ var invalidbase58 = require('./data/bitcoind/base58_keys_invalid.json'); describe('PrivateKey', function() { var hex = '96c132224121b509b7d0a16245e957d9192609c5637c6228311287b1be21627a'; + var hex2 = '8080808080808080808080808080808080808080808080808080808080808080'; var buf = new Buffer(hex, 'hex'); var wifTestnet = 'cSdkPxkAjA4HDr5VHgsebAPDEh9Gyub4HK8UJr2DFGGqKKy4K5sG'; var wifTestnetUncompressed = '92jJzK4tbURm1C7udQXxeCBvXHoHJstDXRxAMouPG1k1XUaXdsu'; @@ -31,6 +32,12 @@ describe('PrivateKey', function() { should.exist(b.bn); }); + it('should create a privatkey from hexa string', function() { + var a = new PrivateKey(hex2); + should.exist(a); + should.exist(a.bn); + }); + it('should create a new random testnet private key with only one argument', function() { var a = new PrivateKey(Networks.testnet); should.exist(a); From c20a0eabed129a13039040af1997aca384e33177 Mon Sep 17 00:00:00 2001 From: Braydon Fuller Date: Fri, 2 Jan 2015 19:32:49 -0500 Subject: [PATCH 43/60] Check type of parsed JSON to determine it's an object and not a number. --- lib/util/js.js | 8 +++++++- test/privatekey.js | 2 +- test/util/js.js | 29 +++++++++++++++++++++++++++++ 3 files changed, 37 insertions(+), 2 deletions(-) create mode 100644 test/util/js.js diff --git a/lib/util/js.js b/lib/util/js.js index 85c1284..3316062 100644 --- a/lib/util/js.js +++ b/lib/util/js.js @@ -28,9 +28,15 @@ module.exports = { * @return {Object|boolean} false if the argument is not a JSON string. */ isValidJSON: function isValidJSON(arg) { + var parsed; try { - return JSON.parse(arg); + parsed = JSON.parse(arg); } catch (e) { + parsed = false; + } + if (typeof(parsed) === 'object') { + return true; + } else { return false; } }, diff --git a/test/privatekey.js b/test/privatekey.js index 3212bd4..dcaab92 100644 --- a/test/privatekey.js +++ b/test/privatekey.js @@ -32,7 +32,7 @@ describe('PrivateKey', function() { should.exist(b.bn); }); - it('should create a privatkey from hexa string', function() { + it('should create a privatekey from hexa string', function() { var a = new PrivateKey(hex2); should.exist(a); should.exist(a.bn); diff --git a/test/util/js.js b/test/util/js.js new file mode 100644 index 0000000..197ccdf --- /dev/null +++ b/test/util/js.js @@ -0,0 +1,29 @@ +'use strict'; +/* jshint unused: false */ + +var should = require('chai').should(); +var expect = require('chai').expect; + +var bitcore = require('../..'); +var JSUtil = bitcore.util.js; + +describe('js utils', function() { + + describe('isValidJSON', function() { + + var hexa = '8080808080808080808080808080808080808080808080808080808080808080'; + var json = '{"key": ["value", "value2"]}'; + + it('does not mistake an integer as valid json object', function() { + var valid = JSUtil.isValidJSON(hexa); + valid.should.equal(false); + }); + + it('correctly validates a json object', function() { + var valid = JSUtil.isValidJSON(json); + valid.should.equal(true); + }); + + }); + +}); From a1ee393dce56539df8dbe14d11a855e503311dae Mon Sep 17 00:00:00 2001 From: Braydon Fuller Date: Fri, 2 Jan 2015 19:38:22 -0500 Subject: [PATCH 44/60] JSUtil: Return result quicker for isValidJSON and add additional tests. --- lib/util/js.js | 5 ++--- test/util/js.js | 6 ++++++ 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/lib/util/js.js b/lib/util/js.js index 3316062..fffd336 100644 --- a/lib/util/js.js +++ b/lib/util/js.js @@ -32,13 +32,12 @@ module.exports = { try { parsed = JSON.parse(arg); } catch (e) { - parsed = false; + return false; } if (typeof(parsed) === 'object') { return true; - } else { - return false; } + return false; }, isHexa: isHexa, isHexaString: isHexa, diff --git a/test/util/js.js b/test/util/js.js index 197ccdf..b392300 100644 --- a/test/util/js.js +++ b/test/util/js.js @@ -13,6 +13,7 @@ describe('js utils', function() { var hexa = '8080808080808080808080808080808080808080808080808080808080808080'; var json = '{"key": ["value", "value2"]}'; + var json2 = '["value", "value2", {"key": "value"}]'; it('does not mistake an integer as valid json object', function() { var valid = JSUtil.isValidJSON(hexa); @@ -24,6 +25,11 @@ describe('js utils', function() { valid.should.equal(true); }); + it('correctly validates an array json object', function() { + var valid = JSUtil.isValidJSON(json); + valid.should.equal(true); + }); + }); }); From 3de6acc1762a4c8520aa02c8c85add3e7e491388 Mon Sep 17 00:00:00 2001 From: Braydon Fuller Date: Sat, 3 Jan 2015 13:20:51 -0500 Subject: [PATCH 45/60] Add tests that reveals incorrect public key calculation --- test/publickey.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/test/publickey.js b/test/publickey.js index de4011d..2adc78c 100644 --- a/test/publickey.js +++ b/test/publickey.js @@ -44,6 +44,7 @@ describe('PublicKey', function() { }); describe('instantiation', function() { + it('from a private key', function() { var privhex = '906977a061af29276e40bf377042ffbde414e496ae2260bbf1fa9d085637bfff'; var pubhex = '02a1633cafcc01ebfb6d78e39f687a1f0995c62fc95f51ead10a02ee0be551b5dc'; @@ -52,6 +53,18 @@ describe('PublicKey', function() { pk.toString().should.equal(pubhex); }); + it('from a known private key', function() { + var privwif = 'KzsjKq2FVqVuQv2ueHVFuB65A9uEZ6S1L6F8NuokCrE3V3kE3Ack'; + var pubhex = '03d6106302d2698d6a41e9c9a114269e7be7c6a0081317de444bb2980bf9265a01'; + var pubxhex = 'd6106302d2698d6a41e9c9a114269e7be7c6a0081317de444bb2980bf9265a01'; + var pubyhex = 'e05fb262e64b108991a29979809fcef9d3e70cafceb3248c922c17d83d66bc9d'; + var privkey = new PrivateKey(privwif); + var pubkey = privkey.toPublicKey(); + pubkey.point.x.toString('hex').should.equal(pubxhex); + pubkey.point.y.toString('hex').should.equal(pubyhex); + pubkey.toString().should.equal(pubhex); + }); + it('from a compressed public key', function() { var publicKeyHex = '031ff0fe0f7b15ffaa85ff9f4744d539139c252a49710fb053bb9f2b933173ff9a'; var publicKey = new PublicKey(publicKeyHex); From bc4e62ecd4f1aa8e3f72afc3403ae27c9e151bf3 Mon Sep 17 00:00:00 2001 From: Braydon Fuller Date: Sat, 3 Jan 2015 14:03:00 -0500 Subject: [PATCH 46/60] Fixed public key calculation by returning the elliptic ec.curve.g --- lib/crypto/point.js | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/lib/crypto/point.js b/lib/crypto/point.js index f635b3e..85f2431 100644 --- a/lib/crypto/point.js +++ b/lib/crypto/point.js @@ -2,9 +2,8 @@ var BN = require('./bn'); var bufferUtil = require('../util/buffer'); -var ec = require('elliptic').curves.secp256k1; -var ecPoint = ec.curve.point.bind(ec.curve); -var ecPointFromX = ec.curve.pointFromX.bind(ec.curve); +var EC = require('elliptic').ec; +var ec = new EC('secp256k1'); /** * @@ -19,7 +18,7 @@ var ecPointFromX = ec.curve.pointFromX.bind(ec.curve); * @constructor */ var Point = function Point(x, y, isRed) { - var point = ecPoint(x, y, isRed); + var point = ec.curve.point(x, y, isRed); point.validate(); return point; }; @@ -36,7 +35,7 @@ Point.prototype = Object.getPrototypeOf(ec.curve.point()); * @returns {Point} An instance of Point */ Point.fromX = function fromX(odd, x){ - var point = ecPointFromX(odd, x); + var point = ec.curve.pointFromX(odd, x); point.validate(); return point; }; @@ -49,7 +48,7 @@ Point.fromX = function fromX(odd, x){ * @returns {Point} An instance of the base point. */ Point.getG = function getG() { - return Point(ec.curve.g.getX(), ec.curve.g.getY()); + return ec.curve.g; }; /** @@ -106,7 +105,7 @@ Point.prototype.validate = function validate() { throw new Error('Invalid x,y value for curve, cannot equal 0.'); } - var p2 = ecPointFromX(this.getY().isOdd(), this.getX()); + var p2 = ec.curve.pointFromX(this.getY().isOdd(), this.getX()); if (p2.y.cmp(this.y) !== 0) { throw new Error('Invalid y value for curve.'); From d2a2be7a938fa470e9330efbdfcbe32270256700 Mon Sep 17 00:00:00 2001 From: Braydon Fuller Date: Sun, 4 Jan 2015 09:19:08 -0500 Subject: [PATCH 47/60] Upgraded bn.js to fix public key calculation problem, restored crypto/point.js, and added additional tests. --- lib/crypto/point.js | 13 +++++++------ npm-shrinkwrap.json | 12 ++++++------ package.json | 2 +- test/publickey.js | 37 +++++++++++++++++++++++++++---------- 4 files changed, 41 insertions(+), 23 deletions(-) diff --git a/lib/crypto/point.js b/lib/crypto/point.js index 85f2431..f635b3e 100644 --- a/lib/crypto/point.js +++ b/lib/crypto/point.js @@ -2,8 +2,9 @@ var BN = require('./bn'); var bufferUtil = require('../util/buffer'); -var EC = require('elliptic').ec; -var ec = new EC('secp256k1'); +var ec = require('elliptic').curves.secp256k1; +var ecPoint = ec.curve.point.bind(ec.curve); +var ecPointFromX = ec.curve.pointFromX.bind(ec.curve); /** * @@ -18,7 +19,7 @@ var ec = new EC('secp256k1'); * @constructor */ var Point = function Point(x, y, isRed) { - var point = ec.curve.point(x, y, isRed); + var point = ecPoint(x, y, isRed); point.validate(); return point; }; @@ -35,7 +36,7 @@ Point.prototype = Object.getPrototypeOf(ec.curve.point()); * @returns {Point} An instance of Point */ Point.fromX = function fromX(odd, x){ - var point = ec.curve.pointFromX(odd, x); + var point = ecPointFromX(odd, x); point.validate(); return point; }; @@ -48,7 +49,7 @@ Point.fromX = function fromX(odd, x){ * @returns {Point} An instance of the base point. */ Point.getG = function getG() { - return ec.curve.g; + return Point(ec.curve.g.getX(), ec.curve.g.getY()); }; /** @@ -105,7 +106,7 @@ Point.prototype.validate = function validate() { throw new Error('Invalid x,y value for curve, cannot equal 0.'); } - var p2 = ec.curve.pointFromX(this.getY().isOdd(), this.getX()); + var p2 = ecPointFromX(this.getY().isOdd(), this.getX()); if (p2.y.cmp(this.y) !== 0) { throw new Error('Invalid y value for curve.'); diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 02adf12..becad21 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -13,9 +13,9 @@ "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-0.4.1.tgz" }, "bn.js": { - "version": "0.16.0", - "from": "bn.js@0.16.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-0.16.0.tgz" + "version": "0.16.1", + "from": "bn.js@0.16.1", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-0.16.1.tgz" }, "browser-request": { "version": "0.3.3", @@ -43,9 +43,9 @@ "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-0.16.0.tgz", "dependencies": { "bn.js": { - "version": "0.16.0", - "from": "bn.js@0.16.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-0.16.0.tgz" + "version": "0.16.1", + "from": "bn.js@0.16.1", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-0.16.1.tgz" }, "brorand": { "version": "1.0.1", diff --git a/package.json b/package.json index d94b9e8..59cd473 100644 --- a/package.json +++ b/package.json @@ -74,7 +74,7 @@ }, "dependencies": { "asn1.js": "=0.4.1", - "bn.js": "=0.16.0", + "bn.js": "=0.16.1", "browser-request": "^0.3.3", "bs58": "=2.0.0", "bufferput": "^0.1.2", diff --git a/test/publickey.js b/test/publickey.js index 2adc78c..dce9707 100644 --- a/test/publickey.js +++ b/test/publickey.js @@ -53,16 +53,33 @@ describe('PublicKey', function() { pk.toString().should.equal(pubhex); }); - it('from a known private key', function() { - var privwif = 'KzsjKq2FVqVuQv2ueHVFuB65A9uEZ6S1L6F8NuokCrE3V3kE3Ack'; - var pubhex = '03d6106302d2698d6a41e9c9a114269e7be7c6a0081317de444bb2980bf9265a01'; - var pubxhex = 'd6106302d2698d6a41e9c9a114269e7be7c6a0081317de444bb2980bf9265a01'; - var pubyhex = 'e05fb262e64b108991a29979809fcef9d3e70cafceb3248c922c17d83d66bc9d'; - var privkey = new PrivateKey(privwif); - var pubkey = privkey.toPublicKey(); - pubkey.point.x.toString('hex').should.equal(pubxhex); - pubkey.point.y.toString('hex').should.equal(pubyhex); - pubkey.toString().should.equal(pubhex); + it('problematic secp256k1 public keys', function() { + + var knownKeys = [ + { + wif: 'KzsjKq2FVqVuQv2ueHVFuB65A9uEZ6S1L6F8NuokCrE3V3kE3Ack', + priv: '6d1229a6b24c2e775c062870ad26bc261051e0198c67203167273c7c62538846', + pub: '03d6106302d2698d6a41e9c9a114269e7be7c6a0081317de444bb2980bf9265a01', + pubx: 'd6106302d2698d6a41e9c9a114269e7be7c6a0081317de444bb2980bf9265a01', + puby: 'e05fb262e64b108991a29979809fcef9d3e70cafceb3248c922c17d83d66bc9d' + }, + { + wif: 'L5MgSwNB2R76xBGorofRSTuQFd1bm3hQMFVf3u2CneFom8u1Yt7G', + priv: 'f2cc9d2b008927db94b89e04e2f6e70c180e547b3e5e564b06b8215d1c264b53', + pub: '03e275faa35bd1e88f5df6e8f9f6edb93bdf1d65f4915efc79fd7a726ec0c21700', + pubx: 'e275faa35bd1e88f5df6e8f9f6edb93bdf1d65f4915efc79fd7a726ec0c21700', + puby: '367216cb35b086e6686d69dddd822a8f4d52eb82ac5d9de18fdcd9bf44fa7df7' + } + ]; + + for(var i = 0; i < knownKeys.length; i++) { + var privkey = new PrivateKey(knownKeys[i].wif); + var pubkey = privkey.toPublicKey(); + pubkey.toString().should.equal(knownKeys[i].pub); + pubkey.point.x.toString('hex').should.equal(knownKeys[i].pubx); + pubkey.point.y.toString('hex').should.equal(knownKeys[i].puby); + } + }); it('from a compressed public key', function() { From 97b6e8b9897b8a3d3d3d1558e0e7c42679625255 Mon Sep 17 00:00:00 2001 From: "Ryan X. Charles" Date: Sun, 4 Jan 2015 19:37:45 -0800 Subject: [PATCH 48/60] add deterministicK test vectors These vectors were produced by @bip32JP and tested against a similar implementation of the "badrs" approach to RFC 6979 in python-ecdsa as described in this related bitcoinjs-lib PR: https://github.com/bitcoinjs/bitcoinjs-lib/pull/337 --- test/crypto/ecdsa.js | 15 +++++++++ test/data/ecdsa.json | 74 +++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 88 insertions(+), 1 deletion(-) diff --git a/test/crypto/ecdsa.js b/test/crypto/ecdsa.js index bce76cd..5a234ee 100644 --- a/test/crypto/ecdsa.js +++ b/test/crypto/ecdsa.js @@ -296,6 +296,21 @@ describe('ECDSA', function() { ecdsa.sigError().should.equal(obj.exception); }); }); + + vectors.deterministicK.forEach(function(obj, i) { + it('should validate deterministicK vector ' + i, function() { + var hashbuf = Hash.sha256(new Buffer(obj.message)); + var privkey = Privkey(BN.fromBuffer(new Buffer(obj.privkey, 'hex')), 'mainnet'); + var ecdsa = ECDSA({ + privkey: privkey, + hashbuf: hashbuf + }); + ecdsa.deterministicK(0).k.toString('hex').should.equal(obj.k_bad00); + ecdsa.deterministicK(1).k.toString('hex').should.equal(obj.k_bad01); + ecdsa.deterministicK(15).k.toString('hex').should.equal(obj.k_bad15); + }); + }); + }); }); }); diff --git a/test/data/ecdsa.json b/test/data/ecdsa.json index fb2bcea..67ccd37 100644 --- a/test/data/ecdsa.json +++ b/test/data/ecdsa.json @@ -154,5 +154,77 @@ } } ] - } + }, + "deterministicK": [ + { + "message": "test data", + "privkey": "fee0a1f7afebf9d2a5a80c0c98a31c709681cce195cbcd06342b517970c0be1e", + "k_bad00": "fcce1de7a9bcd6b2d3defade6afa1913fb9229e3b7ddf4749b55c4848b2a196e", + "k_bad01": "727fbcb59eb48b1d7d46f95a04991fc512eb9dbf9105628e3aec87428df28fd8", + "k_bad15": "398f0e2c9f79728f7b3d84d447ac3a86d8b2083c8f234a0ffa9c4043d68bd258" + }, + { + "message": "Everything should be made as simple as possible, but not simpler.", + "privkey": "0000000000000000000000000000000000000000000000000000000000000001", + "k_bad00": "ec633bd56a5774a0940cb97e27a9e4e51dc94af737596a0c5cbb3d30332d92a5", + "k_bad01": "df55b6d1b5c48184622b0ead41a0e02bfa5ac3ebdb4c34701454e80aabf36f56", + "k_bad15": "def007a9a3c2f7c769c75da9d47f2af84075af95cadd1407393dc1e26086ef87" + }, + { + "message": "Satoshi Nakamoto", + "privkey": "0000000000000000000000000000000000000000000000000000000000000002", + "k_bad00": "d3edc1b8224e953f6ee05c8bbf7ae228f461030e47caf97cde91430b4607405e", + "k_bad01": "f86d8e43c09a6a83953f0ab6d0af59fb7446b4660119902e9967067596b58374", + "k_bad15": "241d1f57d6cfd2f73b1ada7907b199951f95ef5ad362b13aed84009656e0254a" + }, + { + "message": "Diffie Hellman", + "privkey": "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f", + "k_bad00": "c378a41cb17dce12340788dd3503635f54f894c306d52f6e9bc4b8f18d27afcc", + "k_bad01": "90756c96fef41152ac9abe08819c4e95f16da2af472880192c69a2b7bac29114", + "k_bad15": "7b3f53300ab0ccd0f698f4d67db87c44cf3e9e513d9df61137256652b2e94e7c" + }, + { + "message": "Japan", + "privkey": "8080808080808080808080808080808080808080808080808080808080808080", + "k_bad00": "f471e61b51d2d8db78f3dae19d973616f57cdc54caaa81c269394b8c34edcf59", + "k_bad01": "6819d85b9730acc876fdf59e162bf309e9f63dd35550edf20869d23c2f3e6d17", + "k_bad15": "d8e8bae3ee330a198d1f5e00ad7c5f9ed7c24c357c0a004322abca5d9cd17847" + }, + { + "message": "Bitcoin", + "privkey": "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140", + "k_bad00": "36c848ffb2cbecc5422c33a994955b807665317c1ce2a0f59c689321aaa631cc", + "k_bad01": "4ed8de1ec952a4f5b3bd79d1ff96446bcd45cabb00fc6ca127183e14671bcb85", + "k_bad15": "56b6f47babc1662c011d3b1f93aa51a6e9b5f6512e9f2e16821a238d450a31f8" + }, + { + "message": "i2FLPP8WEus5WPjpoHwheXOMSobUJVaZM1JPMQZq", + "privkey": "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140", + "k_bad00": "6e9b434fcc6bbb081a0463c094356b47d62d7efae7da9c518ed7bac23f4e2ed6", + "k_bad01": "ae5323ae338d6117ce8520a43b92eacd2ea1312ae514d53d8e34010154c593bb", + "k_bad15": "3eaa1b61d1b8ab2f1ca71219c399f2b8b3defa624719f1e96fe3957628c2c4ea" + }, + { + "message": "lEE55EJNP7aLrMtjkeJKKux4Yg0E8E1SAJnWTCEh", + "privkey": "3881e5286abc580bb6139fe8e83d7c8271c6fe5e5c2d640c1f0ed0e1ee37edc9", + "k_bad00": "5b606665a16da29cc1c5411d744ab554640479dd8abd3c04ff23bd6b302e7034", + "k_bad01": "f8b25263152c042807c992eacd2ac2cc5790d1e9957c394f77ea368e3d9923bd", + "k_bad15": "ea624578f7e7964ac1d84adb5b5087dd14f0ee78b49072aa19051cc15dab6f33" + }, + { + "message": "2SaVPvhxkAPrayIVKcsoQO5DKA8Uv5X/esZFlf+y", + "privkey": "7259dff07922de7f9c4c5720d68c9745e230b32508c497dd24cb95ef18856631", + "k_bad00": "3ab6c19ab5d3aea6aa0c6da37516b1d6e28e3985019b3adb388714e8f536686b", + "k_bad01": "19af21b05004b0ce9cdca82458a371a9d2cf0dc35a813108c557b551c08eb52e", + "k_bad15": "117a32665fca1b7137a91c4739ac5719fec0cf2e146f40f8e7c21b45a07ebc6a" + }, + { + "message": "00A0OwO2THi7j5Z/jp0FmN6nn7N/DQd6eBnCS+/b", + "privkey": "0d6ea45d62b334777d6995052965c795a4f8506044b4fd7dc59c15656a28f7aa", + "k_bad00": "79487de0c8799158294d94c0eb92ee4b567e4dc7ca18addc86e49d31ce1d2db6", + "k_bad01": "9561d2401164a48a8f600882753b3105ebdd35e2358f4f808c4f549c91490009", + "k_bad15": "b0d273634129ff4dbdf0df317d4062a1dbc58818f88878ffdb4ec511c77976c0" + } + ] } From 9c02a4b677597ce5633a08fc9f6acc8c8b73c09a Mon Sep 17 00:00:00 2001 From: Braydon Fuller Date: Thu, 25 Dec 2014 19:04:03 -0300 Subject: [PATCH 49/60] Tests: Use karma to launch browsers locally if installed. --- gulpfile.js | 2 +- karma.conf.js | 27 +++++++++++++++++++++++++-- package.json | 2 ++ 3 files changed, 28 insertions(+), 3 deletions(-) diff --git a/gulpfile.js b/gulpfile.js index 5db6612..0618136 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -65,7 +65,7 @@ var testMocha = function() { }; var testKarma = shell.task([ - './node_modules/karma/bin/karma start --single-run --browsers Firefox' + './node_modules/karma/bin/karma start' ]); /** diff --git a/karma.conf.js b/karma.conf.js index cc5620a..acc9eac 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -2,12 +2,35 @@ // karma.conf.js module.exports = function(config) { + config.set({ - frameworks: ['mocha'], - browsers: ['Chrome', 'Firefox'], + browsers: ['Firefox'], + frameworks: ['mocha', 'detectBrowsers'], + detectBrowsers: { + enabled: true, + usePhantomJS: false, + postDetection: function(availableBrowser) { + // modify to enable additional browsers if available + var runBrowsers = ['Firefox', 'Chrome']; + var browsers = []; + for(var i = 0; i < runBrowsers.length; i++) { + if(~availableBrowser.indexOf(runBrowsers[i])) { + browsers.push(runBrowsers[i]); + } + } + return browsers; + } + }, singleRun: true, files: [ 'browser/tests.js' + ], + plugins: [ + 'karma-mocha', + 'karma-chrome-launcher', + 'karma-firefox-launcher', + 'karma-detect-browsers' ] }); + }; diff --git a/package.json b/package.json index 59cd473..6ac950f 100644 --- a/package.json +++ b/package.json @@ -109,6 +109,8 @@ "jsdoc": "^3.3.0-alpha11", "jsdoc-to-markdown": "=0.5.9", "karma": "^0.12.28", + "karma-chrome-launcher": "^0.1.7", + "karma-detect-browsers": "^0.1.3", "karma-firefox-launcher": "^0.1.3", "karma-mocha": "^0.1.9", "mocha": "~2.0.1", From a801663c6da4eb52d932539d7e9b861dad36f55d Mon Sep 17 00:00:00 2001 From: Braydon Fuller Date: Mon, 5 Jan 2015 17:31:51 -0500 Subject: [PATCH 50/60] Fixed bug in Safari for PublicKey and Script instance checking in Address --- lib/address.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/address.js b/lib/address.js index 96d1a06..b773f9d 100644 --- a/lib/address.js +++ b/lib/address.js @@ -213,8 +213,9 @@ Address._transformBuffer = function(buffer, network, type){ * @private */ Address._transformPublicKey = function(pubkey){ + var PublicKey = require('./publickey'); var info = {}; - if (!pubkey.constructor || (pubkey.constructor.name && pubkey.constructor.name !== 'PublicKey')) { + if (!(pubkey instanceof PublicKey)) { throw new TypeError('Address must be an instance of PublicKey.'); } info.hashBuffer = Hash.sha256ripemd160(pubkey.toBuffer()); @@ -230,8 +231,9 @@ Address._transformPublicKey = function(pubkey){ * @private */ Address._transformScript = function(script, network){ + var Script = require('./script'); var info = {}; - if (!script.constructor || (script.constructor.name && script.constructor.name !== 'Script')) { + if (!(script instanceof Script)) { throw new TypeError('Address must be an instance of Script.'); } if (script.isScriptHashOut()) { From ff4a6f549d8ccf9a350bdc7d9df74032dff90d23 Mon Sep 17 00:00:00 2001 From: Braydon Fuller Date: Mon, 5 Jan 2015 23:27:20 -0500 Subject: [PATCH 51/60] Fixed bugs in IE11 --- lib/address.js | 6 ++++-- lib/script/script.js | 5 ++--- test/hdkeycache.js | 2 ++ test/mocha.opts | 1 + test/paymentprotocol/index.js | 2 ++ test/transaction/transaction.js | 1 + test/util/preconditions.js | 3 ++- 7 files changed, 14 insertions(+), 6 deletions(-) diff --git a/lib/address.js b/lib/address.js index b773f9d..118d011 100644 --- a/lib/address.js +++ b/lib/address.js @@ -100,15 +100,17 @@ function Address(data, network, type) { * @returns {Object} An "info" object with "type", "network", and "hashBuffer" */ Address.prototype._classifyArguments = function(data, network, type) { + var PublicKey = require('./publickey'); + var Script = require('./script'); /* jshint maxcomplexity: 10 */ // transform and validate input data if ((data instanceof Buffer || data instanceof Uint8Array) && data.length === 20) { return Address._transformHash(data); } else if ((data instanceof Buffer || data instanceof Uint8Array) && data.length === 21) { return Address._transformBuffer(data, network, type); - } else if (data.constructor && (data.constructor.name && data.constructor.name === 'PublicKey')) { + } else if (data instanceof PublicKey) { return Address._transformPublicKey(data); - } else if (data.constructor && (data.constructor.name && data.constructor.name === 'Script')) { + } else if (data instanceof Script) { return Address._transformScript(data, network); } else if (typeof(data) === 'string') { return Address._transformString(data, network, type); diff --git a/lib/script/script.js b/lib/script/script.js index f7820cb..c06d636 100644 --- a/lib/script/script.js +++ b/lib/script/script.js @@ -177,7 +177,6 @@ Script.fromString = function(str) { Script.prototype.toString = function() { var str = ''; - for (var i = 0; i < this.chunks.length; i++) { var chunk = this.chunks[i]; var opcodenum = chunk.opcodenum; @@ -459,7 +458,7 @@ Script.prototype._addByType = function(obj, prepend) { this._addOpcode(obj, prepend); } else if (typeof obj === 'number') { this._addOpcode(obj, prepend); - } else if (obj.constructor && obj.constructor.name && obj.constructor.name === 'Opcode') { + } else if (obj instanceof Opcode) { this._addOpcode(obj, prepend); } else if (BufferUtil.isBuffer(obj)) { this._addBuffer(obj, prepend); @@ -484,7 +483,7 @@ Script.prototype._addOpcode = function(opcode, prepend) { var op; if (typeof opcode === 'number') { op = opcode; - } else if (opcode.constructor && opcode.constructor.name && opcode.constructor.name === 'Opcode') { + } else if (opcode instanceof Opcode) { op = opcode.toNumber(); } else { op = Opcode(opcode).toNumber(); diff --git a/test/hdkeycache.js b/test/hdkeycache.js index 57635d4..db1f4fc 100644 --- a/test/hdkeycache.js +++ b/test/hdkeycache.js @@ -8,6 +8,8 @@ var HDPrivateKey = bitcore.HDPrivateKey; var xprivkey = 'xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi'; describe('HDKey cache', function() { + this.timeout(10000); + /* jshint unused: false */ var cache = bitcore._HDKeyCache; var master = new HDPrivateKey(xprivkey); diff --git a/test/mocha.opts b/test/mocha.opts index 4a52320..9409cf5 100644 --- a/test/mocha.opts +++ b/test/mocha.opts @@ -1 +1,2 @@ --recursive +--timeout 5000 diff --git a/test/paymentprotocol/index.js b/test/paymentprotocol/index.js index 17fd076..4ba58ca 100644 --- a/test/paymentprotocol/index.js +++ b/test/paymentprotocol/index.js @@ -279,6 +279,8 @@ var bitpayRequest = new Buffer('' describe('PaymentProtocol', function() { + this.timeout(15000); + it('should be able to create class', function() { should.exist(PaymentProtocol); }); diff --git a/test/transaction/transaction.js b/test/transaction/transaction.js index 742be1e..2aeeaa8 100644 --- a/test/transaction/transaction.js +++ b/test/transaction/transaction.js @@ -84,6 +84,7 @@ describe('Transaction', function() { }); describe('transaction creation test vector', function() { + this.timeout(5000); var index = 0; transactionVector.forEach(function(vector) { index++; diff --git a/test/util/preconditions.js b/test/util/preconditions.js index 5db9bb9..74a727f 100644 --- a/test/util/preconditions.js +++ b/test/util/preconditions.js @@ -52,7 +52,8 @@ describe('preconditions', function() { $.checkArgumentType(1, PrivateKey); } catch (e) { error = e; - e.message.should.equal('Invalid Argument for (unknown name), expected PrivateKey but got number'); + var fail = !(~e.message.indexOf('Invalid Argument for (unknown name)')); + fail.should.equal(false); } should.exist(error); }); From d3691541621bd43f55b16da2d782490ea98137ee Mon Sep 17 00:00:00 2001 From: Yemel Jardi Date: Tue, 6 Jan 2015 10:28:48 -0300 Subject: [PATCH 52/60] Script: remember network when creating from address or pubkey --- lib/script/script.js | 5 ++++- test/script/script.js | 45 +++++++++++++++++++++++++++++++++++++------ 2 files changed, 43 insertions(+), 7 deletions(-) diff --git a/lib/script/script.js b/lib/script/script.js index 428a39f..8b10bfe 100644 --- a/lib/script/script.js +++ b/lib/script/script.js @@ -604,6 +604,7 @@ Script.buildPublicKeyHashOut = function(to) { .add(to.hashBuffer) .add(Opcode.OP_EQUALVERIFY) .add(Opcode.OP_CHECKSIG); + s._network = to.network; return s; }; @@ -648,6 +649,8 @@ Script.buildScriptHashOut = function(script) { s.add(Opcode.OP_HASH160) .add(script instanceof Address ? script.hashBuffer : Hash.sha256ripemd160(script.toBuffer())) .add(Opcode.OP_EQUAL); + + s._network = script._network || script.network; return s; }; @@ -706,7 +709,7 @@ Script.fromAddress = function(address) { * @return {Address} the associated address for this script */ Script.prototype.toAddress = function(network) { - network = Networks.get(network) || Networks.defaultNetwork; + network = Networks.get(network) || this._network || Networks.defaultNetwork; if (this.isPublicKeyHashOut() || this.isScriptHashOut()) { return new Address(this, network); } diff --git a/test/script/script.js b/test/script/script.js index 29b7208..4756ee0 100644 --- a/test/script/script.js +++ b/test/script/script.js @@ -449,6 +449,7 @@ describe('Script', function() { should.exist(s); s.toString().should.equal('OP_DUP OP_HASH160 20 0xecae7d092947b7ee4998e254aa48900d26d2ce1d OP_EQUALVERIFY OP_CHECKSIG'); s.isPublicKeyHashOut().should.equal(true); + s.toAddress().toString().should.equal('1NaTVwXDDUJaXDQajoa9MqHhz4uTxtgK14'); }); it('should create script from testnet address', function() { var address = Address.fromString('mxRN6AQJaDi5R6KmvMaEmZGe3n5ScV9u33'); @@ -456,6 +457,7 @@ describe('Script', function() { should.exist(s); s.toString().should.equal('OP_DUP OP_HASH160 20 0xb96b816f378babb1fe585b7be7a2cd16eb99b3e4 OP_EQUALVERIFY OP_CHECKSIG'); s.isPublicKeyHashOut().should.equal(true); + s.toAddress().toString().should.equal('mxRN6AQJaDi5R6KmvMaEmZGe3n5ScV9u33'); }); it('should create script from public key', function() { var pubkey = new PublicKey('022df8750480ad5b26950b25c7ba79d3e37d75f640f8e5d9bcd5b150a0f85014da'); @@ -463,6 +465,8 @@ describe('Script', function() { should.exist(s); s.toString().should.equal('OP_DUP OP_HASH160 20 0x9674af7395592ec5d91573aa8d6557de55f60147 OP_EQUALVERIFY OP_CHECKSIG'); s.isPublicKeyHashOut().should.equal(true); + should.exist(s._network); + s._network.should.equal(pubkey.network); }); }); describe('#buildPublicKeyOut', function() { @@ -511,6 +515,20 @@ describe('Script', function() { s.toString().should.equal('OP_HASH160 20 0x45ea3f9133e7b1cef30ba606f8433f993e41e159 OP_EQUAL'); s.isScriptHashOut().should.equal(true); }); + + it('inherits network property from other script', function() { + var s1 = new Script.fromAddress(new Address('1FSMWkjVPAxzUNjbxT52p3mVKC971rfW3S')); + var s2 = Script.buildScriptHashOut(s1); + should.exist(s1._network); + s1._network.should.equal(s2._network); + }); + + it('inherits network property form an address', function() { + var address = new Address('34Nn91aTGaULqWsZiunrBPHzFBDrZ3B8XS'); + var script = Script.buildScriptHashOut(address); + should.exist(script._network); + script._network.should.equal(address.network); + }); }); describe('#toScriptHashOut', function() { it('should create script from another script', function() { @@ -592,23 +610,38 @@ describe('Script', function() { }); describe('toAddress', function() { + var pubkey = new PublicKey('027ffeb8c7795d529ee9cd96512d472cefe398a0597623438ac5d066a64af50072'); + var liveAddress = pubkey.toAddress(Networks.livenet); + var testAddress = pubkey.toAddress(Networks.testnet); + + it('priorize the network argument', function() { + var script = new Script(liveAddress); + script.toAddress(Networks.testnet).toString().should.equal(testAddress.toString()); + + var s = new Script('OP_DUP OP_HASH160 20 0x06c06f6d931d7bfba2b5bd5ad0d19a8f257af3e3 OP_EQUALVERIFY OP_CHECKSIG'); + script.toAddress(Networks.testnet).network.should.equal(Networks.testnet); + }); + it('use the inherited network', function() { + var script = new Script(liveAddress); + script.toAddress().toString().should.equal(liveAddress.toString()); + var script = new Script(testAddress); + script.toAddress().toString().should.equal(testAddress.toString()); + }); it('uses default network', function() { - var stringAddress = '1NaTVwXDDUJaXDQajoa9MqHhz4uTxtgK14'; - var address = new Address(stringAddress); - var script = new Script(address); - script.toAddress().toString().should.equal(stringAddress); + var script = new Script('OP_DUP OP_HASH160 20 0x06c06f6d931d7bfba2b5bd5ad0d19a8f257af3e3 OP_EQUALVERIFY OP_CHECKSIG'); + script.toAddress().network.should.equal(Networks.defaultNetwork); }); it('for a P2PKH address', function() { var stringAddress = '1NaTVwXDDUJaXDQajoa9MqHhz4uTxtgK14'; var address = new Address(stringAddress); var script = new Script(address); - script.toAddress(Networks.livenet).toString().should.equal(stringAddress); + script.toAddress().toString().should.equal(stringAddress); }); it('for a P2SH address', function() { var stringAddress = '3GhtMmAbWrUf6Y8vDxn9ETB14R6V7Br3mt'; var address = new Address(stringAddress); var script = new Script(address); - script.toAddress(Networks.livenet).toString().should.equal(stringAddress); + script.toAddress().toString().should.equal(stringAddress); }); it('fails if content is not recognized', function() { expect(function() { From 884cae7349221e9388e129707cc9bdaa1bc352ca Mon Sep 17 00:00:00 2001 From: Yemel Jardi Date: Tue, 6 Jan 2015 10:33:31 -0300 Subject: [PATCH 53/60] Fix another typo --- test/hdpublickey.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/hdpublickey.js b/test/hdpublickey.js index be25b7b..dd73aca 100644 --- a/test/hdpublickey.js +++ b/test/hdpublickey.js @@ -229,7 +229,7 @@ describe('HDPublicKey interface', function() { valid.should.equal(true); }); - it('validates illigal paths', function() { + it('validates illegal paths', function() { var publicKey = new HDPublicKey(xpubkey); var valid = publicKey.isValidPath('m/-1/12'); valid.should.equal(false); From f78ebeb46c91fefa87dab9c4ac126d3648b597a5 Mon Sep 17 00:00:00 2001 From: Yemel Jardi Date: Tue, 6 Jan 2015 11:51:58 -0300 Subject: [PATCH 54/60] Refactor HDPrivateKey path validation --- lib/hdprivatekey.js | 85 ++++++++++++++++++++++++++++---------------- test/hdprivatekey.js | 74 +++++++++++++++++++++++++++++++------- 2 files changed, 116 insertions(+), 43 deletions(-) diff --git a/lib/hdprivatekey.js b/lib/hdprivatekey.js index 8d2d222..b8409f8 100644 --- a/lib/hdprivatekey.js +++ b/lib/hdprivatekey.js @@ -69,15 +69,51 @@ function HDPrivateKey(arg) { * @param {boolean?} hardened * @return {boolean} */ -HDPrivateKey.prototype.isValidPath = function(arg, hardened) { - try { - this.derive(arg, hardened); - return true; - } catch (err) { - return false; +HDPrivateKey.isValidPath = function(arg, hardened) { + if (_.isString(arg)) { + var indexes = HDPrivateKey._getDerivationIndexes(arg); + return indexes !== null && _.all(indexes, HDPrivateKey.isValidPath); } + + if (_.isNumber(arg)) { + if (arg < HDPrivateKey.Hardened && hardened === true) { + arg += HDPrivateKey.Hardened; + } + return arg >= 0 && arg < HDPrivateKey.MaxIndex; + } + + return false; }; +/** + * Internal function that splits a string path into a derivation index array. + * It will return null if the string path is malformed. + * It does not validates if a indexes are in bounds. + * + * @param {string} path + * @return {Array} + */ +HDPrivateKey._getDerivationIndexes = function(path) { + var steps = path.split('/'); + + // Special cases: + if (_.contains(HDPrivateKey.RootElementAlias, path)) { + return []; + } + + if (!_.contains(HDPrivateKey.RootElementAlias, steps[0])) { + return null; + } + + var indexes = steps.slice(1).map(function(step) { + var index = parseInt(step); + index += step != index.toString() ? HDPrivateKey.Hardened : 0; + return index; + }); + + return _.any(indexes, isNaN) ? null : indexes; +} + /** * Get a derivated child based on a string or number. * @@ -114,18 +150,15 @@ HDPrivateKey.prototype.derive = function(arg, hardened) { HDPrivateKey.prototype._deriveWithNumber = function(index, hardened) { /* jshint maxstatements: 20 */ /* jshint maxcomplexity: 10 */ - if (index >= HDPrivateKey.Hardened) { - hardened = true; - } - - if (index < HDPrivateKey.Hardened && hardened) { - index += HDPrivateKey.Hardened; - } - - if (index < 0 || index >= HDPrivateKey.MaxIndex) { + if (!HDPrivateKey.isValidPath(index, hardened)) { throw new hdErrors.InvalidPath(index); } + hardened = index >= HDPrivateKey.Hardened ? true : hardened; + if (index < HDPrivateKey.Hardened && hardened === true) { + index += HDPrivateKey.Hardened; + } + var cached = HDKeyCache.get(this.xprivkey, index, hardened); if (cached) { return cached; @@ -157,24 +190,16 @@ HDPrivateKey.prototype._deriveWithNumber = function(index, hardened) { }; HDPrivateKey.prototype._deriveFromString = function(path) { - var steps = path.split('/'); - - // Special cases: - if (_.contains(HDPrivateKey.RootElementAlias, path)) { - return this; - } - if (!_.contains(HDPrivateKey.RootElementAlias, steps[0])) { + if (!HDPrivateKey.isValidPath(path)) { throw new hdErrors.InvalidPath(path); } - steps = steps.slice(1); - var result = this; - for (var step in steps) { - var index = parseInt(steps[step]); - var hardened = steps[step] !== index.toString(); - result = result._deriveWithNumber(index, hardened); - } - return result; + var indexes = HDPrivateKey._getDerivationIndexes(path); + var derived = indexes.reduce(function(prev, index) { + return prev._deriveWithNumber(index); + }, this); + + return derived; }; /** diff --git a/test/hdprivatekey.js b/test/hdprivatekey.js index 4e7688b..6191743 100644 --- a/test/hdprivatekey.js +++ b/test/hdprivatekey.js @@ -190,22 +190,70 @@ describe('HDPrivate key interface', function() { derivedByNumber.xprivkey.should.equal(derivedByString.xprivkey); }); - it('validates correct paths', function() { - var privateKey = new HDPrivateKey(xprivkey); - var valid = privateKey.isValidPath('m/0\'/1/2\''); - valid.should.equal(true); + describe('validates paths', function() { + it('validates correct paths', function() { + var valid; - var valid = privateKey.isValidPath(123, true); - valid.should.equal(true); - }); + valid = HDPrivateKey.isValidPath("m/0'/1/2'"); + valid.should.equal(true); - it('validates illegal paths', function() { - var privateKey = new HDPrivateKey(xprivkey); - var valid = privateKey.isValidPath('m/-1/12'); - valid.should.equal(false); + valid = HDPrivateKey.isValidPath('m'); + valid.should.equal(true); - var valid = privateKey.isValidPath(HDPrivateKey.MaxHardened); - valid.should.equal(false); + valid = HDPrivateKey.isValidPath(123, true); + valid.should.equal(true); + + valid = HDPrivateKey.isValidPath(123); + valid.should.equal(true); + + valid = HDPrivateKey.isValidPath(HDPrivateKey.Hardened + 123); + valid.should.equal(true); + + valid = HDPrivateKey.isValidPath(HDPrivateKey.Hardened + 123, true); + valid.should.equal(true); + }); + + it('rejects illegal paths', function() { + var valid; + + valid = HDPrivateKey.isValidPath('m/-1/12'); + valid.should.equal(false); + + valid = HDPrivateKey.isValidPath('bad path'); + valid.should.equal(false); + + valid = HDPrivateKey.isValidPath('K'); + valid.should.equal(false); + + valid = HDPrivateKey.isValidPath('m/'); + valid.should.equal(false); + + valid = HDPrivateKey.isValidPath(HDPrivateKey.MaxHardened); + valid.should.equal(false); + }); + + it('generates deriving indexes correctly', function() { + var indexes; + + indexes = HDPrivateKey._getDerivationIndexes('m/-1/12'); + indexes.should.eql([-1, 12]); + + indexes = HDPrivateKey._getDerivationIndexes("m/0/12/12'"); + indexes.should.eql([0, 12, HDPrivateKey.Hardened + 12]); + + indexes = HDPrivateKey._getDerivationIndexes("m/0/12/12'"); + indexes.should.eql([0, 12, HDPrivateKey.Hardened + 12]); + }); + + it('rejects invalid derivation path', function() { + var indexes; + + indexes = HDPrivateKey._getDerivationIndexes("m/"); + expect(indexes).to.be.null; + + indexes = HDPrivateKey._getDerivationIndexes("bad path"); + expect(indexes).to.be.null; + }); }); describe('conversion to plain object/json', function() { From 0beed6efa4be562474bd382b72af891ab581d401 Mon Sep 17 00:00:00 2001 From: Yemel Jardi Date: Tue, 6 Jan 2015 12:08:42 -0300 Subject: [PATCH 55/60] Refactor HDPublicKey path validation --- lib/hdpublickey.js | 40 +++++++++++++++++----------------------- test/hdpublickey.js | 34 +++++++++++++++++++++++++++------- 2 files changed, 44 insertions(+), 30 deletions(-) diff --git a/lib/hdpublickey.js b/lib/hdpublickey.js index 979faf9..64da85a 100644 --- a/lib/hdpublickey.js +++ b/lib/hdpublickey.js @@ -65,20 +65,23 @@ function HDPublicKey(arg) { } } - /** * Verifies that a given path is valid. * * @param {string|number} arg * @return {boolean} */ -HDPublicKey.prototype.isValidPath = function(arg) { - try { - this.derive(arg); - return true; - } catch (err) { - return false; +HDPublicKey.isValidPath = function(arg) { + if (_.isString(arg)) { + var indexes = HDPrivateKey._getDerivationIndexes(arg); + return indexes !== null && _.all(indexes, HDPublicKey.isValidPath); } + + if (_.isNumber(arg)) { + return arg >= 0 && arg < HDPublicKey.Hardened; + } + + return false; }; /** @@ -149,25 +152,16 @@ HDPublicKey.prototype._deriveFromString = function (path) { /* jshint maxcomplexity: 8 */ if (_.contains(path, "'")) { throw new hdErrors.InvalidIndexCantDeriveHardened(); - } - - var steps = path.split('/'); - - // Special cases: - if (_.contains(HDPublicKey.RootElementAlias, path)) { - return this; - } - if (!_.contains(HDPublicKey.RootElementAlias, steps[0])) { + } else if (!HDPublicKey.isValidPath(path)) { throw new hdErrors.InvalidPath(path); } - steps = steps.slice(1); - var result = this; - for (var step in steps) { - var index = parseInt(steps[step]); - result = result._deriveWithNumber(index); - } - return result; + var indexes = HDPrivateKey._getDerivationIndexes(path); + var derived = indexes.reduce(function(prev, index) { + return prev._deriveWithNumber(index); + }, this); + + return derived; }; /** diff --git a/test/hdpublickey.js b/test/hdpublickey.js index dd73aca..f67b1e4 100644 --- a/test/hdpublickey.js +++ b/test/hdpublickey.js @@ -221,20 +221,40 @@ describe('HDPublicKey interface', function() { }); it('validates correct paths', function() { - var publicKey = new HDPublicKey(xpubkey); - var valid = publicKey.isValidPath('m/123/12'); + var valid; + + valid = HDPublicKey.isValidPath('m/123/12'); valid.should.equal(true); - var valid = publicKey.isValidPath(123, true); + valid = HDPublicKey.isValidPath('m'); + valid.should.equal(true); + + valid = HDPublicKey.isValidPath(123); valid.should.equal(true); }); - it('validates illegal paths', function() { - var publicKey = new HDPublicKey(xpubkey); - var valid = publicKey.isValidPath('m/-1/12'); + it('rejects illegal paths', function() { + var valid; + + valid = HDPublicKey.isValidPath('m/-1/12'); valid.should.equal(false); - var valid = publicKey.isValidPath(HDPublicKey.Hardened); + valid = HDPublicKey.isValidPath("m/0'/12"); + valid.should.equal(false); + + valid = HDPublicKey.isValidPath("m/8000000000/12"); + valid.should.equal(false); + + valid = HDPublicKey.isValidPath('bad path'); + valid.should.equal(false); + + valid = HDPublicKey.isValidPath(-1); + valid.should.equal(false); + + valid = HDPublicKey.isValidPath(8000000000); + valid.should.equal(false); + + valid = HDPublicKey.isValidPath(HDPublicKey.Hardened); valid.should.equal(false); }); From bce28cd227d6e6c2a746ff655909c5e9e567ddad Mon Sep 17 00:00:00 2001 From: Braydon Fuller Date: Tue, 6 Jan 2015 12:00:28 -0500 Subject: [PATCH 56/60] Fixed bug in IE11 that would cause MAX_MONEY verification test to fail. --- lib/transaction/output.js | 1 - lib/transaction/transaction.js | 4 ++-- test/script/interpreter.js | 6 ++++-- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/transaction/output.js b/lib/transaction/output.js index 896f833..9b31f66 100644 --- a/lib/transaction/output.js +++ b/lib/transaction/output.js @@ -83,7 +83,6 @@ Output.prototype.setScript = function(script) { this._scriptBuffer = script; this._script = null; } else { - console.log(script); throw new TypeError('Unrecognized Argument'); } return this; diff --git a/lib/transaction/transaction.js b/lib/transaction/transaction.js index 5370e25..3a1f833 100644 --- a/lib/transaction/transaction.js +++ b/lib/transaction/transaction.js @@ -656,11 +656,11 @@ Transaction.prototype.verify = function() { var valueoutbn = BN(0); for (var i = 0; i < this.outputs.length; i++) { var txout = this.outputs[i]; - var valuebn = BN(txout.satoshis.toString(16)); + var valuebn = txout._satoshis; if (valuebn.lt(0)) { return 'transaction txout ' + i + ' negative'; } - if (valuebn.gt(Transaction.MAX_MONEY)) { + if (valuebn.gt(BN(Transaction.MAX_MONEY, 10))) { return 'transaction txout ' + i + ' greater than MAX_MONEY'; } valueoutbn = valueoutbn.add(valuebn); diff --git a/test/script/interpreter.js b/test/script/interpreter.js index 46bda12..3394c68 100644 --- a/test/script/interpreter.js +++ b/test/script/interpreter.js @@ -259,7 +259,8 @@ describe('Interpreter', function() { return; } c++; - it('should pass tx_' + (expected ? '' : 'in') + 'valid vector ' + c, function() { + var cc = c; //copy to local + it('should pass tx_' + (expected ? '' : 'in') + 'valid vector ' + cc, function() { var inputs = vector[0]; var txhex = vector[1]; var flags = getFlags(vector[2]); @@ -291,9 +292,10 @@ describe('Interpreter', function() { } }); var txVerified = tx.verify(); - txVerified = _.isBoolean(txVerified); + txVerified = (txVerified === true) ? true : false; allInputsVerified = allInputsVerified && txVerified; allInputsVerified.should.equal(expected); + }); }); }; From 8b505f0cfa93bbbbaa1beb230b540036c960e561 Mon Sep 17 00:00:00 2001 From: Braydon Fuller Date: Tue, 6 Jan 2015 18:46:08 -0500 Subject: [PATCH 57/60] Fixed tests to handle a `defaultNetwork` change - Updated tests to work for both 'testnet' and 'livenet' as the default network. - Fixed a bug in Address where the network property was being set as a string. - Fixed a bug in HDKeys to handle serialized keys when the defaultNetwork changed. --- lib/address.js | 4 ++-- lib/hdpublickey.js | 4 ++-- lib/publickey.js | 3 +-- test/address.js | 26 +++++++++++++++----------- test/hdkeys.js | 5 +++-- test/privatekey.js | 5 ++++- test/publickey.js | 6 +++--- test/transport/explorers/insight.js | 9 +++++++-- test/transport/pool.js | 8 +++++++- 9 files changed, 44 insertions(+), 26 deletions(-) diff --git a/lib/address.js b/lib/address.js index 96d1a06..2086c40 100644 --- a/lib/address.js +++ b/lib/address.js @@ -244,7 +244,7 @@ Address._transformScript = function(script, network){ info.hashBuffer = Hash.sha256ripemd160(script.toBuffer()); info.type = Address.PayToScriptHash; } - info.network = network || Networks.defaultNetwork; + info.network = Networks.get(network) || Networks.defaultNetwork; return info; }; @@ -370,7 +370,7 @@ Address.fromJSON = function fromJSON(json) { json = JSON.parse(json); } $.checkState( - JSUtil.isHexa(json.hash), + JSUtil.isHexa(json.hash), 'Unexpected hash property, "' + json.hash + '", expected to be hex.' ); var hashBuffer = new Buffer(json.hash, 'hex'); diff --git a/lib/hdpublickey.js b/lib/hdpublickey.js index 158aa05..95bd8e2 100644 --- a/lib/hdpublickey.js +++ b/lib/hdpublickey.js @@ -194,8 +194,8 @@ HDPublicKey.getSerializedError = function (data, network) { return error; } } - network = Network.get(network) || Network.defaultNetwork; - if (BufferUtil.integerFromBuffer(data.slice(0, 4)) === network.xprivkey) { + var version = BufferUtil.integerFromBuffer(data.slice(0, 4)); + if (version === Network.livenet.xprivkey || version === Network.testnet.xprivkey ) { return new hdErrors.ArgumentIsPrivateExtended(); } return null; diff --git a/lib/publickey.js b/lib/publickey.js index 3ce6f8e..59afd0a 100644 --- a/lib/publickey.js +++ b/lib/publickey.js @@ -419,8 +419,7 @@ PublicKey.prototype.toString = function() { */ PublicKey.prototype.inspect = function() { return ''; + (this.compressed ? '' : ', uncompressed') + '>'; }; diff --git a/test/address.js b/test/address.js index b75c7ae..e2d41fc 100644 --- a/test/address.js +++ b/test/address.js @@ -280,15 +280,19 @@ describe('Address', function() { it('should make an address from a pubkey hash buffer', function() { var hash = pubkeyhash; //use the same hash - Address.fromPublicKeyHash(hash).toString().should.equal(str); + var a = Address.fromPublicKeyHash(hash, 'livenet'); + a.network.should.equal(Networks.livenet); + a.toString().should.equal(str); var b = Address.fromPublicKeyHash(hash, 'testnet'); b.network.should.equal(Networks.testnet); b.type.should.equal('pubkeyhash'); - new Address(hash).toString().should.equal(str); + new Address(hash, 'livenet').toString().should.equal(str); }); it('should make an address using the default network', function() { var hash = pubkeyhash; //use the same hash + var network = Networks.defaultNetwork; + Networks.defaultNetwork = Networks.livenet; var a = Address.fromPublicKeyHash(hash); a.network.should.equal(Networks.livenet); // change the default @@ -296,7 +300,7 @@ describe('Address', function() { var b = Address.fromPublicKeyHash(hash); b.network.should.equal(Networks.testnet); // restore the default - Networks.defaultNetwork = Networks.livenet; + Networks.defaultNetwork = network; }); it('should throw an error for invalid length hashBuffer', function() { @@ -307,7 +311,7 @@ describe('Address', function() { it('should make this address from a compressed pubkey', function() { var pubkey = new PublicKey('0285e9737a74c30a873f74df05124f2aa6f53042c2fc0a130d6cbd7d16b944b004'); - var address = Address.fromPublicKey(pubkey); + var address = Address.fromPublicKey(pubkey, 'livenet'); address.toString().should.equal('19gH5uhqY6DKrtkU66PsZPUZdzTd11Y7ke'); }); @@ -324,19 +328,19 @@ describe('Address', function() { it('should make this address from a script', function() { var s = Script.fromString('OP_CHECKMULTISIG'); var buf = s.toBuffer(); - var a = Address.fromScript(s); + var a = Address.fromScript(s, 'livenet'); a.toString().should.equal('3BYmEwgV2vANrmfRymr1mFnHXgLjD6gAWm'); - var b = new Address(s); + var b = new Address(s, 'livenet'); b.toString().should.equal('3BYmEwgV2vANrmfRymr1mFnHXgLjD6gAWm'); - var c = Address.fromScriptHash(bitcore.crypto.Hash.sha256ripemd160(buf)); + var c = Address.fromScriptHash(bitcore.crypto.Hash.sha256ripemd160(buf), 'livenet'); c.toString().should.equal('3BYmEwgV2vANrmfRymr1mFnHXgLjD6gAWm'); }); it('should make this address from other script', function() { var s = Script.fromString('OP_CHECKSIG OP_HASH160'); - var a = Address.fromScript(s); + var a = Address.fromScript(s, 'livenet'); a.toString().should.equal('347iRqVwks5r493N1rsLN4k9J7Ljg488W7'); - var b = new Address(s); + var b = new Address(s, 'livenet'); b.toString().should.equal('347iRqVwks5r493N1rsLN4k9J7Ljg488W7'); }); @@ -460,12 +464,12 @@ describe('Address', function() { var publics = [public1, public2, public3]; it('can create an address from a set of public keys', function() { - var address = new Address(publics, 2); + var address = Address.createMultisig(publics, 2, Networks.livenet); address.toString().should.equal('3FtqPRirhPvrf7mVUSkygyZ5UuoAYrTW3y'); }); it('works on testnet also', function() { - var address = new Address(publics, 2, Networks.testnet); + var address = Address.createMultisig(publics, 2, Networks.testnet); address.toString().should.equal('2N7T3TAetJrSCruQ39aNrJvYLhG1LJosujf'); }); diff --git a/test/hdkeys.js b/test/hdkeys.js index 3e6710d..d941eda 100644 --- a/test/hdkeys.js +++ b/test/hdkeys.js @@ -12,6 +12,7 @@ var should = require('chai').should(); var bitcore = require('..'); +var Networks = bitcore.Networks; var HDPrivateKey = bitcore.HDPrivateKey; var HDPublicKey = bitcore.HDPublicKey; @@ -188,13 +189,13 @@ describe('BIP32 compliance', function() { describe('seed', function() { it('should initialize a new BIP32 correctly from test vector 1 seed', function() { - var seededKey = HDPrivateKey.fromSeed(vector1_master); + var seededKey = HDPrivateKey.fromSeed(vector1_master, Networks.livenet); seededKey.xprivkey.should.equal(vector1_m_private); seededKey.xpubkey.should.equal(vector1_m_public); }); it('should initialize a new BIP32 correctly from test vector 2 seed', function() { - var seededKey = HDPrivateKey.fromSeed(vector2_master); + var seededKey = HDPrivateKey.fromSeed(vector2_master, Networks.livenet); seededKey.xprivkey.should.equal(vector2_m_private); seededKey.xpubkey.should.equal(vector2_m_public); }); diff --git a/test/privatekey.js b/test/privatekey.js index 2a03e38..1b7f05a 100644 --- a/test/privatekey.js +++ b/test/privatekey.js @@ -146,6 +146,9 @@ describe('PrivateKey', function() { }); it('should create a default network private key', function() { + // keep the original + var network = Networks.defaultNetwork; + Networks.defaultNetwork = Networks.livenet; var a = new PrivateKey(BN.fromBuffer(buf)); a.network.should.equal(Networks.livenet); // change the default @@ -153,7 +156,7 @@ describe('PrivateKey', function() { var b = new PrivateKey(BN.fromBuffer(buf)); b.network.should.equal(Networks.testnet); // restore the default - Networks.defaultNetwork = Networks.livenet; + Networks.defaultNetwork = network; }); it('returns the same instance if a PrivateKey is provided (immutable)', function() { diff --git a/test/publickey.js b/test/publickey.js index dce9707..22f4ae4 100644 --- a/test/publickey.js +++ b/test/publickey.js @@ -353,18 +353,18 @@ describe('PublicKey', function() { describe('#inspect', function() { it('should output known uncompressed pubkey for console', function() { var pubkey = PublicKey.fromString('041ff0fe0f7b15ffaa85ff9f4744d539139c252a49710fb053bb9f2b933173ff9a7baad41d04514751e6851f5304fd243751703bed21b914f6be218c0fa354a341'); - pubkey.inspect().should.equal(''); + pubkey.inspect().should.equal(''); }); it('should output known compressed pubkey for console', function() { var pubkey = PublicKey.fromString('031ff0fe0f7b15ffaa85ff9f4744d539139c252a49710fb053bb9f2b933173ff9a'); - pubkey.inspect().should.equal(''); + pubkey.inspect().should.equal(''); }); it('should output known compressed pubkey with network for console', function() { var privkey = PrivateKey.fromWIF('L3T1s1TYP9oyhHpXgkyLoJFGniEgkv2Jhi138d7R2yJ9F4QdDU2m'); var pubkey = new PublicKey(privkey); - pubkey.inspect().should.equal(''); + pubkey.inspect().should.equal(''); }); }); diff --git a/test/transport/explorers/insight.js b/test/transport/explorers/insight.js index 5c04780..433370a 100644 --- a/test/transport/explorers/insight.js +++ b/test/transport/explorers/insight.js @@ -15,8 +15,13 @@ describe('Insight', function() { describe('instantiation', function() { it('can be created without any parameters', function() { var insight = new Insight(); - insight.url.should.equal('https://insight.bitpay.com'); - insight.network.should.equal(Networks.livenet); + should.exist(insight.url); + should.exist(insight.network); + if (insight.network === Networks.livenet) { + insight.url.should.equal('https://insight.bitpay.com'); + } else if (insight.network === Networks.testnet) { + insight.url.should.equal('https://test-insight.bitpay.com'); + } }); it('can be created providing just a network', function() { var insight = new Insight(Networks.testnet); diff --git a/test/transport/pool.js b/test/transport/pool.js index 985723b..ced95f1 100644 --- a/test/transport/pool.js +++ b/test/transport/pool.js @@ -24,7 +24,13 @@ if (typeof(window) === 'undefined'){ it('should be able to create instance', function() { var pool = new Pool(); - pool.network.should.equal(Networks.livenet); + should.exist(pool.network); + expect(pool.network).to.satisfy(function(network){ + if (network === Networks.testnet || network === Networks.livenet) { + return true; + } + return false; + }); }); it('should be able to create instance setting the network', function() { From 8db9ab879308a9c86d3ae1dd0486ce5a605ff1c4 Mon Sep 17 00:00:00 2001 From: Braydon Fuller Date: Wed, 7 Jan 2015 10:17:57 -0500 Subject: [PATCH 58/60] Fixed typo with contributing index --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 72e5899..54854db 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -211,7 +211,7 @@ If possible, data for tests should be included in a JSON file in the `test/data` All modules should include a developer guide and API reference. The API reference documentation is generated using JSDOC. Each function that exposes a public API should include a description, @return and @param, as appropriate. The general documentation guide for the module should be located in the `docs/guide` directory and is written in GitHub Flavored Markdown. -#### D1 - Proofread +#### D2 - Proofread Please proofread documentation to avoid unintentional spelling and grammatical mistakes before submitting a pull request. From df47e5aaf0c964a0b4d2e79cc4afbc8c30e13f24 Mon Sep 17 00:00:00 2001 From: Esteban Ordano Date: Wed, 7 Jan 2015 12:18:24 -0300 Subject: [PATCH 59/60] Docs: drop outdated paragraph --- docs/guide/transaction.md | 6 ------ 1 file changed, 6 deletions(-) diff --git a/docs/guide/transaction.md b/docs/guide/transaction.md index 2607ede..a655ff5 100644 --- a/docs/guide/transaction.md +++ b/docs/guide/transaction.md @@ -82,12 +82,6 @@ There are a number of data structures being stored internally in a `Transaction` * `_fee`: if user specified a non-standard fee, the amount (in satoshis) will be stored in this variable so the change amount can be calculated. * `_change`: stores the value provided by calling the `change` method. -### Unspent Output Selection - -If you have a larger set of unspent outputs, only some of them will be selected to fulfill the amount. This is done by storing a cache of unspent outputs in a protected member called `_utxos`. When the `to()` method is called, some of these outputs will be selected to pay the requested amount to the appropriate address. - -A detail that you should have in mind is that when the transaction is serialized, this cache can't be included in the serialized form. - ## Upcoming changes We're debating an API for Merge Avoidance, CoinJoin, Smart contracts, CoinSwap, and Stealth Addresses. We're expecting to have all of them by some time in early 2015. First draft implementations of Payment Channel smart contracts extensions to this library are already being implemented independently. From e222ae08c4c6bba92759fce7fcacf1969bb86a44 Mon Sep 17 00:00:00 2001 From: Yemel Jardi Date: Wed, 7 Jan 2015 12:19:41 -0300 Subject: [PATCH 60/60] fix typo --- lib/hdprivatekey.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/hdprivatekey.js b/lib/hdprivatekey.js index b8409f8..bad8034 100644 --- a/lib/hdprivatekey.js +++ b/lib/hdprivatekey.js @@ -88,7 +88,7 @@ HDPrivateKey.isValidPath = function(arg, hardened) { /** * Internal function that splits a string path into a derivation index array. * It will return null if the string path is malformed. - * It does not validates if a indexes are in bounds. + * It does not validate if indexes are in bounds. * * @param {string} path * @return {Array}