diff --git a/lib/http/rpc.js b/lib/http/rpc.js index a7017535..5bfa21ff 100644 --- a/lib/http/rpc.js +++ b/lib/http/rpc.js @@ -791,7 +791,7 @@ RPC.prototype._txToJSON = function _txToJSON(tx, entry) { if (tx.isCoinbase()) { out.coinbase = input.script.toJSON(); } else { - out.txid = input.prevout.rhash(); + out.txid = input.prevout.txid(); out.vout = input.prevout.index; out.scriptSig = { asm: input.script.toASM(), @@ -3381,7 +3381,7 @@ RPC.prototype.listlockunspent = co(function* listlockunspent(args) { for (i = 0; i < outpoints.length; i++) { outpoint = outpoints[i]; out.push({ - txid: outpoint.rhash(), + txid: outpoint.txid(), vout: outpoint.index }); } @@ -3749,7 +3749,7 @@ RPC.prototype.listunspent = co(function* listunspent(args) { ring = yield wallet.getKey(hash); out.push({ - txid: coin.rhash(), + txid: coin.txid(), vout: coin.index, address: address ? address.toBase58(this.network) : null, account: ring ? ring.name : undefined, diff --git a/lib/primitives/coin.js b/lib/primitives/coin.js index fb8635f2..14fe812d 100644 --- a/lib/primitives/coin.js +++ b/lib/primitives/coin.js @@ -62,17 +62,21 @@ Coin.prototype.fromOptions = function fromOptions(options) { assert(options, 'Coin data is required.'); if (options.version != null) { - assert(util.isUInt32(options.version)); + assert(util.isUInt32(options.version), 'Version must be a uint32.'); this.version = options.version; } if (options.height != null) { - assert(options.height === -1 || util.isUInt32(options.height)); - this.height = options.height; + if (options.height !== -1) { + assert(util.isUInt32(options.height), 'Height must be a uint32.'); + this.height = options.height; + } else { + this.height = -1; + } } if (options.value != null) { - assert(util.isUInt53(options.value)); + assert(util.isUInt53(options.value), 'Value must be a uint53.'); this.value = options.value; } @@ -80,17 +84,18 @@ Coin.prototype.fromOptions = function fromOptions(options) { this.script.fromOptions(options.script); if (options.coinbase != null) { - assert(typeof options.coinbase === 'boolean'); + assert(typeof options.coinbase === 'boolean', + 'Coinbase must be a boolean.'); this.coinbase = options.coinbase; } if (options.hash != null) { - assert(typeof options.hash === 'string'); + assert(typeof options.hash === 'string', 'Hash must be a string.'); this.hash = options.hash; } if (options.index != null) { - assert(util.isUInt32(options.index)); + assert(util.isUInt32(options.index), 'Index must be a uint32.'); this.index = options.index; } @@ -107,6 +112,16 @@ Coin.fromOptions = function fromOptions(options) { return new Coin().fromOptions(options); }; +/** + * Clone the coin. + * @private + * @returns {Coin} + */ + +Coin.prototype.clone = function clone() { + assert(false, 'Coins are not cloneable.'); +}; + /** * Calculate number of confirmations since coin was created. * @param {Number?} height - Current chain height. Network @@ -172,6 +187,15 @@ Coin.prototype.rhash = function rhash() { return util.revHex(this.hash); }; +/** + * Get little-endian hash. + * @returns {Hash} + */ + +Coin.prototype.txid = function txid() { + return this.rhash(); +}; + /** * Convert the coin to a more user-friendly object. * @returns {Object} @@ -239,20 +263,25 @@ Coin.prototype.getJSON = function getJSON(network, minimal) { Coin.prototype.fromJSON = function fromJSON(json) { assert(json, 'Coin data required.'); - assert(util.isNumber(json.version)); - assert(util.isNumber(json.height)); - assert(typeof json.value === 'string'); - assert(typeof json.coinbase === 'boolean'); - assert(!json.hash || typeof json.hash === 'string'); - assert(!json.index || util.isNumber(json.index)); + assert(util.isUInt32(json.version), 'Version must be a uint32.'); + assert(json.height === -1 || util.isUInt32(json.height), + 'Height must be a uint32.'); + assert(typeof json.value === 'string', 'Value must be a string.'); + assert(typeof json.coinbase === 'boolean', 'Coinbase must be a boolean.'); this.version = json.version; this.height = json.height; this.value = Amount.value(json.value); this.script.fromJSON(json.script); this.coinbase = json.coinbase; - this.hash = json.hash ? util.revHex(json.hash) : encoding.NULL_HASH; - this.index = json.index != null ? json.index : 0; + + if (json.hash != null) { + assert(typeof json.hash === 'string', 'Hash must be a string.'); + assert(json.hash.length === 64, 'Hash must be a string.'); + assert(util.isUInt32(json.index), 'Index must be a uint32.'); + this.hash = util.revHex(json.hash); + this.index = json.index; + } return this; }; @@ -365,9 +394,9 @@ Coin.fromRaw = function fromRaw(data, enc) { */ Coin.prototype.fromTX = function fromTX(tx, index, height) { - assert(util.isNumber(index)); - assert(index < tx.outputs.length); + assert(typeof index === 'number'); assert(typeof height === 'number'); + assert(index >= 0 && index < tx.outputs.length); this.version = tx.version; this.height = height; this.value = tx.outputs[index].value; diff --git a/lib/primitives/input.js b/lib/primitives/input.js index 4705f10a..4a334b8d 100644 --- a/lib/primitives/input.js +++ b/lib/primitives/input.js @@ -48,7 +48,6 @@ function Input(options) { Input.prototype.fromOptions = function fromOptions(options) { assert(options, 'Input data is required.'); - assert(options.prevout); this.prevout.fromOptions(options.prevout); @@ -56,7 +55,7 @@ Input.prototype.fromOptions = function fromOptions(options) { this.script.fromOptions(options.script); if (options.sequence != null) { - assert(util.isUInt32(options.sequence)); + assert(util.isUInt32(options.sequence), 'Sequence must be a uint32.'); this.sequence = options.sequence; } @@ -76,6 +75,20 @@ Input.fromOptions = function fromOptions(options) { return new Input().fromOptions(options); }; +/** + * Clone the input. + * @returns {Input} + */ + +Input.prototype.clone = function clone() { + var input = new Input(); + input.prevout = this.prevout; + input.script.inject(this.script); + input.sequence = this.sequence; + input.witness.inject(this.witness); + return input; +}; + /** * Get the previous output script type as a string. * Will "guess" based on the input script and/or @@ -300,7 +313,7 @@ Input.prototype.getJSON = function getJSON(network, coin) { Input.prototype.fromJSON = function fromJSON(json) { assert(json, 'Input data is required.'); - assert(util.isUInt32(json.sequence)); + assert(util.isUInt32(json.sequence), 'Sequence must be a uint32.'); this.prevout.fromJSON(json.prevout); this.script.fromJSON(json.script); this.witness.fromJSON(json.witness); @@ -453,7 +466,7 @@ Input.fromCoin = function fromCoin(coin) { Input.prototype.fromTX = function fromTX(tx, index) { assert(tx); assert(typeof index === 'number'); - assert(index < tx.outputs.length); + assert(index >= 0 && index < tx.outputs.length); this.prevout.hash = tx.hash('hex'); this.prevout.index = index; return this; diff --git a/lib/primitives/mtx.js b/lib/primitives/mtx.js index aeb1e356..f8a1c6da 100644 --- a/lib/primitives/mtx.js +++ b/lib/primitives/mtx.js @@ -70,35 +70,40 @@ MTX.prototype.fromOptions = function fromOptions(options) { var i; if (options.version != null) { - assert(util.isUInt32(options.version)); + assert(util.isUInt32(options.version), 'Version must a be uint32.'); this.version = options.version; } if (options.flag != null) { - assert(util.isUInt8(options.flag)); + assert(util.isUInt8(options.flag), 'Flag must be a uint8.'); this.flag = options.flag; } if (options.inputs) { - assert(Array.isArray(options.inputs)); + assert(Array.isArray(options.inputs), 'Inputs must be an array.'); for (i = 0; i < options.inputs.length; i++) this.addInput(options.inputs[i]); } if (options.outputs) { - assert(Array.isArray(options.outputs)); + assert(Array.isArray(options.outputs), 'Outputs must be an array.'); for (i = 0; i < options.outputs.length; i++) this.addOutput(options.outputs[i]); } if (options.locktime != null) { - assert(util.isUInt32(options.locktime)); + assert(util.isUInt32(options.locktime), 'Locktime must be a uint32.'); this.locktime = options.locktime; } if (options.changeIndex != null) { - assert(util.isNumber(options.changeIndex)); - this.changeIndex = options.changeIndex; + if (options.changeIndex !== -1) { + assert(util.isUInt32(options.changeIndex), + 'Change index must be a uint32.'); + this.changeIndex = options.changeIndex; + } else { + this.changeIndex = -1; + } } return this; @@ -115,12 +120,16 @@ MTX.fromOptions = function fromOptions(options) { }; /** - * Clone the transaction. + * Clone the transaction. Note that + * this will not carry over the view. * @returns {MTX} */ MTX.prototype.clone = function clone() { - return new MTX(this); + var mtx = new MTX(); + mtx.inject(this); + mtx.changeIndex = this.changeIndex; + return mtx; }; /** @@ -211,8 +220,8 @@ MTX.prototype.addTX = function addTX(tx, index, height) { /** * Add an output. - * @param {Address|Script|Output|Object} options - * @param {Amount?} value - Only needs to be present for non-output options. + * @param {Address|Script|Output|Object} script - Script or output options. + * @param {Amount?} value * @returns {Output} * * @example @@ -222,24 +231,14 @@ MTX.prototype.addTX = function addTX(tx, index, height) { * mtx.addOutput(script, 100000); */ -MTX.prototype.addOutput = function addOutput(options, value) { +MTX.prototype.addOutput = function addOutput(script, value) { var output; - if (typeof options === 'string') - options = Address.fromBase58(options); - - if (options instanceof Address) - options = Script.fromAddress(options); - - output = new Output(); - - if (options instanceof Script) { + if (value != null) { assert(util.isUInt53(value), 'Value must be a uint53.'); - output.script.fromOptions(options); - output.value = value; + output = Output.fromScript(script, value); } else { - output.fromOptions(options); - assert(util.isUInt53(output.value), 'Value must be a uint53.'); + output = Output.fromOptions(script); } this.outputs.push(output); @@ -1404,7 +1403,7 @@ MTX.fromRaw = function fromRaw(data, enc) { */ MTX.prototype.toTX = function toTX() { - return new TX(this); + return new TX().inject(this); }; /** @@ -1414,7 +1413,7 @@ MTX.prototype.toTX = function toTX() { */ MTX.fromTX = function fromTX(tx) { - return new MTX(tx); + return new MTX().inject(tx); }; /** @@ -1834,8 +1833,8 @@ function sortRandom(a, b) { } function sortInputs(a, b) { - var ahash = a.prevout.rhash(); - var bhash = b.prevout.rhash(); + var ahash = a.prevout.txid(); + var bhash = b.prevout.txid(); var cmp = util.strcmp(ahash, bhash); if (cmp !== 0) diff --git a/lib/primitives/outpoint.js b/lib/primitives/outpoint.js index 17959f4b..db07be5e 100644 --- a/lib/primitives/outpoint.js +++ b/lib/primitives/outpoint.js @@ -26,8 +26,15 @@ function Outpoint(hash, index) { if (!(this instanceof Outpoint)) return new Outpoint(hash, index); - this.hash = hash || encoding.NULL_HASH; - this.index = index != null ? index : 0xffffffff; + this.hash = encoding.NULL_HASH; + this.index = 0xffffffff; + + if (hash != null) { + assert(typeof hash === 'string', 'Hash must be a string.'); + assert(util.isUInt32(index), 'Index must be a uint32.'); + this.hash = hash; + this.index = index; + } } /** @@ -37,8 +44,9 @@ function Outpoint(hash, index) { */ Outpoint.prototype.fromOptions = function fromOptions(options) { - assert(typeof options.hash === 'string'); - assert(util.isNumber(options.index)); + assert(options, 'Outpoint data is required.'); + assert(typeof options.hash === 'string', 'Hash must be a string.'); + assert(util.isUInt32(options.index), 'Index must be a uint32.'); this.hash = options.hash; this.index = options.index; return this; @@ -73,6 +81,15 @@ Outpoint.prototype.rhash = function rhash() { return util.revHex(this.hash); }; +/** + * Get little-endian hash. + * @returns {Hash} + */ + +Outpoint.prototype.txid = function txid() { + return this.rhash(); +}; + /** * Serialize outpoint to a key * suitable for a hash table. @@ -185,8 +202,9 @@ Outpoint.fromRaw = function fromRaw(data) { */ Outpoint.prototype.fromJSON = function fromJSON(json) { - assert(typeof json.hash === 'string'); - assert(util.isNumber(json.index)); + assert(json, 'Outpoint data is required.'); + assert(typeof json.hash === 'string', 'Hash must be a string.'); + assert(util.isUInt32(json.index), 'Index must be a uint32.'); this.hash = util.revHex(json.hash); this.index = json.index; return this; @@ -225,7 +243,8 @@ Outpoint.fromJSON = function fromJSON(json) { */ Outpoint.prototype.fromTX = function fromTX(tx, index) { - assert(util.isNumber(index)); + assert(tx); + assert(typeof index === 'number'); this.hash = tx.hash('hex'); this.index = index; return this; diff --git a/lib/primitives/output.js b/lib/primitives/output.js index 76e2cf4c..d8bb11a8 100644 --- a/lib/primitives/output.js +++ b/lib/primitives/output.js @@ -11,6 +11,7 @@ var assert = require('assert'); var util = require('../utils/util'); var Amount = require('../btc/amount'); var Network = require('../protocol/network'); +var Address = require('../primitives/address'); var Script = require('../script/script'); var StaticWriter = require('../utils/staticwriter'); var BufferReader = require('../utils/reader'); @@ -47,7 +48,7 @@ Output.prototype.fromOptions = function fromOptions(options) { assert(options, 'Output data is required.'); if (options.value) { - assert(util.isInt53(options.value)); + assert(util.isUInt53(options.value), 'Value must be a uint53.'); this.value = options.value; } @@ -70,6 +71,53 @@ Output.fromOptions = function fromOptions(options) { return new Output().fromOptions(options); }; +/** + * Inject properties from script/value pair. + * @private + * @param {Script|Address} script + * @param {Amount} value + * @returns {Output} + */ + +Output.prototype.fromScript = function fromScript(script, value) { + if (typeof script === 'string') + script = Address.fromBase58(script); + + if (script instanceof Address) + script = Script.fromAddress(script); + + assert(script instanceof Script, 'Script must be a Script.'); + assert(util.isUInt53(value), 'Value must be a uint53.'); + + this.script = script; + this.value = value; + + return this; +}; + +/** + * Instantiate output from script/value pair. + * @param {Script|Address} script + * @param {Amount} value + * @returns {Output} + */ + +Output.fromScript = function fromScript(script, value) { + return new Output().fromScript(script, value); +}; + +/** + * Clone the output. + * @returns {Output} + */ + +Output.prototype.clone = function clone() { + var output = new Output(); + output.value = this.value; + output.script.inject(this.script); + return output; +}; + /** * Get the script type as a string. * @returns {ScriptType} type @@ -199,7 +247,8 @@ Output.prototype.isDust = function isDust(rate) { */ Output.prototype.fromJSON = function fromJSON(json) { - assert(typeof json.value === 'string'); + assert(json, 'Output data is required.'); + assert(typeof json.value === 'string', 'Value must be a string.'); this.value = Amount.value(json.value); this.script.fromJSON(json.script); return this; diff --git a/lib/primitives/tx.js b/lib/primitives/tx.js index dd874d5b..07a4e1ca 100644 --- a/lib/primitives/tx.js +++ b/lib/primitives/tx.js @@ -94,29 +94,29 @@ TX.prototype.fromOptions = function fromOptions(options) { assert(options, 'TX data is required.'); if (options.version != null) { - assert(util.isUInt32(options.version)); + assert(util.isUInt32(options.version), 'Version must be a uint32.'); this.version = options.version; } if (options.flag != null) { - assert(util.isUInt8(options.flag)); + assert(util.isUInt8(options.flag), 'Flag must be a uint8.'); this.flag = options.flag; } if (options.inputs) { - assert(Array.isArray(options.inputs)); + assert(Array.isArray(options.inputs), 'Inputs must be an array.'); for (i = 0; i < options.inputs.length; i++) this.inputs.push(new Input(options.inputs[i])); } if (options.outputs) { - assert(Array.isArray(options.outputs)); + assert(Array.isArray(options.outputs), 'Outputs must be an array.'); for (i = 0; i < options.outputs.length; i++) this.outputs.push(new Output(options.outputs[i])); } if (options.locktime != null) { - assert(util.isUInt32(options.locktime)); + assert(util.isUInt32(options.locktime), 'Locktime must be a uint32.'); this.locktime = options.locktime; } @@ -139,7 +139,36 @@ TX.fromOptions = function fromOptions(options) { */ TX.prototype.clone = function clone() { - return new TX(this); + return new TX().inject(this); +}; + +/** + * Inject properties from tx. + * Used for cloning. + * @private + * @param {TX} tx + * @returns {TX} + */ + +TX.prototype.inject = function inject(tx) { + var i, input, output; + + this.version = tx.version; + this.flag = tx.flag; + + for (i = 0; i < tx.inputs.length; i++) { + input = tx.inputs[i]; + this.inputs.push(input.clone()); + } + + for (i = 0; i < tx.outputs.length; i++) { + output = tx.outputs[i]; + this.outputs.push(output.clone()); + } + + this.locktime = tx.locktime; + + return this; }; /** diff --git a/lib/script/script.js b/lib/script/script.js index 96f83c68..369d5da4 100644 --- a/lib/script/script.js +++ b/lib/script/script.js @@ -147,14 +147,14 @@ Script.prototype.fromOptions = function fromOptions(options) { if (options.raw) { if (!options.code) return this.fromRaw(options.raw); - assert(Buffer.isBuffer(options.raw)); + assert(Buffer.isBuffer(options.raw), 'Raw must be a Buffer.'); this.raw = options.raw; } if (options.code) { if (!options.raw) return this.fromCode(options.code); - assert(Array.isArray(options.code)); + assert(Array.isArray(options.code), 'Code must be an array.'); this.code = options.code; } @@ -284,7 +284,21 @@ Script.fromCode = function fromCode(code) { */ Script.prototype.clone = function clone() { - return new Script(this.raw); + return new Script().inject(this); +}; + +/** + * Inject properties from script. + * Used for cloning. + * @private + * @param {Script} script + * @returns {Script} + */ + +Script.prototype.inject = function inject(script) { + this.code = script.code.slice(); + this.raw = script.raw; + return this; }; /** @@ -388,7 +402,7 @@ Script.prototype.toJSON = function toJSON() { */ Script.prototype.fromJSON = function fromJSON(json) { - assert(typeof json === 'string'); + assert(typeof json === 'string', 'Code must be a string.'); return this.fromRaw(new Buffer(json, 'hex')); }; @@ -417,12 +431,14 @@ Script.prototype.getSubscript = function getSubscript(lastSep) { for (i = lastSep; i < this.code.length; i++) { op = this.code[i]; + if (op.value === -1) break; + code.push(op); } - return new Script(code); + return Script.fromCode(code); }; /** @@ -434,21 +450,42 @@ Script.prototype.getSubscript = function getSubscript(lastSep) { */ Script.prototype.removeSeparators = function removeSeparators() { - var code = []; - var i, op; + var found = false; + var i, op, code; + + // Optimizing for the common case: + // Check for any separators first. + for (i = 0; i < this.code.length; i++) { + op = this.code[i]; + + if (op.value === -1) + break; + + if (op.value === opcodes.OP_CODESEPARATOR) { + found = true; + break; + } + } + + if (!found) + return this.clone(); + + // Uncommon case: someone actually + // has a code separator. Go through + // and remove them all. + code = []; for (i = 0; i < this.code.length; i++) { op = this.code[i]; + if (op.value === -1) break; + if (op.value !== opcodes.OP_CODESEPARATOR) code.push(op); } - if (code.length === this.code.length) - return this.clone(); - - return new Script(code); + return Script.fromCode(code); }; /** diff --git a/lib/script/witness.js b/lib/script/witness.js index bad0fea4..1741f408 100644 --- a/lib/script/witness.js +++ b/lib/script/witness.js @@ -165,7 +165,20 @@ Witness.prototype.toASM = function toASM(decode) { */ Witness.prototype.clone = function clone() { - return new Witness(this.items.slice()); + return new Witness().inject(this); +}; + +/** + * Inject properties from witness. + * Used for cloning. + * @private + * @param {Witness} witness + * @returns {Witness} + */ + +Witness.prototype.inject = function inject(witness) { + this.items = witness.items.slice(); + return this; }; /** @@ -387,7 +400,7 @@ Witness.prototype.toJSON = function toJSON() { */ Witness.prototype.fromJSON = function fromJSON(json) { - assert(typeof json === 'string'); + assert(typeof json === 'string', 'Witness must be a string.'); return this.fromRaw(new Buffer(json, 'hex')); };