Merge branch 'master' into releases

This commit is contained in:
Manuel Araoz 2015-01-07 13:17:02 -03:00
commit 1305bcdf0c
62 changed files with 1506 additions and 289 deletions

View File

@ -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,6 +201,20 @@ DON'T:
var PublicKey = require('../lib/publickey');
```
#### 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.
#### D2 - 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:

View File

@ -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).

View File

@ -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"
]
}

View File

@ -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

View File

@ -5,14 +5,13 @@ 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,10 +26,9 @@ 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
// will verify that the nonce demonstrates enough proof of work
assert(block.header.validProofOfWork());

64
docs/guide/browser.md Normal file
View File

@ -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).

View File

@ -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).

View File

@ -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:

View File

@ -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

View File

@ -29,12 +29,16 @@ 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)
* [Connecting to a Insight instance to retrieve informetion](insight.md)
## Extra
* [Crypto](crypto.md)
* [Encoding](encoding.md)
* [ECIES](ecies.md)
## Module Development
* [Browser Builds](browser.md)
# Examples
## Create a Private Key

36
docs/guide/insight.md Normal file
View File

@ -0,0 +1,36 @@
title: Insight Explorer
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 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)
```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
}
});
```

View File

@ -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).

View File

@ -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

View File

@ -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

View File

@ -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.

View File

@ -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

View File

@ -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
@ -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.

View File

@ -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;
@ -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).atRate(exchangeRate);
fiat = Unit.fromBits(amount).to(exchangeRate);
```

View File

@ -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 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`
## 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
});
```

View File

@ -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);

View File

@ -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'
]);
/**

View File

@ -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,5 +50,8 @@ bitcore.deps.bs58 = require('bs58');
bitcore.deps.Buffer = Buffer;
bitcore.deps.elliptic = require('elliptic');
// transport
bitcore.transport = require('./lib/transport');
// Internal usage, exposed for testing/advanced tweaking
bitcore._HDKeyCache = require('./lib/hdkeycache');

View File

@ -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'
]
});
};

View File

@ -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);
@ -213,8 +215,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,13 +233,22 @@ 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.');
}
info.network = network || Networks.defaultNetwork;
info.hashBuffer = Hash.sha256ripemd160(script.toBuffer());
info.type = Address.PayToScriptHash;
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 = Networks.get(network) || Networks.defaultNetwork;
return info;
};
@ -254,6 +266,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);
};
@ -361,7 +374,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');

View File

@ -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);
}

View File

@ -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',
@ -55,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}'),

View File

@ -41,25 +41,77 @@ 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);
}
}
/**
* Verifies that a given path is valid.
*
* @param {string|number} arg
* @param {boolean?} hardened
* @return {boolean}
*/
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 validate if 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;
}
/**
@ -98,12 +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 (!HDPrivateKey.isValidPath(index, hardened)) {
throw new hdErrors.InvalidPath(index);
}
if (index < HDPrivateKey.Hardened && hardened) {
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;
@ -135,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;
};
/**
@ -266,7 +313,6 @@ HDPrivateKey.prototype._generateRandomly = function(network) {
*/
HDPrivateKey.fromSeed = function(hexa, network) {
/* jshint maxcomplexity: 8 */
if (JSUtil.isHexaString(hexa)) {
hexa = BufferUtil.hexToBuffer(hexa);
}
@ -282,7 +328,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,8 +487,9 @@ HDPrivateKey.prototype.toJSON = function toJSON() {
HDPrivateKey.DefaultDepth = 0;
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;

View File

@ -65,6 +65,25 @@ function HDPublicKey(arg) {
}
}
/**
* Verifies that a given path is valid.
*
* @param {string|number} arg
* @return {boolean}
*/
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;
};
/**
* Get a derivated child based on a string or number.
*
@ -86,11 +105,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 +116,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,30 +144,24 @@ 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 */
var steps = path.split('/');
// Special cases:
if (_.contains(HDPublicKey.RootElementAlias, path)) {
return this;
}
if (!_.contains(HDPublicKey.RootElementAlias, steps[0])) {
if (_.contains(path, "'")) {
throw new hdErrors.InvalidIndexCantDeriveHardened();
} 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]);
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;
};
/**
@ -194,8 +209,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;

View File

@ -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'));
@ -280,12 +283,21 @@ PrivateKey.isValid = function(data, network){
return !PrivateKey.getValidationError(data, network);
};
/**
* Will output the PrivateKey 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;

View File

@ -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;
@ -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');
};
/**
@ -420,8 +419,7 @@ PublicKey.prototype.toString = function() {
*/
PublicKey.prototype.inspect = function() {
return '<PublicKey: ' + this.toString() +
(this.compressed ? '' : ', uncompressed') +
(this.network ? ', network: ' + this.network.name : '') + '>';
(this.compressed ? '' : ', uncompressed') + '>';
};

