diff --git a/lib/bcoin/mtx.js b/lib/bcoin/mtx.js index a38804fc..c310682a 100644 --- a/lib/bcoin/mtx.js +++ b/lib/bcoin/mtx.js @@ -539,8 +539,8 @@ MTX.prototype.signInput = function signInput(index, addr, type) { return signatures === m; }; -MTX.prototype.isSigned = function isSigned(m) { - var i, input, prev, vector, len, j; +MTX.prototype.isSigned = function isSigned() { + var i, input, prev, vector, m, len, j; var total = 0; for (i = 0; i < this.inputs.length; i++) { @@ -567,10 +567,7 @@ MTX.prototype.isSigned = function isSigned(m) { // If the output script is a witness program, // we have to switch the vector to the witness - // and potentially alter the length. Note that - // witnesses are stack items, so the `dummy` - // _has_ to be an empty buffer (what OP_0 - // pushes onto the stack). + // and potentially alter the length. if (prev.isWitnessScripthash()) { prev = input.witness.getRedeem(); vector = input.witness.items; @@ -602,13 +599,7 @@ MTX.prototype.isSigned = function isSigned(m) { if (len - 1 !== m) return false; } else { - for (j = 0; j < vector.length; j++) { - if (Script.isSignatureEncoding(vector[j])) - total++; - } - - if (total !== m) - return false; + return false; } } @@ -684,15 +675,25 @@ MTX.prototype.scriptOutput = function scriptOutput(index, options) { }; MTX.prototype.isScripted = function isScripted() { - if (this.inputs.length === 0) - return false; + var i, input; if (this.outputs.length === 0) return false; - return this.inputs.every(function(input) { - return input.script.code.length > 0 || input.witness.items.length > 0; - }); + if (this.inputs.length === 0) + return false; + + for (i = 0; i < this.inputs.length; i++) { + input = this.inputs[i]; + + if (input.script.code.length === 0) + return false; + + if (input.witness.items.length === 0) + return false; + } + + return true; }; MTX.prototype.maxSize = function maxSize(options, force) { @@ -855,25 +856,20 @@ MTX.prototype.maxSize = function maxSize(options, force) { }; MTX.prototype.selectCoins = function selectCoins(coins, options) { - var chosen, lastAdded, tx, outputValue; - var i, size, change, fee, tryFree, minValue; + var chosen = []; + var index = 0; + var tx = this.clone(); + var outputValue = tx.getOutputValue(); + var tryFree, i, size, change, fee, minValue; - if (!options || typeof options !== 'object') { - options = { - changeAddress: arguments[1], - fee: arguments[2] - }; - } + if (!options) + options = {}; - tx = this.clone(); - tx.inputs.length = 0; - - chosen = []; - lastAdded = 0; - - outputValue = tx.getOutputValue(); tryFree = options.free; + // Null the inputs if there are any. + tx.inputs.length = 0; + if (options.confirmed) { coins = coins.filter(function(coin) { return coin.height !== -1; @@ -895,7 +891,7 @@ MTX.prototype.selectCoins = function selectCoins(coins, options) { } function total() { - if (options.subtractFee) + if (options.subtractFee != null) return outputValue; return outputValue.add(fee); } @@ -905,15 +901,13 @@ MTX.prototype.selectCoins = function selectCoins(coins, options) { } function addCoins() { - var i, index; - - for (i = lastAdded; i < coins.length; i++) { + while (index < coins.length) { // Add new inputs until TX will have enough // funds to cover both minimum post cost // and fee. - tx.addInput(coins[i]); - chosen.push(coins[i]); - lastAdded++; + tx.addInput(coins[index]); + chosen.push(coins[index]); + index++; if (options.selection === 'all') continue; @@ -939,6 +933,9 @@ MTX.prototype.selectCoins = function selectCoins(coins, options) { // calculate maximum TX size. tx.addOutput({ address: options.changeAddress, + // In case we don't have a change address, + // use a fake p2pkh output to gauge size. + keyHash: constants.zeroHash.slice(0, 20), value: new bn(0) }); @@ -961,10 +958,10 @@ MTX.prototype.selectCoins = function selectCoins(coins, options) { else fee = tx.getMaxFee(size); - // Failed to get enough funds, add more inputs. + // Failed to get enough funds, add more coins. if (!isFull()) addCoins(); - } while (!isFull() && lastAdded < coins.length); + } while (!isFull() && index < coins.length); } if (!isFull()) { @@ -1012,50 +1009,37 @@ MTX.prototype.selectCoins = function selectCoins(coins, options) { }; MTX.prototype.fill = function fill(coins, options) { - var self = this; - var result, err; + var result, i; assert(this.ts === 0, 'Cannot modify a confirmed tx.'); assert(this.inputs.length === 0, 'TX is already filled.'); - - if (!options || typeof options !== 'object') { - options = { - changeAddress: arguments[1], - fee: arguments[2] - }; - } - - assert(coins); + assert(options, '`options` are required.'); assert(options.changeAddress, '`changeAddress` is required.'); + // Select necessary coins. result = this.selectCoins(coins, options); - result.coins.forEach(function(coin) { - self.addInput(coin); - }); + // Add coins to transaction. + for (i = 0; i < result.coins.length; i++) + this.addInput(result.coins[i]); if (result.change.cmpn(constants.tx.dustThreshold) < 0) { // Do nothing. Change is added to fee. - assert.equal( - this.getFee().toNumber(), - result.fee.add(result.change).toNumber() - ); + assert(this.getFee().cmp(result.fee.add(result.change)) === 0); this.changeIndex = -1; } else { + // Add a change output. this.addOutput({ address: options.changeAddress, value: result.change }); - this.changeIndex = this.outputs.length - 1; - - assert.equal(this.getFee().toNumber(), result.fee.toNumber()); + assert(this.getFee().cmp(result.fee) === 0); } return result; }; -// https://github.com/bitcoin/bips/blob/master/bip-0069.mediawiki MTX.prototype.sortMembers = function sortMembers() { var changeOutput; diff --git a/lib/bcoin/wallet.js b/lib/bcoin/wallet.js index 7a1b1f08..59b9fe2c 100644 --- a/lib/bcoin/wallet.js +++ b/lib/bcoin/wallet.js @@ -202,6 +202,8 @@ Wallet.prototype.addKey = function addKey(key) { if (key instanceof bcoin.hd.privateKey) key = key.hdPublicKey; + assert(key instanceof bcoin.hd, 'Must add HD keys to wallet.'); + if (this.derivation === 'bip44') { if (!key || !key.isAccount44()) throw new Error('Must add HD account keys to BIP44 wallet.'); @@ -246,6 +248,8 @@ Wallet.prototype.removeKey = function removeKey(key) { if (key instanceof bcoin.hd.privateKey) key = key.hdPublicKey; + assert(key instanceof bcoin.hd, 'Must add HD keys to wallet.'); + if (this.derivation === 'bip44') { if (!key || !key.isAccount44()) throw new Error('Must add HD account keys to BIP44 wallet.'); @@ -616,6 +620,10 @@ Wallet.prototype.createTX = function createTX(options, outputs, callback) { }); }; +Wallet.prototype.deriveInput = function deriveInput(tx, i) { + return this.deriveInputs(tx.inputs[i])[0]; +}; + Wallet.prototype.deriveInputs = function deriveInputs(tx) { var paths = this.getInputPaths(tx); var addresses = []; @@ -731,7 +739,7 @@ Wallet.prototype.getRedeem = function getRedeem(hash, prefix) { else if (hash.length === 32) prefix = 'witnessscripthash'; else - assert(false, 'Unknown hash length.'); + return; } addr = bcoin.address.compileHash(hash, prefix); @@ -759,8 +767,9 @@ Wallet.prototype.zap = function zap(now, age, callback) { Wallet.prototype.scan = function scan(txByAddress, callback) { var self = this; var res = false; + var i; - return this._scan({}, txByAddress, function(err, depth) { + return this._scan({}, txByAddress, function(err, depth, txs) { if (err) return callback(err); @@ -770,25 +779,50 @@ Wallet.prototype.scan = function scan(txByAddress, callback) { if (self.setReceiveDepth(depth.receiveDepth + 1)) res = true; - return callback(null, res); + if (self.provider && self.provider.addTX) { + utils.forEachSerial(txs, function(tx, next) { + self.addTX(tx, next); + }, function(err) { + if (err) + return callback(err); + return callback(null, res, txs); + }); + return; + } + + return callback(null, res, txs); }); }; +Wallet.prototype.clone = function clone() { + var passphrase = this.options.passphrase; + var wallet; + + delete this.options.passphrase; + + wallet = Wallet.fromJSON(this.toJSON()); + + this.options.passphrase = passphrase; + + return wallet; +}; + Wallet.prototype._scan = function _scan(options, txByAddress, callback) { - var self = this; var depth = { changeDepth: 0, receiveDepth: 0 }; + var wallet = this.clone(); + var all = []; assert(this._initialized); - return (function chainCheck(change) { + (function chainCheck(change) { var addressIndex = 0; var total = 0; var gap = 0; - return (function next() { - var address = self.deriveAddress(change, addressIndex++); + (function next() { + var address = wallet.deriveAddress(change, addressIndex++); - return txByAddress(address.getAddress(), function(err, txs) { + txByAddress(address.getAddress(), function(err, txs) { var result; if (err) @@ -805,7 +839,7 @@ Wallet.prototype._scan = function _scan(options, txByAddress, callback) { result = false; if (Array.isArray(txs) && (txs[0] instanceof bcoin.tx)) - txs.forEach(function(tx) { self.addTX(tx); }); + all = all.concat(txs); } if (result) { @@ -827,7 +861,7 @@ Wallet.prototype._scan = function _scan(options, txByAddress, callback) { if (change === false) return chainCheck(true); - return callback(null, depth); + return callback(null, depth, all); }); })(); })(false); @@ -835,26 +869,35 @@ Wallet.prototype._scan = function _scan(options, txByAddress, callback) { Wallet.prototype.scriptInputs = function scriptInputs(tx, index) { var addresses = this.deriveInputs(tx); + var total = 0; + var i; - return addresses.reduce(function(total, address) { - return total + address.scriptInputs(tx, index); - }, 0); + for (i = 0; i < addresses.length; i++) + total += addresses[i].scriptInputs(tx, index); + + return total; }; Wallet.prototype.signInputs = function signInputs(tx, type, index) { var addresses = this.deriveInputs(tx); + var total = 0; + var i; - return addresses.reduce(function(total, address) { - return total + address.signInputs(tx, type, index); - }, 0); + for (i = 0; i < addresses.length; i++) + total += addresses[i].signInputs(tx, type, index); + + return total; }; Wallet.prototype.sign = function sign(tx, type, index) { var addresses = this.deriveInputs(tx); + var total = 0; + var i; - return addresses.reduce(function(total, address) { - return total + address.sign(tx, type, index); - }, 0); + for (i = 0; i < addresses.length; i++) + total += addresses[i].sign(tx, type, index); + + return total; }; Wallet.prototype.addTX = function addTX(tx, callback) {