diff --git a/lib/bcoin/input.js b/lib/bcoin/input.js index 5ecf0c9d..202fe303 100644 --- a/lib/bcoin/input.js +++ b/lib/bcoin/input.js @@ -7,6 +7,7 @@ var bn = require('bn.js'); var bcoin = require('../bcoin'); var utils = bcoin.utils; +var assert = utils.assert; var constants = bcoin.protocol.constants; /** @@ -68,10 +69,8 @@ Input.prototype.__defineGetter__('data', function() { data = Input.getData(this); - if (!this.tx || this.tx.ps === 0) { - if (this.script.length && this.prevout.tx) - utils.hidden(this, '_data', data); - } + if (this.script.length && this.output) + utils.hidden(this, '_data', data); return data; }); @@ -96,8 +95,12 @@ Input.prototype.__defineGetter__('hash', function() { return this.data.scriptHash || this.hashes[0]; }); +Input.prototype.__defineGetter__('id', function() { + return this.address || this.getID(); +}); + Input.prototype.__defineGetter__('address', function() { - return this.data.scriptAddress || this.addresses[0] || this.getID(); + return this.data.scriptAddress || this.addresses[0]; }); Input.prototype.__defineGetter__('signatures', function() { @@ -158,7 +161,7 @@ Input.prototype.__defineGetter__('output', function() { Input.prototype.__defineGetter__('value', function() { if (!this.output) - return new bn(0); + return; return this.output.value; }); @@ -221,10 +224,9 @@ Input.prototype.__defineGetter__('scriptaddr', function() { // } Input.getData = function getData(input) { - var def, data, output; + var def, data; - if (!input || !input.script) - return; + assert(input instanceof Input); def = { side: 'input', @@ -233,12 +235,10 @@ Input.getData = function getData(input) { seq: input.seq }; - if (input.prevout) { - def.prev = input.prevout.hash; - def.index = input.prevout.index; - } + def.prev = input.prevout.hash; + def.index = input.prevout.index; - if (input.prevout && +input.prevout.hash === 0) { + if (+input.prevout.hash === 0) { data = bcoin.script.getCoinbaseData(input.script); return utils.merge(def, data, { type: 'coinbase', @@ -246,18 +246,40 @@ Input.getData = function getData(input) { }); } - if (input.prevout && input.prevout.tx) { - output = input.prevout.tx.outputs[input.prevout.index]; - if (output) { - data = bcoin.script.getInputData(input.script, output.script); - data.value = output.value; - return utils.merge(def, data); - } + if (input.output) { + data = bcoin.script.getInputData(input.script, input.output.script); + data.value = input.output.value; + return utils.merge(def, data); } return utils.merge(def, bcoin.script.getInputData(input.script)); }; +Input.prototype.getData = function getData() { + return Input.getData(this); +}; + +Input.prototype.getAddresses = function getAddresses() { + return this.getData().addresses; +}; + +Input.prototype.getScriptAddress = function getScriptAddress() { + return this.getData().scriptAddress; +}; + +Input.prototype.getKeyAddress = function getKeyAddress() { + return this.getData().addresses[0]; +}; + +Input.prototype.getAddress = function getAddress() { + var data = this.getData(); + + if (data.scriptAddress) + return data.scriptAddress; + + return data.addresses[0]; +}; + Input.prototype.isFinal = function isFinal() { return this.sequence === 0xffffffff; }; @@ -271,9 +293,9 @@ Input.prototype.getID = function getID() { Input.prototype.getLocktime = function getLocktime() { var output, redeem, lock, type; - assert(this.prevout.tx); + assert(this.output); - output = this.prevout.tx.outputs[this.prevout.index]; + output = this.output; redeem = output.script; if (bcoin.script.isScripthash(redeem)) @@ -349,8 +371,8 @@ Input.prototype.inspect = function inspect() { text: this.text, locktime: this.locktime, value: utils.btc(output.value), - script: bcoin.script.format(this.script)[0], - redeem: this.redeem ? bcoin.script.format(this.redeem)[0] : null, + script: bcoin.script.format(this.script), + redeem: this.redeem ? bcoin.script.format(this.redeem) : null, seq: this.seq, output: output }; diff --git a/lib/bcoin/output.js b/lib/bcoin/output.js index 935094f9..3d62de33 100644 --- a/lib/bcoin/output.js +++ b/lib/bcoin/output.js @@ -48,10 +48,8 @@ Output.prototype.__defineGetter__('data', function() { data = Output.getData(this); - if (!this.tx || this.tx.ps === 0) { - if (this.script.length && this.value.cmpn(0) > 0) - utils.hidden(this, '_data', data); - } + if (this.script.length) + utils.hidden(this, '_data', data); return data; }); @@ -72,8 +70,12 @@ Output.prototype.__defineGetter__('hash', function() { return this.data.scriptHash || this.hashes[0]; }); +Output.prototype.__defineGetter__('id', function() { + return this.address || this.getID(); +}); + Output.prototype.__defineGetter__('address', function() { - return this.data.scriptAddress || this.addresses[0] || this.getID(); + return this.data.scriptAddress || this.addresses[0]; }); Output.prototype.__defineGetter__('signatures', function() { @@ -178,8 +180,7 @@ Output.prototype.__defineGetter__('scriptaddr', function() { Output.getData = function getData(output) { var def; - if (!output || !output.script) - return; + assert(output instanceof Output); def = { side: 'output', @@ -190,6 +191,31 @@ Output.getData = function getData(output) { return utils.merge(def, bcoin.script.getOutputData(output.script)); }; +Output.prototype.getData = function getData() { + return Output.getData(this); +}; + +Output.prototype.getAddresses = function getAddresses() { + return this.getData().addresses; +}; + +Output.prototype.getScriptAddress = function getScriptAddress() { + return this.getData().scriptAddress; +}; + +Output.prototype.getKeyAddress = function getKeyAddress() { + return this.getData().addresses[0]; +}; + +Output.prototype.getAddress = function getAddress() { + var data = this.getData(); + + if (data.scriptAddress) + return data.scriptAddress; + + return data.addresses[0]; +}; + Output.prototype.getID = function getID() { var data = bcoin.script.encode(this.script); var hash = utils.toHex(utils.ripesha(data)); @@ -221,7 +247,7 @@ Output.prototype.inspect = function inspect() { text: this.text, locktime: this.locktime, value: utils.btc(this.value), - script: bcoin.script.format(this.script)[0] + script: bcoin.script.format(this.script) }; }; diff --git a/lib/bcoin/pool.js b/lib/bcoin/pool.js index 1040da92..3489a1db 100644 --- a/lib/bcoin/pool.js +++ b/lib/bcoin/pool.js @@ -999,7 +999,7 @@ Pool.prototype.unwatch = function unwatch(id) { // See "Filter matching algorithm": // https://github.com/bitcoin/bips/blob/master/bip-0037.mediawiki Pool.prototype.isWatched = function(tx, bloom) { - var i, input, output, prev; + var i, input, output; if (!bloom) bloom = this.bloom; @@ -1039,9 +1039,8 @@ Pool.prototype.isWatched = function(tx, bloom) { return true; // Test the prev_out script - if (input.prevout.tx) { - prev = input.prevout.tx.outputs[input.prevout.index]; - if (testScript(prev.script)) + if (input.output) { + if (testScript(input.output.script)) return true; } diff --git a/lib/bcoin/script.js b/lib/bcoin/script.js index 643e8928..222d2d4b 100644 --- a/lib/bcoin/script.js +++ b/lib/bcoin/script.js @@ -278,6 +278,20 @@ script.getSubscript = function getSubscript(s, lastSep) { return res; }; +script.concat = function concat(scripts) { + var s = []; + var i; + + s.push(scripts[0]); + + for (i = 1; i < scripts.length; i++) { + s.push('codeseparator'); + s = s.concat(scripts[i]); + } + + return s; +}; + script.checksig = function checksig(msg, sig, key) { if (key.getPublic) key = key.getPublic(); @@ -2067,26 +2081,20 @@ script.isLowDER = function isLowDER(sig) { return true; }; -script.format = function format(input, output) { +script.format = function format(s) { var scripts = []; - var prev, redeem; - if (Array.isArray(input)) { - scripts.push(input); - } else if (Array.isArray(output)) { - scripts.push(output); - } else if (input) { - scripts.push(input.script); - if (input.prevout.tx && input.prevout.tx.outputs[input.prevout.index]) { - prev = input.prevout.tx.outputs[input.prevout.index].script; - scripts.push(prev); - if (script.isScripthash(prev)) { - redeem = script.decode(input.script[input.script.length - 1]); - scripts.push(redeem); - } + if (Array.isArray(s)) { + scripts.push(s); + } else if (s instanceof bcoin.input) { + scripts.push(s.script); + if (s.output) { + scripts.push(s.output.script); + if (script.isScripthash(s.output.script)) + scripts.push(script.getRedeem(s.script)); } - } else if (output) { - scripts.push(output.script); + } else if (s instanceof bcoin.output) { + scripts.push(s.script); } scripts = scripts.map(function(script) { @@ -2102,7 +2110,7 @@ script.format = function format(input, output) { }).join(' '); }); - return scripts; + return script.concat(scripts); }; script.isPushOnly = function isPushOnly(s) { @@ -2111,8 +2119,6 @@ script.isPushOnly = function isPushOnly(s) { op = s[i]; if (Array.isArray(op) || op === '1negate' || (op >= 1 && op <= 16)) continue; - if (constants.opcodes[op] == null) - return false; return false; } return true; diff --git a/lib/bcoin/tx-pool.js b/lib/bcoin/tx-pool.js index c98c44e8..c59577e8 100644 --- a/lib/bcoin/tx-pool.js +++ b/lib/bcoin/tx-pool.js @@ -286,33 +286,28 @@ TXPool.prototype.getAll = function getAll(address) { }; TXPool.prototype._addOutput = function _addOutput(tx, i, remove) { - var i, data, address, addr, output; - - output = tx.outputs[i]; - data = bcoin.script.getOutputData(output.script); + var output = tx.outputs[i]; + var address; if (!this._wallet.ownOutput(tx, i)) return; - if (data.scriptAddress) - data.addresses = [data.scriptAddress]; + address = output.getAddress(); - for (i = 0; i < data.addresses.length; i++) { - addr = data.addresses[i]; - if (!this._addresses[addr]) { - this._addresses[addr] = { - received: new bn(0), - sent: new bn(0), - balance: new bn(0) - }; - } - if (!remove) { - this._addresses[addr].balance.iadd(output.value); - this._addresses[addr].received.iadd(output.value); - } else { - this._addresses[addr].balance.isub(output.value); - this._addresses[addr].received.isub(output.value); - } + if (!this._addresses[address]) { + this._addresses[address] = { + received: new bn(0), + sent: new bn(0), + balance: new bn(0) + }; + } + + if (!remove) { + this._addresses[address].balance.iadd(output.value); + this._addresses[address].received.iadd(output.value); + } else { + this._addresses[address].balance.isub(output.value); + this._addresses[address].received.isub(output.value); } if (!remove) { @@ -329,36 +324,31 @@ TXPool.prototype._removeOutput = function _removeOutput(tx, i) { }; TXPool.prototype._addInput = function _addInput(tx, i, remove) { - var i, input, prev, data, address, addr, output; - - input = tx.inputs[i]; - assert(input.prevout.tx); + var input = tx.inputs[i]; + var prev, address; if (!this._wallet.ownOutput(input.prevout.tx, input.prevout.index)) return; - prev = input.prevout.tx.outputs[input.prevout.index]; - data = bcoin.script.getInputData(input.script, prev.script); + assert(input.output); - if (data.scriptAddress) - data.addresses = [data.scriptAddress]; + prev = input.output; + address = prev.getAddress(); - for (i = 0; i < data.addresses.length; i++) { - addr = data.addresses[i]; - if (!this._addresses[addr]) { - this._addresses[addr] = { - received: new bn(0), - sent: new bn(0), - balance: new bn(0) - }; - } - if (!remove) { - this._addresses[addr].balance.isub(prev.value); - this._addresses[addr].sent.iadd(prev.value); - } else { - this._addresses[addr].balance.iadd(prev.value); - this._addresses[addr].sent.isub(prev.value); - } + if (!this._addresses[address]) { + this._addresses[address] = { + received: new bn(0), + sent: new bn(0), + balance: new bn(0) + }; + } + + if (!remove) { + this._addresses[address].balance.isub(prev.value); + this._addresses[address].sent.iadd(prev.value); + } else { + this._addresses[address].balance.iadd(prev.value); + this._addresses[address].sent.isub(prev.value); } if (!remove) { diff --git a/lib/bcoin/tx.js b/lib/bcoin/tx.js index cc08dba8..e607b0a2 100644 --- a/lib/bcoin/tx.js +++ b/lib/bcoin/tx.js @@ -206,16 +206,16 @@ TX.prototype.scriptInput = function scriptInput(index, pub, redeem) { // return; // We should have previous outputs by now. - assert(input.prevout.tx); + assert(input.output); // Get the previous output's subscript - s = input.prevout.tx.getSubscript(input.prevout.index); + s = input.output.script; // P2SH if (bcoin.script.isScripthash(s)) { if (!redeem) return false; - s = bcoin.script.getSubscript(bcoin.script.decode(redeem)); + s = bcoin.script.decode(redeem); } else { redeem = null; } @@ -316,17 +316,15 @@ TX.prototype.createSignature = function createSignature(index, key, type) { assert(input); // We should have previous outputs by now. - assert(input.prevout.tx); + assert(input.output); // Get the previous output's subscript - s = input.prevout.tx.getSubscript(input.prevout.index); + s = input.output.script; // We need to grab the redeem script when // signing p2sh transactions. - if (bcoin.script.isScripthash(s)) { - s = bcoin.script.decode(input.script[input.script.length - 1]); - s = bcoin.script.getSubscript(s); - } + if (bcoin.script.isScripthash(s)) + s = bcoin.script.getRedeem(input.script); // Get the hash of the current tx, minus the other // inputs, plus the sighash type. @@ -357,13 +355,13 @@ TX.prototype.signInput = function signInput(index, key, type) { assert(input); // We should have previous outputs by now. - assert(input.prevout.tx); + assert(input.output); // Create our signature. signature = this.createSignature(index, key, type); // Get the previous output's subscript - s = input.prevout.tx.getSubscript(input.prevout.index); + s = input.output.script; // Script length, needed for multisig len = input.script.length; @@ -371,8 +369,7 @@ TX.prototype.signInput = function signInput(index, key, type) { // We need to grab the redeem script when // signing p2sh transactions. if (bcoin.script.isScripthash(s)) { - s = bcoin.script.decode(input.script[input.script.length - 1]); - s = bcoin.script.getSubscript(s); + s = bcoin.script.getRedeem(input.script); // Decrement `len` to avoid the redeem script len--; } @@ -571,10 +568,10 @@ TX.prototype.isSigned = function isSigned(index, required) { // We can't check for signatures unless // we have the previous output. - assert(input.prevout.tx); + assert(input.output); // Get the prevout's subscript - s = input.prevout.tx.getSubscript(input.prevout.index); + s = input.output.script; // Script length, needed for multisig len = input.script.length; @@ -642,7 +639,7 @@ TX.prototype.isSigned = function isSigned(index, required) { TX.prototype.addOutput = function addOutput(obj, value) { var options, output; - if (obj.getAddress) + if ((obj instanceof bcoin.wallet) || (obj instanceof bcoin.address)) obj = obj.getAddress(); if (typeof obj === 'string') { @@ -770,11 +767,9 @@ TX.prototype.signatureHash = function signatureHash(index, s, type) { if (!Array.isArray(s)) { type = s; - s = this.inputs[index].prevout.tx.getSubscript(this.inputs[index].prevout.index); - if (bcoin.script.isScripthash(s)) { - s = this.inputs[index].script[this.inputs[index.script.length - 1]]; - s = bcoin.script.getSubscript(bcoin.script.decode(s)); - } + s = this.inputs[index].output.script; + if (bcoin.script.isScripthash(s)) + s = bcoin.script.getRedeem(this.inputs[index].script); } if (typeof index !== 'number') @@ -886,31 +881,17 @@ TX.prototype.verify = function verify(index, force, flags) { assert(this.inputs[index]); return this.inputs.every(function(input, i) { - var output; - if (index != null && i !== index) return true; - if (!input.prevout.tx) + if (!input.output) return false; // Somethis is very wrong if this is // not the case. assert.equal(input.prevout.tx.hash('hex'), input.prevout.hash); - // Grab the previous output. - output = input.prevout.tx.outputs[input.prevout.index]; - - // Transaction is referencing an output - // that does not exist. - if (!output) - return false; - - // Transaction cannot reference itself. - if (input.prevout.hash === this.hash('hex')) - return false; - - return bcoin.script.verify(input.script, output.script, this, i, flags); + return bcoin.script.verify(input.script, input.output.script, this, i, flags); }, this); }; @@ -933,19 +914,20 @@ TX.prototype.maxSize = function maxSize() { input = copy.inputs[i]; size = 0; + assert(input.output); + // Get the previous output's subscript - s = input.prevout.tx.getSubscript(input.prevout.index); + s = input.output.script; // If we have access to the redeem script, // we can use it to calculate size much easier. if (this.inputs[i].script.length && bcoin.script.isScripthash(s)) { - s = this.inputs[i].script[this.inputs[i].script.length - 1]; + s = bcoin.script.getRedeem(this.inputs[i].script); // Need to add the redeem script size // here since it will be ignored by // the isMultisig clause. // OP_PUSHDATA2 [redeem] - size += 3 + s.length; - s = bcoin.script.getSubscript(bcoin.script.decode(s)); + size += 3 + bcoin.script.getSize(s); } if (bcoin.script.isPubkey(s)) { @@ -1244,18 +1226,15 @@ TX.prototype.getFee = function getFee() { TX.prototype.getInputValue = function getInputValue() { var acc = new bn(0); - var inputs = this.inputs.filter(function(input) { - return input.prevout.tx; - }); - - if (inputs.length === 0) + if (this.inputs.length === 0) return acc; - inputs.reduce(function(acc, input) { - return acc.iadd(input.prevout.tx.outputs[input.prevout.index].value); - }, acc); + if (!this.hasPrevout()) + return acc; - return acc; + return this.inputs.reduce(function(acc, input) { + return acc.iadd(input.output.value); + }, acc); }; TX.prototype.getOutputValue = function getOutputValue() { @@ -1264,11 +1243,9 @@ TX.prototype.getOutputValue = function getOutputValue() { if (this.outputs.length === 0) return acc; - this.outputs.reduce(function(acc, output) { + return this.outputs.reduce(function(acc, output) { return acc.iadd(output.value); }, acc); - - return acc; }; TX.prototype.getFunds = function getFunds(side) { @@ -1313,7 +1290,7 @@ TX.prototype.getTargetLocktime = function getTargetLocktime() { TX.prototype.testInputs = function testInputs(addressTable, index, collect) { var inputs = []; - var i, input, prev, data, j; + var i, input, j, addresses, scriptAddress; if (typeof addressTable === 'string') addressTable = [addressTable]; @@ -1337,16 +1314,10 @@ TX.prototype.testInputs = function testInputs(addressTable, index, collect) { input = this.inputs[i]; - if (input.prevout.tx) - prev = input.prevout.tx.outputs[input.prevout.index].script; - else - prev = null; - - data = bcoin.script.getInputData(input.script, prev); - - if (data.addresses) { - for (j = 0; j < data.addresses.length; j++) { - if (addressTable[data.addresses[j]] != null) { + addresses = input.getAddresses(); + if (addresses) { + for (j = 0; j < addresses.length; j++) { + if (addressTable[addresses[j]] != null) { if (!collect) return true; inputs.push(input); @@ -1354,8 +1325,9 @@ TX.prototype.testInputs = function testInputs(addressTable, index, collect) { } } - if (data.scriptAddress) { - if (addressTable[data.scriptAddress] != null) { + scriptAddress = input.getScriptAddress(); + if (scriptAddress) { + if (addressTable[scriptAddress] != null) { if (!collect) return true; inputs.push(input); @@ -1374,7 +1346,7 @@ TX.prototype.testInputs = function testInputs(addressTable, index, collect) { TX.prototype.testOutputs = function testOutputs(addressTable, index, collect) { var outputs = []; - var i, output, data, j; + var i, output, data, j, addresses, scriptAddress; if (typeof addressTable === 'string') addressTable = [addressTable]; @@ -1398,11 +1370,10 @@ TX.prototype.testOutputs = function testOutputs(addressTable, index, collect) { output = this.outputs[i]; - data = bcoin.script.getOutputData(output.script); - - if (data.addresses) { - for (j = 0; j < data.addresses.length; j++) { - if (addressTable[data.addresses[j]] != null) { + addresses = output.getAddresses(); + if (addresses) { + for (j = 0; j < addresses.length; j++) { + if (addressTable[addresses[j]] != null) { if (!collect) return true; outputs.push(output); @@ -1410,8 +1381,9 @@ TX.prototype.testOutputs = function testOutputs(addressTable, index, collect) { } } - if (data.scriptAddress) { - if (addressTable[data.scriptAddress] != null) { + scriptAddress = output.getScriptAddress(); + if (scriptAddress) { + if (addressTable[scriptAddress] != null) { if (!collect) return true; outputs.push(output); @@ -1465,6 +1437,7 @@ TX.prototype.increaseFee = function increaseFee(fee) { TX.prototype.hasPrevout = function hasPrevout() { if (this.inputs.length === 0) return false; + return this.inputs.every(function(input) { return !!input.prevout.tx; }); @@ -1614,7 +1587,7 @@ TX.prototype.isStandard = function isStandard() { }; TX.prototype.isStandardInputs = function isStandardInputs(flags) { - var i, input, prev, args, stack, res, s, targs; + var i, input, args, stack, res, s, targs; if (this.isCoinbase()) return true; @@ -1622,15 +1595,10 @@ TX.prototype.isStandardInputs = function isStandardInputs(flags) { for (i = 0; i < this.inputs.length; i++) { input = this.inputs[i]; - if (!input.prevout.tx) + if (!input.output) return false; - prev = input.prevout.tx.outputs[input.prevout.index]; - - if (!prev) - return false; - - args = bcoin.script.getArgs(prev.script); + args = bcoin.script.getArgs(input.output.script); if (args < 0) return false; @@ -1642,7 +1610,7 @@ TX.prototype.isStandardInputs = function isStandardInputs(flags) { if (!res) return false; - if (bcoin.script.isScripthash(prev.script)) { + if (bcoin.script.isScripthash(input.output.script)) { if (stack.length === 0) return false; @@ -1651,7 +1619,7 @@ TX.prototype.isStandardInputs = function isStandardInputs(flags) { if (!Array.isArray(s)) return false; - s = bcoin.script.getSubscript(bcoin.script.decode(s)); + s = bcoin.script.decode(s); if (bcoin.script.getType(s) !== 'unknown') { targs = bcoin.script.getArgs(s); @@ -1671,7 +1639,7 @@ TX.prototype.isStandardInputs = function isStandardInputs(flags) { }; TX.prototype.getPriority = function getPriority() { - var size, value, i, input, output, age; + var size, value, i, input, age; size = this.maxSize(); value = new bn(0); @@ -1682,7 +1650,6 @@ TX.prototype.getPriority = function getPriority() { if (!input.prevout.tx) return constants.tx.freeThreshold.clone(); - output = input.prevout.tx.outputs[input.prevout.index]; age = input.prevout.tx.getConfirmations(); if (age === -1) @@ -1691,7 +1658,7 @@ TX.prototype.getPriority = function getPriority() { if (age !== 0) age += 1; - value.iadd(output.value.muln(age)); + value.iadd(input.output.value.muln(age)); } return priority.divn(size); diff --git a/lib/bcoin/wallet.js b/lib/bcoin/wallet.js index df2034a5..7b211702 100644 --- a/lib/bcoin/wallet.js +++ b/lib/bcoin/wallet.js @@ -763,17 +763,35 @@ Wallet.prototype.createKey = function createKey(change, index) { }; Wallet.prototype.setAddressDepth = function setAddressDepth(depth) { + var i; + assert(this.derivation !== 'normal'); - for (var i = this.addressDepth; i < depth; i++) + + if (depth <= this.addressDepth) + return false; + + for (i = this.addressDepth; i < depth; i++) this.currentAddress = this.createAddress(false, i); + this.addressDepth = depth; + + return true; }; Wallet.prototype.setChangeDepth = function setChangeDepth(depth) { + var i; + assert(this.derivation !== 'normal'); - for (var i = this.addressDepth; i < depth; i++) + + if (depth <= this.changeDepth) + return false; + + for (i = this.changeDepth; i < depth; i++) this.changeAddress = this.createAddress(true, i); + this.changeDepth = depth; + + return true; }; Wallet.prototype.getKeyHash =