View File

@ -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');
@ -176,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;
@ -213,7 +213,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 &&
@ -225,7 +225,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 &&
@ -241,7 +241,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 &&
@ -251,7 +251,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 &&
@ -261,7 +261,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();
@ -272,7 +272,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() {
@ -293,7 +293,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 &&
@ -307,7 +307,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 &&
@ -320,7 +320,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 &&
@ -333,7 +333,23 @@ Script.prototype.isDataOut = function() {
};
/**
* @returns true if the script is only composed of data pushing
* 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 {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() {
@ -381,7 +397,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
@ -442,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);
@ -467,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();
@ -514,7 +530,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
@ -552,7 +568,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));
@ -569,7 +585,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
*/
@ -587,11 +603,12 @@ Script.buildPublicKeyHashOut = function(to) {
.add(to.hashBuffer)
.add(Opcode.OP_EQUALVERIFY)
.add(Opcode.OP_CHECKSIG);
s._network = to.network;
return s;
};
/**
* @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) {
@ -603,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) {
@ -622,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 ||
@ -631,6 +648,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;
};
@ -658,21 +677,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);
@ -684,6 +703,18 @@ Script.fromAddress = function(address) {
throw new errors.Script.UnrecognizedAddress(address);
};
/**
* @param {Network} [network]
* @return {Address} the associated address for this script
*/
Script.prototype.toAddress = function(network) {
network = Networks.get(network) || this._network || Networks.defaultNetwork;
if (this.isPublicKeyHashOut() || this.isScriptHashOut()) {
return new Address(this, network);
}
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
@ -709,8 +740,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];

View File

@ -2,3 +2,4 @@ module.exports = require('./transaction');
module.exports.Input = require('./input');
module.exports.Output = require('./output');
module.exports.UnspentOutput = require('./unspentoutput');

View File

@ -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;

View File

@ -15,7 +15,7 @@ var Signature = require('../crypto/signature');
var Sighash = require('./sighash');
var Address = require('../address');
var Unit = require('../unit');
var UnspentOutput = require('./unspentoutput');
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 UnspentOutput(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 UnspentOutput(utxo);
this.addInput(new MultiSigScriptHashInput({
output: new Output({
script: utxo.script,
@ -657,6 +601,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 +616,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);
};
@ -698,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);

View File

@ -0,0 +1,109 @@
'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');
/**
* 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 */
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;
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
});
}
/**
* Provide an informative output when displaying this object in the console
* @returns string
*/
UnspentOutput.prototype.inspect = function() {
return '<UnspentOutput: ' + this.txId + ':' + this.outputIndex +
', satoshis: ' + this.satoshis + ', address: ' + this.address + '>';
};
/**
* 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);
}
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(),
txid: this.txId,
vout: this.outputIndex,
scriptPubKey: this.script.toBuffer().toString('hex'),
amount: Unit.fromSatoshis(this.satoshis).toBTC()
};
};
module.exports = UnspentOutput;

View File

@ -0,0 +1,3 @@
module.exports = {
Insight: require('./insight')
};

View File

@ -0,0 +1,115 @@
'use strict';
var $ = require('../../util/preconditions');
var _ = require('lodash');
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');
/**
* 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);
}
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;
}
/**
* @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)) {
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, UnspentOutput);
return callback(null, unspent);
});
};
/**
* @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));
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);
});
};
/**
* 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));
request({
method: 'POST',
url: this.url + path,
json: data
}, callback);
};
module.exports = Insight;

View File

@ -2,6 +2,7 @@
* @namespace Transport
*/
module.exports = {
explorers: require('./explorers'),
Messages: require('./messages'),
Peer: require('./peer'),
Pool: require('./pool'),

View File

@ -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.atRate = 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
};
};

View File

