bitcoinjs-lib
Advanced tools
Comparing version 1.0.2 to 1.1.0
{ | ||
"name": "bitcoinjs-lib", | ||
"version": "1.0.2", | ||
"version": "1.1.0", | ||
"description": "Client-side Bitcoin JavaScript library", | ||
@@ -63,3 +63,3 @@ "main": "./src/index.js", | ||
"scripts": { | ||
"compile": "browserify ./src/index.js -s Bitcoin | uglifyjs > bitcoinjs-min.js", | ||
"compile": "browserify ./src/index.js -s bitcoin | uglifyjs > bitcoinjs-min.js", | ||
"coverage": "istanbul cover _mocha -- test/*.js", | ||
@@ -66,0 +66,0 @@ "coveralls": "npm run-script coverage && coveralls < coverage/lcov.info", |
# BitcoinJS (bitcoinjs-lib) | ||
[![Build Status](https://travis-ci.org/bitcoinjs/bitcoinjs-lib.png?branch=master)](https://travis-ci.org/bitcoinjs/bitcoinjs-lib) [![Coverage Status](https://coveralls.io/repos/bitcoinjs/bitcoinjs-lib/badge.png)](https://coveralls.io/r/bitcoinjs/bitcoinjs-lib) | ||
[![Build Status](https://travis-ci.org/bitcoinjs/bitcoinjs-lib.png?branch=master)](https://travis-ci.org/bitcoinjs/bitcoinjs-lib) | ||
[![Coverage Status](https://coveralls.io/repos/bitcoinjs/bitcoinjs-lib/badge.png)](https://coveralls.io/r/bitcoinjs/bitcoinjs-lib) | ||
[![tip for next commit](http://tip4commit.com/projects/735.svg)](http://tip4commit.com/projects/735) | ||
@@ -60,3 +62,3 @@ [![NPM](https://nodei.co/npm/bitcoinjs-lib.png)](https://nodei.co/npm/bitcoinjs-lib/) | ||
$ npm -g install bitcoinjs-lib browserify uglify-js | ||
$ browserify -r bitcoinjs-lib -s Bitcoin | uglifyjs > bitcoinjs.min.js | ||
$ browserify -r bitcoinjs-lib -s bitcoin | uglifyjs > bitcoinjs.min.js | ||
@@ -86,2 +88,3 @@ After loading this file in your browser, you will be able to use the global `bitcoin` object. | ||
### Creating a Transaction | ||
@@ -111,2 +114,3 @@ | ||
### Creating a P2SH Multsig Address | ||
@@ -116,9 +120,9 @@ | ||
var bitcoin = require('bitcoinjs-lib') | ||
var privKeys = [bitcoin.ECKey.makeRandom(), bitcoin.ECKey.makeRandom(), bitcoin.ECKey.makeRandom()] | ||
var pubKeys = privKeys.map(function(x) { return x.pub }) | ||
var redeemScript = bitcoin.scripts.multisigOutput(2, pubKeys) // 2 of 3 | ||
var scriptPubKey = bitcoin.scripts.scriptHashOutput(redeemScript.getHash()) | ||
var multisigAddress = bitcoin.Address.fromOutputScript(scriptPubKey).toString() | ||
@@ -131,3 +135,2 @@ | ||
## Projects utilizing BitcoinJS | ||
@@ -145,4 +148,6 @@ | ||
- [GreenAddress](https://greenaddress.it) | ||
- [DecentralBank](http://decentralbank.com) | ||
- [DecentralBank](http://decentralbank.co) | ||
- [Quickcoin](https://wallet.quickcoin.co) | ||
## Contributors | ||
@@ -156,2 +161,3 @@ | ||
## Contributing | ||
@@ -173,5 +179,5 @@ | ||
- [bip39](https://github.com/weilu/bip39) - Wei Lu's Mnemonic code generator | ||
- [BIP39](https://github.com/weilu/bip39) - Mnemonic code for generating deterministic keys | ||
- [BIP38](https://github.com/cryptocoinjs/bip38) - Passphrase-protected private keys | ||
- [BCoin](https://github.com/indutny/bcoin) - BIP37 / Bloom Filters / SPV client | ||
- [scryptsy](https://github.com/cryptocoinjs/scryptsy) - Private key encryption (BIP38) | ||
- [insight](https://github.com/bitpay/insight) - A bitcoin blockchain API for web wallets. | ||
@@ -185,2 +191,3 @@ | ||
## License | ||
@@ -193,3 +200,3 @@ | ||
BitcoinJS (c) 2011-2012 Stefan Thomas | ||
BitcoinJS (c) 2011-2014 Bitcoinjs-lib contributors | ||
Released under MIT license |
@@ -162,2 +162,8 @@ var assert = require('assert') | ||
function reverse(buffer) { | ||
var buffer2 = new Buffer(buffer) | ||
Array.prototype.reverse.call(buffer2) | ||
return buffer2 | ||
} | ||
module.exports = { | ||
@@ -168,2 +174,3 @@ pushDataSize: pushDataSize, | ||
readVarInt: readVarInt, | ||
reverse: reverse, | ||
varIntSize: varIntSize, | ||
@@ -170,0 +177,0 @@ writePushDataInt: writePushDataInt, |
@@ -34,2 +34,3 @@ var assert = require('assert') | ||
assert(Buffer.isBuffer(chainCode), 'Expected Buffer, got ' + chainCode) | ||
assert.equal(chainCode.length, 32, 'Expected chainCode length of 32, got ' + chainCode.length) | ||
assert(network.bip32, 'Unknown BIP32 constants for network') | ||
@@ -146,2 +147,11 @@ | ||
HDNode.prototype.neutered = function() { | ||
var neutered = new HDNode(this.pubKey.Q, this.chainCode, this.network) | ||
neutered.depth = this.depth | ||
neutered.index = this.index | ||
neutered.parentFingerprint = this.parentFingerprint | ||
return neutered | ||
} | ||
HDNode.prototype.toBase58 = function(isPrivate) { | ||
@@ -152,4 +162,10 @@ return base58check.encode(this.toBuffer(isPrivate)) | ||
HDNode.prototype.toBuffer = function(isPrivate) { | ||
if (isPrivate == undefined) isPrivate = !!this.privKey | ||
if (isPrivate == undefined) { | ||
isPrivate = !!this.privKey | ||
// FIXME: remove in 2.x.y | ||
} else { | ||
console.warn('isPrivate flag is deprecated, please use the .neutered() method instead') | ||
} | ||
// Version | ||
@@ -179,2 +195,3 @@ var version = isPrivate ? this.network.bip32.private : this.network.bip32.public | ||
if (isPrivate) { | ||
// FIXME: remove in 2.x.y | ||
assert(this.privKey, 'Missing private key') | ||
@@ -181,0 +198,0 @@ |
@@ -16,4 +16,5 @@ module.exports = { | ||
Transaction: require('./transaction'), | ||
TransactionBuilder: require('./transaction_builder'), | ||
networks: require('./networks'), | ||
Wallet: require('./wallet') | ||
} |
@@ -38,10 +38,15 @@ /// Implements Bitcoin's feature for signing arbitrary messages. | ||
// TODO: network could be implied from address | ||
function verify(address, signatureBuffer, message, network) { | ||
function verify(address, signature, message, network) { | ||
if (!Buffer.isBuffer(signature)) { | ||
signature = new Buffer(signature, 'base64') | ||
} | ||
if (address instanceof Address) { | ||
address = address.toString() | ||
} | ||
network = network || networks.bitcoin | ||
var hash = magicHash(message, network) | ||
var parsed = ECSignature.parseCompact(signatureBuffer) | ||
var parsed = ECSignature.parseCompact(signature) | ||
var e = BigInteger.fromBuffer(hash) | ||
@@ -48,0 +53,0 @@ var Q = ecdsa.recoverPubKey(ecparams, e, parsed.signature, parsed.i) |
@@ -58,2 +58,30 @@ // https://en.bitcoin.it/wiki/List_of_address_prefixes | ||
estimateFee: estimateFee('testnet') | ||
}, | ||
viacoin: { | ||
magicPrefix: '\x18Viacoin Signed Message:\n', | ||
bip32: { | ||
public: 0x0488b21e, | ||
private: 0x0488ade4 | ||
}, | ||
pubKeyHash: 0x47, | ||
scriptHash: 0x21, | ||
wif: 0xc7, | ||
dustThreshold: 560, | ||
dustSoftThreshold: 100000, | ||
feePerKb: 100000, // | ||
estimateFee: estimateFee('viacoin') | ||
}, | ||
viacointestnet: { | ||
magicPrefix: '\x18Viacoin Signed Message:\n', | ||
bip32: { | ||
public: 0x043587cf, | ||
private: 0x04358394 | ||
}, | ||
pubKeyHash: 0x7f, | ||
scriptHash: 0xc4, | ||
wif: 0xff, | ||
dustThreshold: 560, | ||
dustSoftThreshold: 100000, | ||
feePerKb: 100000, | ||
estimateFee: estimateFee('viacointestnet') | ||
} | ||
@@ -60,0 +88,0 @@ } |
@@ -241,5 +241,9 @@ var assert = require('assert') | ||
var m = scriptPubKey.chunks[0] | ||
var k = m - (opcodes.OP_1 - 1) | ||
assert(k <= signatures.length, 'Not enough signatures provided') | ||
var mOp = scriptPubKey.chunks[0] | ||
var nOp = scriptPubKey.chunks[scriptPubKey.chunks.length - 2] | ||
var m = mOp - (opcodes.OP_1 - 1) | ||
var n = nOp - (opcodes.OP_1 - 1) | ||
assert(signatures.length >= m, 'Not enough signatures provided') | ||
assert(signatures.length <= n, 'Too many signatures provided') | ||
} | ||
@@ -246,0 +250,0 @@ |
@@ -40,6 +40,4 @@ var assert = require('assert') | ||
if (typeof tx === 'string') { | ||
hash = new Buffer(tx, 'hex') | ||
// TxId hex is big-endian, we need little-endian | ||
Array.prototype.reverse.call(hash) | ||
hash = bufferutils.reverse(new Buffer(tx, 'hex')) | ||
@@ -55,4 +53,6 @@ } else if (tx instanceof Transaction) { | ||
assert.equal(hash.length, 32, 'Expected hash length of 32, got ' + hash.length) | ||
assert.equal(typeof index, 'number', 'Expected number index, got ' + index) | ||
assert(isFinite(index), 'Expected number index, got ' + index) | ||
assert(isFinite(sequence), 'Expected number sequence, got ' + sequence) | ||
// Add the input and return the input's index | ||
return (this.ins.push({ | ||
@@ -83,10 +83,9 @@ hash: hash, | ||
if (scriptPubKey instanceof Address) { | ||
var address = scriptPubKey | ||
scriptPubKey = address.toOutputScript() | ||
scriptPubKey = scriptPubKey.toOutputScript() | ||
} | ||
assert(scriptPubKey instanceof Script, 'Expected Address or Script, got ' + scriptPubKey) | ||
assert.equal(typeof value, 'number', 'Expected number value, got ' + value) | ||
assert(isFinite(value), 'Expected number value, got ' + value) | ||
// Add the output and return the output's index | ||
return (this.outs.push({ | ||
@@ -168,3 +167,13 @@ script: scriptPubKey, | ||
*/ | ||
Transaction.prototype.hashForSignature = function(prevOutScript, inIndex, hashType) { | ||
Transaction.prototype.hashForSignature = function(inIndex, prevOutScript, hashType) { | ||
// FIXME: remove in 2.x.y | ||
if (arguments[0] instanceof Script) { | ||
console.warn('hashForSignature(prevOutScript, inIndex, ...) has been deprecated. Use hashForSignature(inIndex, prevOutScript, ...)') | ||
// swap the arguments (must be stored in tmp, arguments is special) | ||
var tmp = arguments[0] | ||
inIndex = arguments[1] | ||
prevOutScript = tmp | ||
} | ||
assert(inIndex >= 0, 'Invalid vin index') | ||
@@ -208,8 +217,4 @@ assert(inIndex < this.ins.length, 'Invalid vin index') | ||
Transaction.prototype.getId = function () { | ||
var buffer = this.getHash() | ||
// Big-endian is used for TxHash | ||
Array.prototype.reverse.call(buffer) | ||
return buffer.toString('hex') | ||
// TxHash is little-endian, we need big-endian | ||
return bufferutils.reverse(this.getHash()).toString('hex') | ||
} | ||
@@ -304,10 +309,13 @@ | ||
/** | ||
* Signs a pubKeyHash output at some index with the given key | ||
*/ | ||
Transaction.prototype.setInputScript = function(index, script) { | ||
this.ins[index].script = script | ||
} | ||
// FIXME: remove in 2.x.y | ||
Transaction.prototype.sign = function(index, privKey, hashType) { | ||
console.warn("Transaction.prototype.sign is deprecated. Use TransactionBuilder instead.") | ||
var prevOutScript = privKey.pub.getAddress().toOutputScript() | ||
var signature = this.signInput(index, prevOutScript, privKey, hashType) | ||
// FIXME: Assumed prior TX was pay-to-pubkey-hash | ||
var scriptSig = scripts.pubKeyHashInput(signature, privKey.pub) | ||
@@ -317,6 +325,9 @@ this.setInputScript(index, scriptSig) | ||
// FIXME: remove in 2.x.y | ||
Transaction.prototype.signInput = function(index, prevOutScript, privKey, hashType) { | ||
console.warn("Transaction.prototype.signInput is deprecated. Use TransactionBuilder instead.") | ||
hashType = hashType || Transaction.SIGHASH_ALL | ||
var hash = this.hashForSignature(prevOutScript, index, hashType) | ||
var hash = this.hashForSignature(index, prevOutScript, hashType) | ||
var signature = privKey.sign(hash) | ||
@@ -327,10 +338,8 @@ | ||
Transaction.prototype.setInputScript = function(index, script) { | ||
this.ins[index].script = script | ||
} | ||
// FIXME: remove in 2.x.y | ||
Transaction.prototype.validateInput = function(index, prevOutScript, pubKey, buffer) { | ||
console.warn("Transaction.prototype.validateInput is deprecated. Use TransactionBuilder instead.") | ||
// FIXME: could be validateInput(index, prevTxOut, pub) | ||
Transaction.prototype.validateInput = function(index, prevOutScript, pubKey, buffer) { | ||
var parsed = ECSignature.parseScriptSignature(buffer) | ||
var hash = this.hashForSignature(prevOutScript, index, parsed.hashType) | ||
var hash = this.hashForSignature(index, prevOutScript, parsed.hashType) | ||
@@ -337,0 +346,0 @@ return pubKey.verify(hash, parsed.signature) |
var assert = require('assert') | ||
var bufferutils = require('./bufferutils') | ||
var crypto = require('crypto') | ||
@@ -7,29 +8,35 @@ var networks = require('./networks') | ||
var HDNode = require('./hdnode') | ||
var Transaction = require('./transaction') | ||
var TransactionBuilder = require('./transaction_builder') | ||
var Script = require('./script') | ||
function Wallet(seed, network) { | ||
seed = seed || crypto.randomBytes(32) | ||
network = network || networks.bitcoin | ||
// Stored in a closure to make accidental serialization less likely | ||
var masterkey = null | ||
var me = this | ||
var accountZero = null | ||
var internalAccount = null | ||
var externalAccount = null | ||
var masterKey = HDNode.fromSeedBuffer(seed, network) | ||
// Addresses | ||
// HD first-level child derivation method should be hardened | ||
// See https://bitcointalk.org/index.php?topic=405179.msg4415254#msg4415254 | ||
var accountZero = masterKey.deriveHardened(0) | ||
var externalAccount = accountZero.derive(0) | ||
var internalAccount = accountZero.derive(1) | ||
this.addresses = [] | ||
this.changeAddresses = [] | ||
this.network = network | ||
this.unspents = [] | ||
// Transaction output data | ||
this.outputs = {} | ||
// FIXME: remove in 2.0.0 | ||
this.unspentMap = {} | ||
// Make a new master key | ||
// FIXME: remove in 2.0.0 | ||
var me = this | ||
this.newMasterKey = function(seed) { | ||
console.warn('newMasterKey is deprecated, please make a new Wallet instance instead') | ||
seed = seed || crypto.randomBytes(32) | ||
masterkey = HDNode.fromSeedBuffer(seed, network) | ||
masterKey = HDNode.fromSeedBuffer(seed, network) | ||
// HD first-level child derivation method should be hardened | ||
// See https://bitcointalk.org/index.php?topic=405179.msg4415254#msg4415254 | ||
accountZero = masterkey.deriveHardened(0) | ||
accountZero = masterKey.deriveHardened(0) | ||
externalAccount = accountZero.derive(0) | ||
@@ -41,266 +48,311 @@ internalAccount = accountZero.derive(1) | ||
me.outputs = {} | ||
me.unspents = [] | ||
me.unspentMap = {} | ||
} | ||
this.newMasterKey(seed) | ||
this.getMasterKey = function() { return masterKey } | ||
this.getAccountZero = function() { return accountZero } | ||
this.getExternalAccount = function() { return externalAccount } | ||
this.getInternalAccount = function() { return internalAccount } | ||
} | ||
this.generateAddress = function() { | ||
var key = externalAccount.derive(this.addresses.length) | ||
this.addresses.push(key.getAddress().toString()) | ||
return this.addresses[this.addresses.length - 1] | ||
} | ||
Wallet.prototype.createTransaction = function(to, value, options) { | ||
// FIXME: remove in 2.0.0 | ||
if (typeof options !== 'object') { | ||
if (options !== undefined) { | ||
console.warn('Non options object parameters are deprecated, use options object instead') | ||
this.generateChangeAddress = function() { | ||
var key = internalAccount.derive(this.changeAddresses.length) | ||
this.changeAddresses.push(key.getAddress().toString()) | ||
return this.changeAddresses[this.changeAddresses.length - 1] | ||
options = { | ||
fixedFee: arguments[2], | ||
changeAddress: arguments[3] | ||
} | ||
} | ||
} | ||
this.getBalance = function() { | ||
return this.getUnspentOutputs().reduce(function(memo, output){ | ||
return memo + output.value | ||
}, 0) | ||
} | ||
options = options || {} | ||
this.getUnspentOutputs = function() { | ||
var utxo = [] | ||
assert(value > this.network.dustThreshold, value + ' must be above dust threshold (' + this.network.dustThreshold + ' Satoshis)') | ||
for(var key in this.outputs){ | ||
var output = this.outputs[key] | ||
if(!output.to) utxo.push(outputToUnspentOutput(output)) | ||
} | ||
var changeAddress = options.changeAddress | ||
var fixedFee = options.fixedFee | ||
var minConf = options.minConf === undefined ? 0 : options.minConf // FIXME: change minConf:1 by default in 2.0.0 | ||
return utxo | ||
} | ||
// filter by minConf, then pending and sort by descending value | ||
var unspents = this.unspents.filter(function(unspent) { | ||
return unspent.confirmations >= minConf | ||
}).filter(function(unspent) { | ||
return !unspent.pending | ||
}).sort(function(o1, o2) { | ||
return o2.value - o1.value | ||
}) | ||
this.setUnspentOutputs = function(utxo) { | ||
var outputs = {} | ||
var accum = 0 | ||
var addresses = [] | ||
var subTotal = value | ||
utxo.forEach(function(uo){ | ||
validateUnspentOutput(uo) | ||
var o = unspentOutputToOutput(uo) | ||
outputs[o.from] = o | ||
}) | ||
var txb = new TransactionBuilder() | ||
txb.addOutput(to, value) | ||
this.outputs = outputs | ||
} | ||
for (var i = 0; i < unspents.length; ++i) { | ||
var unspent = unspents[i] | ||
addresses.push(unspent.address) | ||
function outputToUnspentOutput(output){ | ||
var hashAndIndex = output.from.split(":") | ||
txb.addInput(unspent.txHash, unspent.index) | ||
return { | ||
hash: hashAndIndex[0], | ||
outputIndex: parseInt(hashAndIndex[1]), | ||
address: output.address, | ||
value: output.value, | ||
pending: output.pending | ||
} | ||
} | ||
var fee = fixedFee === undefined ? estimatePaddedFee(txb.buildIncomplete(), this.network) : fixedFee | ||
function unspentOutputToOutput(o) { | ||
var hash = o.hash | ||
var key = hash + ":" + o.outputIndex | ||
return { | ||
from: key, | ||
address: o.address, | ||
value: o.value, | ||
pending: o.pending | ||
} | ||
} | ||
accum += unspent.value | ||
subTotal = value + fee | ||
function validateUnspentOutput(uo) { | ||
var missingField | ||
if (accum >= subTotal) { | ||
var change = accum - subTotal | ||
if (isNullOrUndefined(uo.hash)) { | ||
missingField = "hash" | ||
} | ||
var requiredKeys = ['outputIndex', 'address', 'value'] | ||
requiredKeys.forEach(function (key) { | ||
if (isNullOrUndefined(uo[key])){ | ||
missingField = key | ||
if (change > this.network.dustThreshold) { | ||
txb.addOutput(changeAddress || this.getChangeAddress(), change) | ||
} | ||
}) | ||
if (missingField) { | ||
var message = [ | ||
'Invalid unspent output: key', missingField, 'is missing.', | ||
'A valid unspent output must contain' | ||
] | ||
message.push(requiredKeys.join(', ')) | ||
message.push("and hash") | ||
throw new Error(message.join(' ')) | ||
break | ||
} | ||
} | ||
function isNullOrUndefined(value) { | ||
return value == undefined | ||
} | ||
assert(accum >= subTotal, 'Not enough funds (incl. fee): ' + accum + ' < ' + subTotal) | ||
this.processPendingTx = function(tx){ | ||
processTx(tx, true) | ||
} | ||
return this.signWith(txb, addresses).build() | ||
} | ||
this.processConfirmedTx = function(tx){ | ||
processTx(tx, false) | ||
} | ||
// FIXME: remove in 2.0.0 | ||
Wallet.prototype.processPendingTx = function(tx){ | ||
this.__processTx(tx, true) | ||
} | ||
function processTx(tx, isPending) { | ||
var txid = tx.getId() | ||
// FIXME: remove in 2.0.0 | ||
Wallet.prototype.processConfirmedTx = function(tx){ | ||
this.__processTx(tx, false) | ||
} | ||
tx.outs.forEach(function(txOut, i) { | ||
var address | ||
// FIXME: remove in 2.0.0 | ||
Wallet.prototype.__processTx = function(tx, isPending) { | ||
console.warn('processTransaction is considered harmful, see issue #260 for more information') | ||
try { | ||
address = Address.fromOutputScript(txOut.script, network).toString() | ||
} catch(e) { | ||
if (!(e.message.match(/has no matching Address/))) throw e | ||
} | ||
var txId = tx.getId() | ||
var txHash = tx.getHash() | ||
if (isMyAddress(address)) { | ||
var output = txid + ':' + i | ||
tx.outs.forEach(function(txOut, i) { | ||
var address | ||
me.outputs[output] = { | ||
from: output, | ||
value: txOut.value, | ||
address: address, | ||
pending: isPending | ||
} | ||
try { | ||
address = Address.fromOutputScript(txOut.script, this.network).toString() | ||
} catch(e) { | ||
if (!(e.message.match(/has no matching Address/))) throw e | ||
} | ||
var myAddresses = this.addresses.concat(this.changeAddresses) | ||
if (myAddresses.indexOf(address) > -1) { | ||
var lookup = txId + ':' + i | ||
if (lookup in this.unspentMap) return | ||
// its unique, add it | ||
var unspent = { | ||
address: address, | ||
confirmations: 0, // no way to determine this without more information | ||
index: i, | ||
txHash: txHash, | ||
txId: txId, | ||
value: txOut.value, | ||
pending: isPending | ||
} | ||
}) | ||
tx.ins.forEach(function(txIn, i) { | ||
// copy and convert to big-endian hex | ||
var txinId = new Buffer(txIn.hash) | ||
Array.prototype.reverse.call(txinId) | ||
txinId = txinId.toString('hex') | ||
this.unspentMap[lookup] = unspent | ||
this.unspents.push(unspent) | ||
} | ||
}, this) | ||
var output = txinId + ':' + txIn.index | ||
tx.ins.forEach(function(txIn, i) { | ||
// copy and convert to big-endian hex | ||
var txInId = bufferutils.reverse(txIn.hash).toString('hex') | ||
if (!(output in me.outputs)) return | ||
var lookup = txInId + ':' + txIn.index | ||
if (!(lookup in this.unspentMap)) return | ||
if (isPending) { | ||
me.outputs[output].to = txid + ':' + i | ||
me.outputs[output].pending = true | ||
} else { | ||
delete me.outputs[output] | ||
} | ||
}) | ||
} | ||
var unspent = this.unspentMap[lookup] | ||
this.createTx = function(to, value, fixedFee, changeAddress) { | ||
assert(value > network.dustThreshold, value + ' must be above dust threshold (' + network.dustThreshold + ' Satoshis)') | ||
if (isPending) { | ||
unspent.pending = true | ||
unspent.spent = true | ||
var utxos = getCandidateOutputs(value) | ||
var accum = 0 | ||
var subTotal = value | ||
var addresses = [] | ||
} else { | ||
delete this.unspentMap[lookup] | ||
var tx = new Transaction() | ||
tx.addOutput(to, value) | ||
this.unspents = this.unspents.filter(function(unspent2) { | ||
return unspent !== unspent2 | ||
}) | ||
} | ||
}, this) | ||
} | ||
for (var i = 0; i < utxos.length; ++i) { | ||
var utxo = utxos[i] | ||
addresses.push(utxo.address) | ||
Wallet.prototype.generateAddress = function() { | ||
var k = this.addresses.length | ||
var address = this.getExternalAccount().derive(k).getAddress() | ||
var outpoint = utxo.from.split(':') | ||
tx.addInput(outpoint[0], parseInt(outpoint[1])) | ||
this.addresses.push(address.toString()) | ||
var fee = fixedFee == undefined ? estimateFeePadChangeOutput(tx) : fixedFee | ||
return this.getReceiveAddress() | ||
} | ||
accum += utxo.value | ||
subTotal = value + fee | ||
if (accum >= subTotal) { | ||
var change = accum - subTotal | ||
Wallet.prototype.generateChangeAddress = function() { | ||
var k = this.changeAddresses.length | ||
var address = this.getInternalAccount().derive(k).getAddress() | ||
if (change > network.dustThreshold) { | ||
tx.addOutput(changeAddress || getChangeAddress(), change) | ||
} | ||
this.changeAddresses.push(address.toString()) | ||
break | ||
} | ||
} | ||
return this.getChangeAddress() | ||
} | ||
assert(accum >= subTotal, 'Not enough funds (incl. fee): ' + accum + ' < ' + subTotal) | ||
this.signWith(tx, addresses) | ||
return tx | ||
Wallet.prototype.getAddress = function() { | ||
if (this.addresses.length === 0) { | ||
this.generateAddress() | ||
} | ||
function getCandidateOutputs() { | ||
var unspent = [] | ||
return this.addresses[this.addresses.length - 1] | ||
} | ||
for (var key in me.outputs) { | ||
var output = me.outputs[key] | ||
if (!output.pending) unspent.push(output) | ||
} | ||
Wallet.prototype.getBalance = function(minConf) { | ||
minConf = minConf || 0 | ||
var sortByValueDesc = unspent.sort(function(o1, o2){ | ||
return o2.value - o1.value | ||
}) | ||
return this.unspents.filter(function(unspent) { | ||
return unspent.confirmations >= minConf | ||
return sortByValueDesc | ||
// FIXME: remove spent filter in 2.0.0 | ||
}).filter(function(unspent) { | ||
return !unspent.spent | ||
}).reduce(function(accum, unspent) { | ||
return accum + unspent.value | ||
}, 0) | ||
} | ||
Wallet.prototype.getChangeAddress = function() { | ||
if (this.changeAddresses.length === 0) { | ||
this.generateChangeAddress() | ||
} | ||
function estimateFeePadChangeOutput(tx) { | ||
var tmpTx = tx.clone() | ||
tmpTx.addOutput(getChangeAddress(), network.dustSoftThreshold || 0) | ||
return this.changeAddresses[this.changeAddresses.length - 1] | ||
} | ||
return network.estimateFee(tmpTx) | ||
Wallet.prototype.getInternalPrivateKey = function(index) { | ||
return this.getInternalAccount().derive(index).privKey | ||
} | ||
Wallet.prototype.getPrivateKey = function(index) { | ||
return this.getExternalAccount().derive(index).privKey | ||
} | ||
Wallet.prototype.getPrivateKeyForAddress = function(address) { | ||
var index | ||
if ((index = this.addresses.indexOf(address)) > -1) { | ||
return this.getPrivateKey(index) | ||
} | ||
function getChangeAddress() { | ||
if(me.changeAddresses.length === 0) me.generateChangeAddress(); | ||
return me.changeAddresses[me.changeAddresses.length - 1] | ||
if ((index = this.changeAddresses.indexOf(address)) > -1) { | ||
return this.getInternalPrivateKey(index) | ||
} | ||
this.signWith = function(tx, addresses) { | ||
assert.equal(tx.ins.length, addresses.length, 'Number of addresses must match number of transaction inputs') | ||
assert(false, 'Unknown address. Make sure the address is from the keychain and has been generated') | ||
} | ||
addresses.forEach(function(address, i) { | ||
var key = me.getPrivateKeyForAddress(address) | ||
Wallet.prototype.getUnspentOutputs = function(minConf) { | ||
minConf = minConf || 0 | ||
tx.sign(i, key) | ||
}) | ||
return this.unspents.filter(function(unspent) { | ||
return unspent.confirmations >= minConf | ||
return tx | ||
} | ||
// FIXME: remove spent filter in 2.0.0 | ||
}).filter(function(unspent) { | ||
return !unspent.spent | ||
}).map(function(unspent) { | ||
return { | ||
address: unspent.address, | ||
confirmations: unspent.confirmations, | ||
index: unspent.index, | ||
txId: unspent.txId, | ||
value: unspent.value, | ||
this.getMasterKey = function() { return masterkey } | ||
this.getAccountZero = function() { return accountZero } | ||
this.getInternalAccount = function() { return internalAccount } | ||
this.getExternalAccount = function() { return externalAccount } | ||
// FIXME: remove in 2.0.0 | ||
hash: unspent.txId, | ||
pending: unspent.pending | ||
} | ||
}) | ||
} | ||
this.getPrivateKey = function(index) { | ||
return externalAccount.derive(index).privKey | ||
} | ||
Wallet.prototype.setUnspentOutputs = function(unspents) { | ||
this.unspentMap = {} | ||
this.unspents = unspents.map(function(unspent) { | ||
// FIXME: remove unspent.hash in 2.0.0 | ||
var txId = unspent.txId || unspent.hash | ||
var index = unspent.index | ||
this.getInternalPrivateKey = function(index) { | ||
return internalAccount.derive(index).privKey | ||
} | ||
// FIXME: remove in 2.0.0 | ||
if (unspent.hash !== undefined) { | ||
console.warn('unspent.hash is deprecated, use unspent.txId instead') | ||
} | ||
this.getPrivateKeyForAddress = function(address) { | ||
var index | ||
if((index = this.addresses.indexOf(address)) > -1) { | ||
return this.getPrivateKey(index) | ||
} else if((index = this.changeAddresses.indexOf(address)) > -1) { | ||
return this.getInternalPrivateKey(index) | ||
} else { | ||
throw new Error('Unknown address. Make sure the address is from the keychain and has been generated.') | ||
// FIXME: remove in 2.0.0 | ||
if (index === undefined) { | ||
console.warn('unspent.outputIndex is deprecated, use unspent.index instead') | ||
index = unspent.outputIndex | ||
} | ||
} | ||
function isReceiveAddress(address){ | ||
return me.addresses.indexOf(address) > -1 | ||
} | ||
assert.equal(typeof txId, 'string', 'Expected txId, got ' + txId) | ||
assert.equal(txId.length, 64, 'Expected valid txId, got ' + txId) | ||
assert.doesNotThrow(function() { Address.fromBase58Check(unspent.address) }, 'Expected Base58 Address, got ' + unspent.address) | ||
assert(isFinite(index), 'Expected number index, got ' + index) | ||
assert.equal(typeof unspent.value, 'number', 'Expected number value, got ' + unspent.value) | ||
function isChangeAddress(address){ | ||
return me.changeAddresses.indexOf(address) > -1 | ||
} | ||
// FIXME: remove branch in 2.0.0 | ||
if (unspent.confirmations !== undefined) { | ||
assert.equal(typeof unspent.confirmations, 'number', 'Expected number confirmations, got ' + unspent.confirmations) | ||
} | ||
function isMyAddress(address) { | ||
return isReceiveAddress(address) || isChangeAddress(address) | ||
} | ||
var txHash = bufferutils.reverse(new Buffer(txId, 'hex')) | ||
unspent = { | ||
address: unspent.address, | ||
confirmations: unspent.confirmations || 0, | ||
index: index, | ||
txHash: txHash, | ||
txId: txId, | ||
value: unspent.value, | ||
// FIXME: remove in 2.0.0 | ||
pending: unspent.pending || false | ||
} | ||
// FIXME: remove in 2.0.0 | ||
this.unspentMap[txId + ':' + index] = unspent | ||
return unspent | ||
}, this) | ||
} | ||
Wallet.prototype.signWith = function(tx, addresses) { | ||
addresses.forEach(function(address, i) { | ||
var privKey = this.getPrivateKeyForAddress(address) | ||
tx.sign(i, privKey) | ||
}, this) | ||
return tx | ||
} | ||
function estimatePaddedFee(tx, network) { | ||
var tmpTx = tx.clone() | ||
tmpTx.addOutput(Script.EMPTY, network.dustSoftThreshold || 0) | ||
return network.estimateFee(tmpTx) | ||
} | ||
// FIXME: 1.0.0 shims, remove in 2.0.0 | ||
Wallet.prototype.getReceiveAddress = Wallet.prototype.getAddress | ||
Wallet.prototype.createTx = Wallet.prototype.createTransaction | ||
module.exports = Wallet |
@@ -186,3 +186,3 @@ var assert = require('assert') | ||
try { | ||
actualHash = transaction.hashForSignature(script, inIndex, hashType) | ||
actualHash = transaction.hashForSignature(inIndex, script, hashType) | ||
} catch (e) { | ||
@@ -189,0 +189,0 @@ // don't fail if we don't support it yet, TODO |
@@ -78,2 +78,15 @@ var assert = require('assert') | ||
describe('reverse', function() { | ||
fixtures.valid.forEach(function(f) { | ||
it('reverses ' + f.hex64 + ' correctly', function() { | ||
var buffer = new Buffer(f.hex64, 'hex') | ||
var buffer2 = bufferutils.reverse(buffer) | ||
Array.prototype.reverse.call(buffer) | ||
assert.deepEqual(buffer, buffer2) | ||
}) | ||
}) | ||
}) | ||
describe('varIntSize', function() { | ||
@@ -80,0 +93,0 @@ fixtures.valid.forEach(function(f) { |
@@ -94,3 +94,3 @@ var assert = require('assert') | ||
assert.ok(pubKey.verify(hash, signature)) | ||
assert(pubKey.verify(hash, signature)) | ||
}) | ||
@@ -101,5 +101,5 @@ | ||
assert.ok(!pubKey.verify(hash, signature)) | ||
assert(!pubKey.verify(hash, signature)) | ||
}) | ||
}) | ||
}) |
@@ -104,2 +104,15 @@ { | ||
"scriptPubKey": false | ||
}, | ||
{ | ||
"exception": "Too many signatures provided", | ||
"pubKeys": [ | ||
"02359c6e3f04cefbf089cf1d6670dc47c3fb4df68e2bad1fa5a369f9ce4b42bbd1", | ||
"0395a9d84d47d524548f79f435758c01faec5da2b7e551d3b8c995b7e06326ae4a" | ||
], | ||
"signatures": [ | ||
"304402207515cf147d201f411092e6be5a64a6006f9308fad7b2a8fdaab22cd86ce764c202200974b8aca7bf51dbf54150d3884e1ae04f675637b926ec33bf75939446f6ca2801", | ||
"3045022100ef253c1faa39e65115872519e5f0a33bbecf430c0f35cf562beabbad4da24d8d02201742be8ee49812a73adea3007c9641ce6725c32cd44ddb8e3a3af460015d140501", | ||
"3045022100ef253c1faa39e65115872519e5f0a33bbecf430c0f35cf562beabbad4da24d8d02201742be8ee49812a73adea3007c9641ce6725c32cd44ddb8e3a3af460015d140501" | ||
], | ||
"scriptPubKey": false | ||
} | ||
@@ -106,0 +119,0 @@ ] |
@@ -52,4 +52,10 @@ var assert = require('assert') | ||
it('throws an exception when an unknown network is given', function() { | ||
it('throws when an invalid length chain code is given', function() { | ||
assert.throws(function() { | ||
new HDNode(d, chainCode.slice(0, 20), networks.testnet) | ||
}, /Expected chainCode length of 32, got 20/) | ||
}) | ||
it('throws when an unknown network is given', function() { | ||
assert.throws(function() { | ||
new HDNode(d, chainCode, {}) | ||
@@ -86,5 +92,5 @@ }, /Unknown BIP32 constants for network/) | ||
it('exports ' + f.master.base58 + ' (public) correctly', function() { | ||
var hd = HDNode.fromSeedHex(f.master.seed) | ||
var hd = HDNode.fromSeedHex(f.master.seed).neutered() | ||
assert.equal(hd.toBase58(false), f.master.base58) | ||
assert.equal(hd.toBase58(), f.master.base58) | ||
}) | ||
@@ -97,6 +103,7 @@ }) | ||
assert.equal(hd.toBase58(true), f.master.base58Priv) | ||
assert.equal(hd.toBase58(), f.master.base58Priv) | ||
}) | ||
}) | ||
// FIXME: remove in 2.x.y | ||
it('fails when there is no private key', function() { | ||
@@ -166,5 +173,5 @@ var hd = HDNode.fromBase58(fixtures.valid[0].master.base58) | ||
it('exports ' + f.master.hex + ' (public) correctly', function() { | ||
var hd = HDNode.fromSeedHex(f.master.seed) | ||
var hd = HDNode.fromSeedHex(f.master.seed).neutered() | ||
assert.equal(hd.toHex(false), f.master.hex) | ||
assert.equal(hd.toHex(), f.master.hex) | ||
}) | ||
@@ -177,6 +184,7 @@ }) | ||
assert.equal(hd.toHex(true), f.master.hexPriv) | ||
assert.equal(hd.toHex(), f.master.hexPriv) | ||
}) | ||
}) | ||
// FIXME: remove in 2.x.y | ||
it('fails when there is no private key', function() { | ||
@@ -228,2 +236,17 @@ var hd = HDNode.fromHex(fixtures.valid[0].master.hex) | ||
describe('neutered', function() { | ||
var f = fixtures.valid[0] | ||
it('strips all private information', function() { | ||
var hd = HDNode.fromBase58(f.master.base58) | ||
var hdn = hd.neutered() | ||
assert.equal(hdn.privKey, undefined) | ||
assert.equal(hdn.pubKey.toHex(), hd.pubKey.toHex()) | ||
assert.equal(hdn.chainCode, hd.chainCode) | ||
assert.equal(hdn.depth, hd.depth) | ||
assert.equal(hdn.index, hd.index) | ||
}) | ||
}) | ||
describe('derive', function() { | ||
@@ -265,8 +288,6 @@ function verifyVector(hd, v, depth) { | ||
var parentNode = HDNode.fromBase58(f.master.base58Priv) | ||
var child = parentNode.derive(c.m) | ||
var master = HDNode.fromBase58(f.master.base58Priv) | ||
var child = master.derive(c.m).neutered() | ||
// FIXME: N(CKDpriv((kpar, cpar), i)), could be done better... | ||
var childNeutered = HDNode.fromBase58(child.toBase58(false)) // neuter | ||
assert.equal(childNeutered.toBase58(), c.base58) | ||
assert.equal(child.toBase58(), c.base58) | ||
}) | ||
@@ -278,8 +299,6 @@ | ||
var parentNode = HDNode.fromBase58(f.master.base58Priv) | ||
var child = parentNode.deriveHardened(c.m) | ||
var master = HDNode.fromBase58(f.master.base58Priv) | ||
var child = master.deriveHardened(c.m).neutered() | ||
// FIXME: N(CKDpriv((kpar, cpar), i)), could be done better... | ||
var childNeutered = HDNode.fromBase58(child.toBase58(false)) // neuter | ||
assert.equal(childNeutered.toBase58(), c.base58) | ||
assert.equal(child.toBase58(), c.base58) | ||
}) | ||
@@ -291,4 +310,4 @@ | ||
var parentNode = HDNode.fromBase58(f.master.base58) | ||
var child = parentNode.derive(c.m) | ||
var master = HDNode.fromBase58(f.master.base58) | ||
var child = master.derive(c.m) | ||
@@ -302,6 +321,6 @@ assert.equal(child.toBase58(), c.base58) | ||
var parentNode = HDNode.fromBase58(f.master.base58) | ||
var master = HDNode.fromBase58(f.master.base58) | ||
assert.throws(function() { | ||
parentNode.deriveHardened(c.m) | ||
master.deriveHardened(c.m) | ||
}, /Could not derive hardened child key/) | ||
@@ -308,0 +327,0 @@ }) |
var assert = require('assert') | ||
var bitcoin = require('../../') | ||
var crypto = bitcoin.crypto | ||
var networks = bitcoin.networks | ||
@@ -10,4 +9,3 @@ var scripts = bitcoin.scripts | ||
var ECKey = bitcoin.ECKey | ||
var Transaction = bitcoin.Transaction | ||
var Script = bitcoin.Script | ||
var TransactionBuilder = bitcoin.TransactionBuilder | ||
@@ -47,23 +45,19 @@ var helloblock = require('helloblock-js')({ | ||
// get latest unspents from the multisigAddress | ||
helloblock.addresses.getUnspents(multisigAddress, function(err, resp, resource) { | ||
helloblock.addresses.getUnspents(multisigAddress, function(err, res, unspents) { | ||
if (err) return done(err) | ||
// use the oldest unspent | ||
var unspent = resource[resource.length - 1] | ||
var unspent = unspents[unspents.length - 1] | ||
var spendAmount = Math.min(unspent.value, outputAmount) | ||
var tx = new Transaction() | ||
tx.addInput(unspent.txHash, unspent.index) | ||
tx.addOutput(targetAddress, spendAmount) | ||
var txb = new TransactionBuilder() | ||
txb.addInput(unspent.txHash, unspent.index) | ||
txb.addOutput(targetAddress, spendAmount) | ||
var signatures = privKeys.map(function(privKey) { | ||
return tx.signInput(0, redeemScript, privKey) | ||
privKeys.forEach(function(privKey) { | ||
txb.sign(0, privKey, redeemScript) | ||
}) | ||
var redeemScriptSig = scripts.multisigInput(signatures) | ||
var scriptSig = scripts.scriptHashInput(redeemScriptSig, redeemScript) | ||
tx.setInputScript(0, scriptSig) | ||
// broadcast our transaction | ||
helloblock.transactions.propagate(tx.toHex(), function(err, resp, resource) { | ||
helloblock.transactions.propagate(txb.build().toHex(), function(err, res) { | ||
// no err means that the transaction has been successfully propagated | ||
@@ -73,6 +67,6 @@ if (err) return done(err) | ||
// Check that the funds (spendAmount Satoshis) indeed arrived at the intended address | ||
helloblock.addresses.get(targetAddress, function(err, resp, resource) { | ||
helloblock.addresses.get(targetAddress, function(err, res, addrInfo) { | ||
if (err) return done(err) | ||
assert.equal(resource.balance, spendAmount) | ||
assert.equal(addrInfo.balance, spendAmount) | ||
done() | ||
@@ -79,0 +73,0 @@ }) |
@@ -29,4 +29,3 @@ var assert = require('assert') | ||
var address = Address.fromBase58Check(f.address) | ||
var signature = new Buffer(f.signature, 'base64') | ||
assert.ok(Message.verify(address, signature, f.message, network)) | ||
assert(Message.verify(address, f.signature, f.message, network)) | ||
}) | ||
@@ -38,9 +37,7 @@ | ||
var signature = new Buffer(f.signature, 'base64') | ||
assert.ok(Message.verify(f.address, signature, f.message, network)) | ||
var signature = f.signature | ||
assert(Message.verify(f.address, f.signature, f.message, network)) | ||
if (f.compressed) { | ||
var compressedSignature = new Buffer(f.compressed.signature, 'base64') | ||
assert.ok(Message.verify(f.compressed.address, compressedSignature, f.message, network)) | ||
assert(Message.verify(f.compressed.address, f.compressed.signature, f.message, network)) | ||
} | ||
@@ -52,4 +49,3 @@ }) | ||
it(f.description, function() { | ||
var signature = new Buffer(f.signature, 'base64') | ||
assert.ok(!Message.verify(f.address, signature, f.message)) | ||
assert(!Message.verify(f.address, f.signature, f.message)) | ||
}) | ||
@@ -56,0 +52,0 @@ }) |
@@ -224,3 +224,3 @@ var assert = require('assert') | ||
// FIXME: could be better | ||
// FIXME: remove in 2.x.y | ||
describe('signInput/validateInput', function() { | ||
@@ -227,0 +227,0 @@ it('works for multi-sig redeem script', function() { |
var assert = require('assert') | ||
var bufferutils = require('../src/bufferutils') | ||
var crypto = require('../src/crypto') | ||
@@ -10,2 +11,3 @@ var networks = require('../src/networks') | ||
var Transaction = require('../src/transaction') | ||
var TransactionBuilder = require('../src/transaction_builder') | ||
var Wallet = require('../src/wallet') | ||
@@ -30,9 +32,13 @@ | ||
describe('Wallet', function() { | ||
var seed, wallet | ||
beforeEach(function(){ | ||
var seed | ||
beforeEach(function() { | ||
seed = crypto.sha256("don't use a string seed like this in real life") | ||
wallet = new Wallet(seed) | ||
}) | ||
describe('constructor', function() { | ||
var wallet | ||
beforeEach(function() { | ||
wallet = new Wallet(seed) | ||
}) | ||
it('defaults to Bitcoin network', function() { | ||
@@ -60,6 +66,6 @@ assert.equal(wallet.getMasterKey().network, networks.bitcoin) | ||
describe('when seed is not specified', function(){ | ||
it('generates a seed', function(){ | ||
describe('when seed is not specified', function() { | ||
it('generates a seed', function() { | ||
var wallet = new Wallet() | ||
assert.ok(wallet.getMasterKey()) | ||
assert(wallet.getMasterKey()) | ||
}) | ||
@@ -79,4 +85,4 @@ }) | ||
describe('newMasterKey', function(){ | ||
it('resets accounts', function(){ | ||
describe('newMasterKey', function() { | ||
it('resets accounts', function() { | ||
var wallet = new Wallet() | ||
@@ -93,3 +99,3 @@ var oldAccountZero = wallet.getAccountZero() | ||
it('resets addresses', function(){ | ||
it('resets addresses', function() { | ||
var wallet = new Wallet() | ||
@@ -109,4 +115,4 @@ wallet.generateAddress() | ||
describe('generateAddress', function(){ | ||
it('generate receiving addresses', function(){ | ||
describe('generateAddress', function() { | ||
it('generate receiving addresses', function() { | ||
var wallet = new Wallet(seed, networks.testnet) | ||
@@ -124,4 +130,9 @@ var expectedAddresses = [ | ||
describe('generateChangeAddress', function(){ | ||
it('generates change addresses', function(){ | ||
describe('generateChangeAddress', function() { | ||
var wallet | ||
beforeEach(function() { | ||
wallet = new Wallet(seed) | ||
}) | ||
it('generates change addresses', function() { | ||
var wallet = new Wallet(seed, networks.testnet) | ||
@@ -135,4 +146,9 @@ var expectedAddresses = ["mnXiDR4MKsFxcKJEZjx4353oXvo55iuptn"] | ||
describe('getPrivateKey', function(){ | ||
it('returns the private key at the given index of external account', function(){ | ||
describe('getPrivateKey', function() { | ||
var wallet | ||
beforeEach(function() { | ||
wallet = new Wallet(seed) | ||
}) | ||
it('returns the private key at the given index of external account', function() { | ||
var wallet = new Wallet(seed, networks.testnet) | ||
@@ -145,4 +161,9 @@ | ||
describe('getInternalPrivateKey', function(){ | ||
it('returns the private key at the given index of internal account', function(){ | ||
describe('getInternalPrivateKey', function() { | ||
var wallet | ||
beforeEach(function() { | ||
wallet = new Wallet(seed) | ||
}) | ||
it('returns the private key at the given index of internal account', function() { | ||
var wallet = new Wallet(seed, networks.testnet) | ||
@@ -155,4 +176,9 @@ | ||
describe('getPrivateKeyForAddress', function(){ | ||
it('returns the private key for the given address', function(){ | ||
describe('getPrivateKeyForAddress', function() { | ||
var wallet | ||
beforeEach(function() { | ||
wallet = new Wallet(seed) | ||
}) | ||
it('returns the private key for the given address', function() { | ||
var wallet = new Wallet(seed, networks.testnet) | ||
@@ -173,45 +199,50 @@ wallet.generateChangeAddress() | ||
it('raises an error when address is not found', function(){ | ||
it('raises an error when address is not found', function() { | ||
var wallet = new Wallet(seed, networks.testnet) | ||
assert.throws(function() { | ||
wallet.getPrivateKeyForAddress("n2fiWrHqD6GM5GiEqkbWAc6aaZQp3ba93X") | ||
}, /Unknown address. Make sure the address is from the keychain and has been generated./) | ||
}, /Unknown address. Make sure the address is from the keychain and has been generated/) | ||
}) | ||
}) | ||
describe('Unspent Outputs', function(){ | ||
var expectedUtxo, expectedOutputKey | ||
beforeEach(function(){ | ||
expectedUtxo = { | ||
"hash":"6a4062273ac4f9ea4ffca52d9fd102b08f6c32faa0a4d1318e3a7b2e437bb9c7", | ||
"outputIndex": 0, | ||
describe('Unspent Outputs', function() { | ||
var utxo, expectedOutputKey | ||
var wallet | ||
beforeEach(function() { | ||
utxo = { | ||
"address" : "1AZpKpcfCzKDUeTFBQUL4MokQai3m3HMXv", | ||
"confirmations": 1, | ||
"index": 0, | ||
"txId": fakeTxId(6), | ||
"value": 20000, | ||
"pending": true | ||
"pending": false | ||
} | ||
expectedOutputKey = expectedUtxo.hash + ":" + expectedUtxo.outputIndex | ||
}) | ||
function addUtxoToOutput(utxo){ | ||
var key = utxo.hash + ":" + utxo.outputIndex | ||
wallet.outputs[key] = { | ||
from: key, | ||
address: utxo.address, | ||
value: utxo.value, | ||
pending: utxo.pending | ||
} | ||
} | ||
describe('on construction', function() { | ||
beforeEach(function() { | ||
wallet = new Wallet(seed, networks.bitcoin) | ||
wallet.setUnspentOutputs([utxo]) | ||
}) | ||
describe('getBalance', function(){ | ||
var utxo1 | ||
it('matches the expected behaviour', function() { | ||
var output = wallet.unspents[0] | ||
beforeEach(function(){ | ||
utxo1 = cloneObject(expectedUtxo) | ||
utxo1.hash = utxo1.hash.replace('7', 'l') | ||
assert.equal(output.address, utxo.address) | ||
assert.equal(output.value, utxo.value) | ||
}) | ||
}) | ||
it('sums over utxo values', function(){ | ||
addUtxoToOutput(expectedUtxo) | ||
addUtxoToOutput(utxo1) | ||
describe('getBalance', function() { | ||
beforeEach(function() { | ||
var utxo1 = cloneObject(utxo) | ||
utxo1.hash = fakeTxId(5) | ||
wallet = new Wallet(seed, networks.bitcoin) | ||
wallet.setUnspentOutputs([utxo, utxo1]) | ||
}) | ||
it('sums over utxo values', function() { | ||
assert.equal(wallet.getBalance(), 40000) | ||
@@ -221,56 +252,81 @@ }) | ||
describe('getUnspentOutputs', function(){ | ||
beforeEach(function(){ | ||
addUtxoToOutput(expectedUtxo) | ||
describe('getUnspentOutputs', function() { | ||
beforeEach(function() { | ||
wallet = new Wallet(seed, networks.bitcoin) | ||
wallet.setUnspentOutputs([utxo]) | ||
}) | ||
it('parses wallet outputs to the expect format', function(){ | ||
assert.deepEqual(wallet.getUnspentOutputs(), [expectedUtxo]) | ||
it('parses wallet unspents to the expected format', function() { | ||
var outputs = wallet.getUnspentOutputs() | ||
var output = outputs[0] | ||
assert.equal(utxo.address, output.address) | ||
assert.equal(utxo.index, output.index) | ||
assert.equal(utxo.value, output.value) | ||
// FIXME: remove in 2.0.0 | ||
assert.equal(utxo.txId, output.hash) | ||
assert.equal(utxo.pending, output.pending) | ||
// new in 2.0.0 | ||
assert.equal(utxo.txId, output.txId) | ||
assert.equal(utxo.confirmations, output.confirmations) | ||
}) | ||
it("ignores pending spending outputs (outputs with 'to' property)", function(){ | ||
var output = wallet.outputs[expectedOutputKey] | ||
output.to = fakeTxId(0) + ':' + 0 | ||
output.pending = true | ||
it("ignores spent unspents (outputs with 'spent' property)", function() { | ||
var unspent = wallet.unspents[0] | ||
unspent.pending = true | ||
unspent.spent = true | ||
assert.deepEqual(wallet.getUnspentOutputs(), []) | ||
}) | ||
}) | ||
}) | ||
describe('setUnspentOutputs', function(){ | ||
var utxo | ||
beforeEach(function(){ | ||
utxo = cloneObject([expectedUtxo]) | ||
}) | ||
describe('setUnspentOutputs', function() { | ||
var utxo | ||
var expectedOutputKey | ||
var wallet | ||
it('matches the expected behaviour', function(){ | ||
wallet.setUnspentOutputs(utxo) | ||
verifyOutputs() | ||
}) | ||
beforeEach(function() { | ||
utxo = { | ||
hash: fakeTxId(0), | ||
index: 0, | ||
address: '115qa7iPZqn6as57hxLL8E9VUnhmGQxKWi', | ||
value: 500000 | ||
} | ||
describe('required fields', function(){ | ||
['outputIndex', 'address', 'hash', 'value'].forEach(function(field){ | ||
it("throws an error when " + field + " is missing", function(){ | ||
delete utxo[0][field] | ||
wallet = new Wallet(seed, networks.bitcoin) | ||
}) | ||
assert.throws(function() { | ||
wallet.setUnspentOutputs(utxo) | ||
}, new RegExp('Invalid unspent output: key ' + field + ' is missing')) | ||
it('matches the expected behaviour', function() { | ||
wallet.setUnspentOutputs([utxo]) | ||
var output = wallet.unspents[0] | ||
assert.equal(output.value, utxo.value) | ||
assert.equal(output.address, utxo.address) | ||
}) | ||
describe('required fields', function() { | ||
['index', 'address', 'hash', 'value'].forEach(function(field){ | ||
it("throws an error when " + field + " is missing", function() { | ||
delete utxo[field] | ||
assert.throws(function() { | ||
wallet.setUnspentOutputs([utxo]) | ||
}) | ||
}) | ||
}) | ||
function verifyOutputs() { | ||
var output = wallet.outputs[expectedOutputKey] | ||
assert(output) | ||
assert.equal(output.value, utxo[0].value) | ||
assert.equal(output.address, utxo[0].address) | ||
} | ||
}) | ||
}) | ||
describe('Process transaction', function(){ | ||
describe('Process transaction', function() { | ||
var wallet | ||
beforeEach(function() { | ||
wallet = new Wallet(seed) | ||
}) | ||
var addresses | ||
var tx | ||
beforeEach(function(){ | ||
beforeEach(function() { | ||
addresses = [ | ||
@@ -285,4 +341,4 @@ '115qa7iPZqn6as57hxLL8E9VUnhmGQxKWi', | ||
describe("processPendingTx", function(){ | ||
it("incoming: sets the pending flag on output", function(){ | ||
describe("processPendingTx", function() { | ||
it("incoming: sets the pending flag on output", function() { | ||
wallet.addresses = [addresses[0]] | ||
@@ -294,5 +350,5 @@ wallet.processPendingTx(tx) | ||
describe("when tx ins outpoint contains a known txhash:i", function(){ | ||
describe("when tx ins outpoint contains a known txhash:i", function() { | ||
var spendTx | ||
beforeEach(function(){ | ||
beforeEach(function() { | ||
wallet.addresses = [addresses[0]] | ||
@@ -304,3 +360,3 @@ wallet.processConfirmedTx(tx) | ||
it("outgoing: sets the pending flag and 'to' on output", function(){ | ||
it("outgoing: sets the pending flag and 'spent' on output", function() { | ||
var txIn = spendTx.ins[0] | ||
@@ -311,8 +367,8 @@ var txInId = new Buffer(txIn.hash) | ||
var key = txInId + ':' + txIn.index | ||
assert(!wallet.outputs[key].pending) | ||
var unspent = wallet.unspents[0] | ||
assert(!unspent.pending) | ||
wallet.processPendingTx(spendTx) | ||
assert(wallet.outputs[key].pending) | ||
assert.equal(wallet.outputs[key].to, spendTx.getId() + ':' + 0) | ||
assert(unspent.pending) | ||
assert(unspent.spent, true) | ||
}) | ||
@@ -322,3 +378,3 @@ }) | ||
describe('processConfirmedTx', function(){ | ||
describe('processConfirmedTx', function() { | ||
it('does not throw on scripts with no corresponding Address', function() { | ||
@@ -335,4 +391,4 @@ var pubKey = wallet.getPrivateKey(0).pub | ||
describe("when tx outs contains an address owned by the wallet, an 'output' gets added to wallet.outputs", function(){ | ||
it("works for receive address", function(){ | ||
describe("when tx outs contains an address owned by the wallet, an 'output' gets added to wallet.unspentMap", function() { | ||
it("works for receive address", function() { | ||
var totalOuts = outputCount() | ||
@@ -347,3 +403,3 @@ | ||
it("works for change address", function(){ | ||
it("works for change address", function() { | ||
var totalOuts = outputCount() | ||
@@ -358,10 +414,10 @@ wallet.changeAddresses = [addresses[1]] | ||
function outputCount(){ | ||
return Object.keys(wallet.outputs).length | ||
function outputCount() { | ||
return Object.keys(wallet.unspentMap).length | ||
} | ||
}) | ||
describe("when tx ins outpoint contains a known txhash:i", function(){ | ||
describe("when tx ins contains a known txhash:i", function() { | ||
var spendTx | ||
beforeEach(function(){ | ||
beforeEach(function() { | ||
wallet.addresses = [addresses[0]] // the address fixtureTx2 used as input | ||
@@ -373,32 +429,32 @@ wallet.processConfirmedTx(tx) | ||
it("does not add to wallet.outputs", function(){ | ||
it("does not add to wallet.unspentMap", function() { | ||
wallet.processConfirmedTx(spendTx) | ||
assert.deepEqual(wallet.outputs, {}) | ||
assert.deepEqual(wallet.unspentMap, {}) | ||
}) | ||
it("deletes corresponding 'output'", function(){ | ||
it("deletes corresponding 'unspent'", function() { | ||
var txIn = spendTx.ins[0] | ||
var txInId = new Buffer(txIn.hash) | ||
Array.prototype.reverse.call(txInId) | ||
txInId = txInId.toString('hex') | ||
var txInId = bufferutils.reverse(txIn.hash).toString('hex') | ||
var expected = txInId + ':' + txIn.index | ||
assert(expected in wallet.outputs) | ||
assert(expected in wallet.unspentMap) | ||
wallet.processConfirmedTx(spendTx) | ||
assert(!(expected in wallet.outputs)) | ||
assert(!(expected in wallet.unspentMap)) | ||
}) | ||
}) | ||
}) | ||
it("does nothing when none of the involved addresses belong to the wallet", function(){ | ||
wallet.processConfirmedTx(tx) | ||
assert.deepEqual(wallet.outputs, {}) | ||
}) | ||
it("does nothing when none of the involved addresses belong to the wallet", function() { | ||
wallet.processConfirmedTx(tx) | ||
assert.deepEqual(wallet.unspentMap, {}) | ||
}) | ||
function verifyOutputAdded(index, pending) { | ||
var txOut = tx.outs[index] | ||
var key = tx.getId() + ":" + index | ||
var output = wallet.outputs[key] | ||
assert.equal(output.from, key) | ||
var output = wallet.unspentMap[key] | ||
assert.deepEqual(output.txHash, tx.getHash()) | ||
assert.equal(output.value, txOut.value) | ||
@@ -412,31 +468,31 @@ assert.equal(output.pending, pending) | ||
describe('createTx', function(){ | ||
describe('createTx', function() { | ||
var wallet | ||
var address1, address2 | ||
var to, value | ||
var address1, address2 | ||
beforeEach(function(){ | ||
to = '15mMHKL96tWAUtqF3tbVf99Z8arcmnJrr3' | ||
beforeEach(function() { | ||
to = 'mt7MyTVVEWnbwpF5hBn6fgnJcv95Syk2ue' | ||
value = 500000 | ||
// generate 2 addresses | ||
address1 = wallet.generateAddress() | ||
address2 = wallet.generateAddress() | ||
address1 = "n1GyUANZand9Kw6hGSV9837cCC9FFUQzQa" | ||
address2 = "n2fiWrHqD6GM5GiEqkbWAc6aaZQp3ba93X" | ||
// set up 3 utxo | ||
utxo = [ | ||
// set up 3 utxos | ||
var utxos = [ | ||
{ | ||
"hash": fakeTxId(1), | ||
"outputIndex": 0, | ||
"address" : address1, | ||
"txId": fakeTxId(1), | ||
"index": 0, | ||
"address": address1, | ||
"value": 400000 // not enough for value | ||
}, | ||
{ | ||
"hash": fakeTxId(2), | ||
"outputIndex": 1, | ||
"address" : address1, | ||
"txId": fakeTxId(2), | ||
"index": 1, | ||
"address": address1, | ||
"value": 500000 // enough for only value | ||
}, | ||
{ | ||
"hash": fakeTxId(3), | ||
"outputIndex": 0, | ||
"txId": fakeTxId(3), | ||
"index": 0, | ||
"address" : address2, | ||
@@ -446,9 +502,13 @@ "value": 510000 // enough for value and fee | ||
] | ||
wallet.setUnspentOutputs(utxo) | ||
wallet = new Wallet(seed, networks.testnet) | ||
wallet.setUnspentOutputs(utxos) | ||
wallet.generateAddress() | ||
wallet.generateAddress() | ||
}) | ||
describe('transaction fee', function(){ | ||
it('allows fee to be specified', function(){ | ||
describe('transaction fee', function() { | ||
it('allows fee to be specified', function() { | ||
var fee = 30000 | ||
var tx = wallet.createTx(to, value, fee) | ||
var tx = wallet.createTx(to, value, { fixedFee: fee }) | ||
@@ -458,6 +518,6 @@ assert.equal(getFee(wallet, tx), fee) | ||
it('allows fee to be set to zero', function(){ | ||
it('allows fee to be set to zero', function() { | ||
value = 510000 | ||
var fee = 0 | ||
var tx = wallet.createTx(to, value, fee) | ||
var tx = wallet.createTx(to, value, { fixedFee: fee }) | ||
@@ -467,14 +527,16 @@ assert.equal(getFee(wallet, tx), fee) | ||
it('does not overestimate fees when network has dustSoftThreshold', function(){ | ||
var wallet = new Wallet(seed, networks.litecoin) | ||
var address = wallet.generateAddress() | ||
wallet.setUnspentOutputs([{ | ||
hash: fakeTxId(0), | ||
outputIndex: 0, | ||
address: address, | ||
it('does not overestimate fees when network has dustSoftThreshold', function() { | ||
var utxo = { | ||
txId: fakeTxId(0), | ||
index: 0, | ||
address: "LeyySKbQrRRwodKEj1W4a8y3YQupPLw5os", | ||
value: 500000 | ||
}]) | ||
} | ||
var wallet = new Wallet(seed, networks.litecoin) | ||
wallet.setUnspentOutputs([utxo]) | ||
wallet.generateAddress() | ||
value = 200000 | ||
var tx = wallet.createTx(address, value) | ||
var tx = wallet.createTx(utxo.address, value) | ||
@@ -485,9 +547,10 @@ assert.equal(getFee(wallet, tx), 100000) | ||
function getFee(wallet, tx) { | ||
var inputValue = tx.ins.reduce(function(memo, input){ | ||
var id = Array.prototype.reverse.call(input.hash).toString('hex') | ||
return memo + wallet.outputs[id + ':' + input.index].value | ||
var inputValue = tx.ins.reduce(function(accum, input) { | ||
var txId = bufferutils.reverse(input.hash).toString('hex') | ||
return accum + wallet.unspentMap[txId + ':' + input.index].value | ||
}, 0) | ||
return tx.outs.reduce(function(memo, output){ | ||
return memo - output.value | ||
return tx.outs.reduce(function(accum, output) { | ||
return accum - output.value | ||
}, inputValue) | ||
@@ -497,4 +560,4 @@ } | ||
describe('choosing utxo', function(){ | ||
it('takes fees into account', function(){ | ||
describe('choosing utxo', function() { | ||
it('takes fees into account', function() { | ||
var tx = wallet.createTx(to, value) | ||
@@ -507,65 +570,40 @@ | ||
it('ignores pending outputs', function(){ | ||
utxo.push( | ||
{ | ||
"hash": fakeTxId(4), | ||
"outputIndex": 0, | ||
"address" : address2, | ||
"value": 530000, | ||
"pending": true | ||
} | ||
) | ||
wallet.setUnspentOutputs(utxo) | ||
it('uses confirmed outputs', function() { | ||
var tx2 = new Transaction() | ||
tx2.addInput(fakeTxId(4), 0) | ||
tx2.addOutput(address2, 530000) | ||
wallet.processConfirmedTx(tx2) | ||
var tx = wallet.createTx(to, value) | ||
assert.equal(tx.ins.length, 1) | ||
assert.deepEqual(tx.ins[0].hash, fakeTxHash(3)) | ||
assert.deepEqual(tx.ins[0].hash, tx2.getHash()) | ||
assert.equal(tx.ins[0].index, 0) | ||
}) | ||
}) | ||
describe('works for testnet', function(){ | ||
it('should create transaction', function(){ | ||
var wallet = new Wallet(seed, networks.testnet) | ||
var address = wallet.generateAddress() | ||
it('ignores pending outputs', function() { | ||
var tx2 = new Transaction() | ||
tx2.addInput(fakeTxId(4), 0) | ||
tx2.addOutput(address2, 530000) | ||
wallet.setUnspentOutputs([{ | ||
hash: fakeTxId(0), | ||
outputIndex: 0, | ||
address: address, | ||
value: value | ||
}]) | ||
wallet.processPendingTx(tx2) | ||
var tx = wallet.createTx(to, value) | ||
var to = 'mt7MyTVVEWnbwpF5hBn6fgnJcv95Syk2ue' | ||
var toValue = value - 10000 | ||
var tx = wallet.createTx(to, toValue) | ||
assert.equal(tx.outs.length, 1) | ||
var outAddress = Address.fromOutputScript(tx.outs[0].script, networks.testnet) | ||
assert.equal(outAddress.toString(), to) | ||
assert.equal(tx.outs[0].value, toValue) | ||
assert.equal(tx.ins.length, 1) | ||
assert.deepEqual(tx.ins[0].hash, fakeTxHash(3)) | ||
assert.equal(tx.ins[0].index, 0) | ||
}) | ||
}) | ||
describe('changeAddress', function(){ | ||
it('should allow custom changeAddress', function(){ | ||
var wallet = new Wallet(seed, networks.testnet) | ||
var address = wallet.generateAddress() | ||
wallet.setUnspentOutputs([{ | ||
hash: fakeTxId(0), | ||
outputIndex: 0, | ||
address: address, | ||
value: value | ||
}]) | ||
assert.equal(wallet.getBalance(), value) | ||
describe('changeAddress', function() { | ||
it('should allow custom changeAddress', function() { | ||
var changeAddress = 'mfrFjnKZUvTcvdAK2fUX5D8v1Epu5H8JCk' | ||
var to = 'mt7MyTVVEWnbwpF5hBn6fgnJcv95Syk2ue' | ||
var toValue = value / 2 | ||
var fromValue = 510000 | ||
var toValue = fromValue / 2 | ||
var fee = 1e3 | ||
var tx = wallet.createTx(to, toValue, fee, changeAddress) | ||
var tx = wallet.createTx(to, toValue, { | ||
fixedFee: fee, | ||
changeAddress: changeAddress | ||
}) | ||
assert.equal(tx.outs.length, 2) | ||
@@ -580,8 +618,8 @@ | ||
assert.equal(outAddress1.toString(), changeAddress) | ||
assert.equal(tx.outs[1].value, value - (toValue + fee)) | ||
assert.equal(tx.outs[1].value, fromValue - (toValue + fee)) | ||
}) | ||
}) | ||
describe('transaction outputs', function(){ | ||
it('includes the specified address and amount', function(){ | ||
describe('transaction outputs', function() { | ||
it('includes the specified address and amount', function() { | ||
var tx = wallet.createTx(to, value) | ||
@@ -591,3 +629,3 @@ | ||
var out = tx.outs[0] | ||
var outAddress = Address.fromOutputScript(out.script) | ||
var outAddress = Address.fromOutputScript(out.script, networks.testnet) | ||
@@ -598,12 +636,12 @@ assert.equal(outAddress.toString(), to) | ||
describe('change', function(){ | ||
it('uses the last change address if there is any', function(){ | ||
describe('change', function() { | ||
it('uses the last change address if there is any', function() { | ||
var fee = 0 | ||
wallet.generateChangeAddress() | ||
wallet.generateChangeAddress() | ||
var tx = wallet.createTx(to, value, fee) | ||
var tx = wallet.createTx(to, value, { fixedFee: fee }) | ||
assert.equal(tx.outs.length, 2) | ||
var out = tx.outs[1] | ||
var outAddress = Address.fromOutputScript(out.script) | ||
var outAddress = Address.fromOutputScript(out.script, networks.testnet) | ||
@@ -614,11 +652,11 @@ assert.equal(outAddress.toString(), wallet.changeAddresses[1]) | ||
it('generates a change address if there is not any', function(){ | ||
it('generates a change address if there is not any', function() { | ||
var fee = 0 | ||
assert.equal(wallet.changeAddresses.length, 0) | ||
var tx = wallet.createTx(to, value, fee) | ||
var tx = wallet.createTx(to, value, { fixedFee: fee }) | ||
assert.equal(wallet.changeAddresses.length, 1) | ||
var out = tx.outs[1] | ||
var outAddress = Address.fromOutputScript(out.script) | ||
var outAddress = Address.fromOutputScript(out.script, networks.testnet) | ||
@@ -629,6 +667,8 @@ assert.equal(outAddress.toString(), wallet.changeAddresses[0]) | ||
it('skips change if it is not above dust threshold', function(){ | ||
var fee = 14570 | ||
var tx = wallet.createTx(to, value) | ||
assert.equal(tx.outs.length, 1) | ||
it('skips change if it is not above dust threshold', function() { | ||
var tx1 = wallet.createTx(to, value - 546) | ||
assert.equal(tx1.outs.length, 1) | ||
var tx2 = wallet.createTx(to, value - 547) | ||
assert.equal(tx2.outs.length, 2) | ||
}) | ||
@@ -638,20 +678,26 @@ }) | ||
describe('signing', function(){ | ||
afterEach(function(){ | ||
Transaction.prototype.sign.restore() | ||
describe('signing', function() { | ||
afterEach(function() { | ||
TransactionBuilder.prototype.sign.restore() | ||
}) | ||
it('signes the inputs with respective keys', function(){ | ||
it('signs the inputs with respective keys', function() { | ||
var fee = 30000 | ||
sinon.stub(Transaction.prototype, "sign") | ||
sinon.spy(TransactionBuilder.prototype, "sign") | ||
var tx = wallet.createTx(to, value, fee) | ||
wallet.createTx(to, value, { fixedFee: fee }) | ||
assert(Transaction.prototype.sign.calledWith(0, wallet.getPrivateKeyForAddress(address2))) | ||
assert(Transaction.prototype.sign.calledWith(1, wallet.getPrivateKeyForAddress(address1))) | ||
var priv1 = wallet.getPrivateKeyForAddress(address1) | ||
var priv2 = wallet.getPrivateKeyForAddress(address2) | ||
// FIXME: boo (required) side effects | ||
priv1.pub.Q.affineX, priv2.pub.Q.affineX | ||
assert(TransactionBuilder.prototype.sign.calledWith(0, priv2)) | ||
assert(TransactionBuilder.prototype.sign.calledWith(1, priv1)) | ||
}) | ||
}) | ||
describe('when value is below dust threshold', function(){ | ||
it('throws an error', function(){ | ||
describe('when value is below dust threshold', function() { | ||
it('throws an error', function() { | ||
var value = 546 | ||
@@ -665,4 +711,4 @@ | ||
describe('when there is not enough money', function(){ | ||
it('throws an error', function(){ | ||
describe('when there is not enough money', function() { | ||
it('throws an error', function() { | ||
var value = 1400001 | ||
@@ -669,0 +715,0 @@ |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
534371
67
8497
193
1