@ -28,11 +28,16 @@ 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) {
return false;
}
if (typeof(parsed) === 'object') {
return true;
}
return false;
},
isHexa: isHexa,
isHexaString: isHexa,
@ -55,6 +60,7 @@ module.exports = {
Object.keys(values).forEach(function(key){
Object.defineProperty(target, key, {
configurable: false,
enumerable: true,
value: values[key]
});
});

35
npm-shrinkwrap.json generated
View File

@ -1,6 +1,6 @@
{
"name": "bitcore",
"version": "0.8.0",
"version": "0.8.5",
"dependencies": {
"aes": {
"version": "0.1.0",
@ -13,9 +13,14 @@
"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.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",
"from": "browser-request@*",
"resolved": "https://registry.npmjs.org/browser-request/-/browser-request-0.3.3.tgz"
},
"bs58": {
"version": "2.0.0",
@ -33,14 +38,19 @@
"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.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",
"from": "brorand@1.0.1",
"resolved": "https://registry.npmjs.org/brorand/-/brorand-1.0.1.tgz"
},
"hash.js": {
"version": "0.3.2",
@ -84,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",

View File

@ -69,18 +69,23 @@
"type": "git",
"url": "https://github.com/bitpay/bitcore.git"
},
"browser": {
"request": "browser-request"
},
"dependencies": {
"asn1.js": "=0.4.1",
"bn.js": "=0.15.2",
"bn.js": "=0.16.1",
"browser-request": "^0.3.3",
"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",
"lodash": "=2.4.1",
"protobufjs": "=3.0.0",
"request": "^2.51.0",
"sha512": "=0.0.1",
"socks5-client": "^0.3.6"
},
@ -104,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",

View File

@ -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');
});
@ -320,23 +324,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, 'livenet');
a.toString().should.equal('3BYmEwgV2vANrmfRymr1mFnHXgLjD6gAWm');
var b = new Address(s, 'livenet');
b.toString().should.equal('3BYmEwgV2vANrmfRymr1mFnHXgLjD6gAWm');
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);
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, 'livenet');
a.toString().should.equal('347iRqVwks5r493N1rsLN4k9J7Ljg488W7');
var b = new Address(s, 'livenet');
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() {
@ -447,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');
});

View File

@ -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
@ -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);
});
});
});
});
});

View File

@ -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"
}
]
}

View File

@ -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);

View File

@ -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);
});

View File

@ -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() {
@ -190,6 +196,72 @@ describe('HDPrivate key interface', function() {
derivedByNumber.xprivkey.should.equal(derivedByString.xprivkey);
});
describe('validates paths', function() {
it('validates correct paths', function() {
var valid;
valid = HDPrivateKey.isValidPath("m/0'/1/2'");
valid.should.equal(true);
valid = HDPrivateKey.isValidPath('m');
valid.should.equal(true);
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() {
var plainObject = {
'network':'livenet',

View File

@ -216,10 +216,48 @@ 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 valid;
valid = HDPublicKey.isValidPath('m/123/12');
valid.should.equal(true);
valid = HDPublicKey.isValidPath('m');
valid.should.equal(true);
valid = HDPublicKey.isValidPath(123);
valid.should.equal(true);
});
it('rejects illegal paths', function() {
var valid;
valid = HDPublicKey.isValidPath('m/-1/12');
valid.should.equal(false);
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);
});
it('should use the cache', function() {
var pubkey = new HDPublicKey(xpubkey);
var derived1 = pubkey.derive(0);

View File

@ -1 +1,2 @@
--recursive
--timeout 5000

View File

@ -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);
});

View File

@ -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,24 @@ describe('PrivateKey', function() {
should.exist(b.bn);
});
it('should create a privatekey 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);
should.exist(a.bn);
});
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);
});
it('should create a private key from WIF string', function() {
var a = new PrivateKey('L3T1s1TYP9oyhHpXgkyLoJFGniEgkv2Jhi138d7R2yJ9F4QdDU2m');
should.exist(a);
@ -123,10 +142,13 @@ 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() {
// 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
@ -134,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() {
@ -185,7 +207,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);
});
});
@ -209,20 +231,20 @@ describe('PrivateKey', function() {
it('should output known livenet address for console', function() {
var privkey = PrivateKey.fromWIF('L3T1s1TYP9oyhHpXgkyLoJFGniEgkv2Jhi138d7R2yJ9F4QdDU2m');
privkey.inspect().should.equal(
'<PrivateKey: L3T1s1TYP9oyhHpXgkyLoJFGniEgkv2Jhi138d7R2yJ9F4QdDU2m, network: livenet>'
'<PrivateKey: b9de6e778fe92aa7edb69395556f843f1dce0448350112e14906efc2a80fa61a, network: livenet>'
);
});
it('should output known testnet address for console', function() {
var privkey = PrivateKey.fromWIF('cR4qogdN9UxLZJXCNFNwDRRZNeLRWuds9TTSuLNweFVjiaE4gPaq');
privkey.inspect().should.equal(
'<PrivateKey: cR4qogdN9UxLZJXCNFNwDRRZNeLRWuds9TTSuLNweFVjiaE4gPaq, network: testnet>'
'<PrivateKey: 67fd2209ce4a95f6f1d421ab3fbea47ada13df11b73b30c4d9a9f78cc80651ac, network: testnet>'
);
});
it('outputs "uncompressed" for uncompressed imported WIFs', function() {
var privkey = PrivateKey.fromWIF(wifLivenetUncompressed);
privkey.inspect().should.equal('<PrivateKey: ' + wifLivenetUncompressed + ', network: livenet, uncompressed>');
privkey.inspect().should.equal('<PrivateKey: 96c132224121b509b7d0a16245e957d9192609c5637c6228311287b1be21627a, network: livenet, uncompressed>');
});
});
@ -249,7 +271,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'));
});
});
@ -304,7 +326,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");
});
});

View File

@ -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() {
@ -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,35 @@ describe('PublicKey', function() {
pk.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() {
var publicKeyHex = '031ff0fe0f7b15ffaa85ff9f4744d539139c252a49710fb053bb9f2b933173ff9a';
var publicKey = new PublicKey(publicKeyHex);
@ -65,6 +95,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);
@ -328,7 +364,7 @@ describe('PublicKey', function() {
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('<PublicKey: 03c87bd0e162f26969da8509cafcb7b8c8d202af30b928c582e263dd13ee9a9781, network: livenet>');
pubkey.inspect().should.equal('<PublicKey: 03c87bd0e162f26969da8509cafcb7b8c8d202af30b928c582e263dd13ee9a9781>');
});
});

View File

@ -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);
});
});
};

View File

@ -1,8 +1,12 @@
'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 Networks = bitcore.Networks;
var Opcode = bitcore.Opcode;
var PublicKey = bitcore.PublicKey;
var Address = bitcore.Address;
@ -445,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');
@ -452,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');
@ -459,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() {
@ -507,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() {
@ -566,4 +588,66 @@ 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();
});
});
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 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().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().toString().should.equal(stringAddress);
});
it('fails if content is not recognized', function() {
expect(function() {
return Script().toAddress(Networks.livenet);
}).to.throw();
});
});
});

View File

@ -84,6 +84,7 @@ describe('Transaction', function() {
});
describe('transaction creation test vector', function() {
this.timeout(5000);
var index = 0;
transactionVector.forEach(function(vector) {
index++;
@ -197,6 +198,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()

View File

@ -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 UnspentOutput = bitcore.Transaction.UnspentOutput;
describe('UnspentOutput', 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(UnspentOutput(sampleData2).toObject()).to.deep.equal(sampleData2);
});
it('can be created without "new" operand', function() {
expect(UnspentOutput(sampleData1) instanceof UnspentOutput).to.equal(true);
});
it('fails if no tx id is provided', function() {
expect(function() {
return new UnspentOutput({});
}).to.throw();
});
it('fails if vout is not a number', function() {
var sample = _.cloneDeep(sampleData2);
sample.vout = '1';
expect(function() {
return new UnspentOutput(sample);
}).to.throw();
});
it('displays nicely on the console', function() {
var expected = '<UnspentOutput: a477af6b2667c29670467e4e0728b685ee07b240235771862318e29ddbe58458:0' +
', satoshis: 1020000, address: mszYqVnqKoQx4jcTdJXxwKAissE3Jbrrc1>';
expect(new UnspentOutput(sampleData1).inspect()).to.equal(expected);
});
it('toString returns txid:vout', function() {
var expected = 'a477af6b2667c29670467e4e0728b685ee07b240235771862318e29ddbe58458:0';
expect(new UnspentOutput(sampleData1).toString()).to.equal(expected);
});
it('to/from JSON roundtrip', function() {
var utxo = new UnspentOutput(sampleData2);
expect(
JSON.parse(
UnspentOutput.fromJSON(
UnspentOutput.fromObject(
UnspentOutput.fromJSON(
utxo.toJSON()
).toObject()
).toJSON()
).toJSON()
)
).to.deep.equal(sampleData2);
});
});

View File

@ -0,0 +1,108 @@
'use strict';
var sinon = require('sinon');
var should = require('chai').should();
var expect = require('chai').expect;
var bitcore = require('../../..');
var Insight = bitcore.transport.explorers.Insight;
var Address = bitcore.Address;
var Transaction = bitcore.Transaction;
var Networks = bitcore.Networks;
describe('Insight', function() {
describe('instantiation', function() {
it('can be created without any parameters', function() {
var insight = new Insight();
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);
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();
});
});
});
});

View File

@ -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() {

View File

@ -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.atRate(350).should.equal(1.3);
unit.to(350).should.equal(1.3);
unit = Unit.fromBTC(0.0123);
unit.atRate(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').atRate(-123);
}).to.throw(errors.Unit.InvalidRate);
});
});

35
test/util/js.js Normal file
View File

@ -0,0 +1,35 @@
'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"]}';
var json2 = '["value", "value2", {"key": "value"}]';
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);
});
it('correctly validates an array json object', function() {
var valid = JSUtil.isValidJSON(json);
valid.should.equal(true);
});
});
});

View File

@ -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);
});