From a4f0807c506e6635bc6f7f6c1a4e54c30119402d Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Sun, 17 Apr 2016 19:34:03 -0700 Subject: [PATCH] strict scripting. --- lib/bcoin/block.js | 10 +- lib/bcoin/compactblock.js | 7 +- lib/bcoin/input.js | 15 +- lib/bcoin/mtx.js | 8 +- lib/bcoin/output.js | 15 +- lib/bcoin/protocol/parser.js | 8 +- lib/bcoin/script.js | 309 +++++++++++++++++++++++++++-------- lib/bcoin/tx.js | 4 +- 8 files changed, 268 insertions(+), 108 deletions(-) diff --git a/lib/bcoin/block.js b/lib/bcoin/block.js index 33869591..633ceb1a 100644 --- a/lib/bcoin/block.js +++ b/lib/bcoin/block.js @@ -387,7 +387,7 @@ Block.prototype._verify = function _verify(ret) { */ Block.prototype.getCoinbaseHeight = function getCoinbaseHeight() { - var coinbase, code, height; + var coinbase, height; if (this.version < 2) return -1; @@ -400,13 +400,7 @@ Block.prototype.getCoinbaseHeight = function getCoinbaseHeight() { if (!coinbase || coinbase.inputs.length === 0) return -1; - code = coinbase.inputs[0].script.code; - - if (Buffer.isBuffer(code[0]) && code[0].length <= 6) - height = new bn(code[0], 'le').toNumber(); - else - height = -1; - + height = coinbase.inputs[0].script.getCoinbaseHeight(); this._cbHeight = height; return height; diff --git a/lib/bcoin/compactblock.js b/lib/bcoin/compactblock.js index 91131a77..18d05284 100644 --- a/lib/bcoin/compactblock.js +++ b/lib/bcoin/compactblock.js @@ -55,12 +55,7 @@ function CompactBlock(data) { bcoin.abstractblock.call(this, data); this.type = 'compactblock'; - this.coinbaseHeight = -1; - - if (this.version >= 2) { - if (Buffer.isBuffer(data.coinbaseHeight) && data.coinbaseHeight.length <= 6) - this.coinbaseHeight = new bn(data.coinbaseHeight, 'le').toNumber(); - } + this.coinbaseHeight = data.coinbaseHeight; } utils.inherits(CompactBlock, bcoin.abstractblock); diff --git a/lib/bcoin/input.js b/lib/bcoin/input.js index 2fa961a8..cfb640e6 100644 --- a/lib/bcoin/input.js +++ b/lib/bcoin/input.js @@ -18,7 +18,7 @@ var BufferWriter = require('./writer'); * @exports Input * @constructor * @param {NakedInput} options - * @param {TX?} tx + * @param {Boolean?} mutable * @property {Object} prevout - Outpoint. * @property {Hash} prevout.hash - Previous transaction hash. * @property {Number} prevout.index - Previous output index. @@ -28,17 +28,18 @@ var BufferWriter = require('./writer'); * @property {Coin?} coin - Previous output. * @property {String} type - Script type. * @property {String?} address - Input address. + * @property {Boolean} mutable */ -function Input(options, tx) { +function Input(options, mutable) { if (!(this instanceof Input)) return new Input(options); + this.mutable = !!mutable; this.prevout = options.prevout; - this.script = bcoin.script(options.script); + this.script = bcoin.script(options.script, this.mutable); this.sequence = options.sequence == null ? 0xffffffff : options.sequence; - this.witness = bcoin.script.witness(options.witness); - this._mutable = !tx || (tx instanceof bcoin.mtx); + this.witness = bcoin.script.witness(options.witness, this.mutable); if (options.coin) this.coin = bcoin.coin(options.coin); @@ -83,7 +84,7 @@ Input.prototype.getType = function getType() { if (!type || type === 'unknown') type = this.script.getInputType(); - if (!this._mutable) + if (!this.mutable) this._type = type; return type; @@ -160,7 +161,7 @@ Input.prototype.getAddress = function getAddress() { if (!address) address = this.script.getInputAddress(); - if (!this._mutable) + if (!this.mutable) this._address = address; return address; diff --git a/lib/bcoin/mtx.js b/lib/bcoin/mtx.js index 36ace670..30893e70 100644 --- a/lib/bcoin/mtx.js +++ b/lib/bcoin/mtx.js @@ -252,7 +252,7 @@ MTX.prototype.addInput = function addInput(options, index) { assert(options.prevout); - input = bcoin.input(options, this); + input = bcoin.input(options, true); if (options.script instanceof Script) input.script = options.script.clone(); @@ -386,7 +386,7 @@ MTX.prototype.scriptInput = function scriptInput(index, addr) { vector[1] = addr.publicKey; } else if (prev.isMultisig()) { // Multisig - if (utils.indexOf(prev.code, addr.publicKey) === -1) + if (prev.code.indexOf(addr.publicKey) === -1) return false; // Already has a script template (at least) @@ -405,7 +405,7 @@ MTX.prototype.scriptInput = function scriptInput(index, addr) { for (i = 0; i < n; i++) vector[i + 1] = dummy; } else { - if (utils.indexOf(prev.code, addr.publicKey) === -1) + if (prev.code.indexOf(addr.publicKey) === -1) return false; // Already has a script template (at least) @@ -799,7 +799,7 @@ MTX.prototype.addOutput = function addOutput(obj, value) { options = obj; } - output = bcoin.output(options, this); + output = bcoin.output(options, true); this.outputs.push(output); diff --git a/lib/bcoin/output.js b/lib/bcoin/output.js index 4fb86457..6029082a 100644 --- a/lib/bcoin/output.js +++ b/lib/bcoin/output.js @@ -16,14 +16,15 @@ var assert = utils.assert; * @exports Output * @constructor * @param {NakedOutput} options - * @param {TX?} tx + * @param {Boolean?} mutable * @property {BN} value - Value in satoshis. * @property {Script} script * @property {String} type - Script type. * @property {String?} address - Input address. + * @property {Boolean} mutable */ -function Output(options, tx) { +function Output(options, mutable) { var value; if (!(this instanceof Output)) @@ -36,12 +37,12 @@ function Output(options, tx) { value = new bn(value); } + this.mutable = !!mutable; this.value = utils.satoshi(value || new bn(0)); - this.script = bcoin.script(options.script); - this._mutable = !tx || (tx instanceof bcoin.mtx); + this.script = bcoin.script(options.script, this.mutable); assert(typeof value !== 'number'); - assert(!this._mutable || !this.value.isNeg()); + assert(!this.mutable || !this.value.isNeg()); } Output.prototype.__defineGetter__('type', function() { @@ -65,7 +66,7 @@ Output.prototype.getType = function getType() { type = this.script.getType(); - if (!this._mutable) + if (!this.mutable) this._type = type; return type; @@ -84,7 +85,7 @@ Output.prototype.getAddress = function getAddress() { address = this.script.getAddress(); - if (!this._mutable) + if (!this.mutable) this._address = address; return address; diff --git a/lib/bcoin/protocol/parser.js b/lib/bcoin/protocol/parser.js index b841944f..68592ea2 100644 --- a/lib/bcoin/protocol/parser.js +++ b/lib/bcoin/protocol/parser.js @@ -937,10 +937,10 @@ Parser.parseBlockCompact = function parseBlockCompact(p) { input = Parser.parseInput(p); } - if (input) { - if (Buffer.isBuffer(input.script.code[0])) - height = input.script.code[0]; - } + if (input) + height = bcoin.script.getCoinbaseHeight(input.script.code); + else + height = -1; raw = p.data; diff --git a/lib/bcoin/script.js b/lib/bcoin/script.js index 35fcfed4..8b5a6f26 100644 --- a/lib/bcoin/script.js +++ b/lib/bcoin/script.js @@ -35,15 +35,25 @@ var STACK_NEGATE = new Buffer([0xff]); * @constructor * @param {Buffer[]|Buffer|NakedWitness} items - Array of * stack items or raw witness buffer. + * @param {Boolean} mutable - Whether the script will + * be changed in the future. + * @property {Buffer[]} items + * @property {Script?} redeem + * @property {Boolean} mutable */ -function Witness(items) { - if (items instanceof Witness) +function Witness(items, mutable) { + if (items instanceof Witness) { + items.mutable = !!mutable; + items.redeem = null; return items; + } if (!(this instanceof Witness)) return new Witness(items); + this.mutable = !!mutable; + if (!items) items = []; @@ -167,12 +177,25 @@ Witness.prototype.isUnknownInput = function isUnknownInput() { */ Witness.prototype.getRedeem = function getRedeem() { + if (this.mutable) + return Script.getRedeem(this.items); + if (!this.redeem) this.redeem = Script.getRedeem(this.items); return this.redeem; }; +/** + * Find a data element in a witness. + * @param {Buffer} data - Data element to match against. + * @returns {Number} Index (`-1` if not present). + */ + +Witness.prototype.indexOf = function indexOf(data) { + return utils.indexOf(this.items, data); +}; + /** * Parse a formatted script * string into a witness object. _Must_ @@ -761,18 +784,27 @@ Stack.isStack = function isStack(obj) { * @constructor * @param {Buffer|Array|Object|NakedScript} code - Array * of script code or a serialized script Buffer. + * @param {Boolean} mutable - Whether the script will + * be changed in the future. * @property {Array} code - Script code. - * @property {Buffer} raw - Serialized script. - * @property {Script} redeem - Redeem script. + * @property {Buffer?} raw - Serialized script. + * @property {Script?} redeem - Redeem script. + * @property {Boolean} mutable */ -function Script(code) { - if (code instanceof Script) +function Script(code, mutable) { + if (code instanceof Script) { + code.mutable = !!mutable; + code.raw = null; + code.redeem = null; return code; + } if (!(this instanceof Script)) return new Script(code); + this.mutable = !!mutable; + if (Buffer.isBuffer(code)) { this.raw = code; this.code = Script.decode(code); @@ -791,6 +823,9 @@ function Script(code) { } } + if (this.mutable) + this.raw = null; + this.redeem = null; } @@ -820,8 +855,12 @@ Script.prototype.inspect = function inspect() { */ Script.prototype.encode = function encode() { + if (this.mutable) + return Script.encode(this.code); + if (!this.raw) this.raw = Script.encode(this.code); + return this.raw; }; @@ -920,12 +959,14 @@ Script.prototype.interpret = function interpret(stack, flags, tx, index, version op = this.code[ip]; if (Buffer.isBuffer(op)) { + if (!Script.checkPush(op)) + throw new ScriptError('Pushdata out of range.', op, ip); if (op.length > constants.script.MAX_PUSH) - throw new ScriptError('Push data too large.', op, ip); + throw new ScriptError('Pushdata too large.', op, ip); // Note that minimaldata is not checked // on unexecuted branches of code. if (stack.negate === 0) { - if (!Script.checkPush(op, flags)) + if (!Script.checkMinimal(op, flags)) throw new ScriptError('Push verification failed.', op, ip); stack.push(op); } @@ -1366,7 +1407,8 @@ Script.prototype.interpret = function interpret(stack, flags, tx, index, version type = sig[sig.length - 1]; subscript = this.getSubscript(lastSep); - subscript.removeData(sig); + if (version === 0) + subscript.removeData(sig); hash = tx.signatureHash(index, subscript, type, version); @@ -1418,7 +1460,8 @@ Script.prototype.interpret = function interpret(stack, flags, tx, index, version for (i = 0; i < m; i++) { sig = stack.get(stack.length - 1 - i); - subscript.removeData(sig); + if (version === 0) + subscript.removeData(sig); } succ = 0; @@ -1680,7 +1723,6 @@ Script.checkSequence = function checkSequence(sequence, tx, i) { /** * Cast a big number or Buffer to a bool. * @see CastToBool - * @static * @param {BN|Buffer} value * @returns {Boolean} */ @@ -1707,7 +1749,6 @@ Script.bool = function bool(value) { /** * Create a CScriptNum. - * @static * @param {Buffer} value * @param {Number?} flags - Script standard flags. * @param {Number?} size - Max size in bytes. @@ -1763,7 +1804,6 @@ Script.num = function num(value, flags, size) { * Create a script array. Will convert Numbers and big * numbers to a little-endian buffer while taking into * account negative zero, minimaldata, etc. - * @static * @example * assert.deepEqual(Script.array(0), new Buffer([])); * assert.deepEqual(Script.array(0xffee), new Buffer([0xee, 0xff])); @@ -1801,27 +1841,76 @@ Script.array = function(value) { * a script's code (used to remove signatures * before verification). * @param {Buffer} data - Data element to match against. + * @returns {Number} Total. */ Script.prototype.removeData = function removeData(data) { - for (var i = this.code.length - 1; i >= 0; i--) { + var total = 0; + var i; + + for (i = this.code.length - 1; i >= 0; i--) { if (!Buffer.isBuffer(this.code[i])) continue; - if (utils.isEqual(this.code[i], data)) + if (utils.isEqual(this.code[i], data)) { this.code.splice(i, 1); + total++; + } } + + return total; +}; + +/** + * Find a data element in a script. + * @param {Buffer} data - Data element to match against. + * @returns {Number} Index (`-1` if not present). + */ + +Script.prototype.indexOf = function indexOf(data) { + return utils.indexOf(this.code, data); +}; + +/** + * Test whether an op is a buffer, also + * check for buffer underflows. + * @param {Buffer?} value + * @returns {Boolean} + */ + +Script.isPush = function isPush(value) { + return Buffer.isBuffer(value) && Script.checkPush(value); +}; + +/** + * Perform some range checking on the pushdatas + * (exactly what GetOp2 does). Note that this + * _must_ be done during execution, not parsing. + * @see GetOp2 + * @param {Buffer} value - Pushdata op from script code + * (must be from a deserialized script). + * @returns {Boolean} + */ + +Script.checkPush = function checkPush(value) { + var pushdata = value.pushdata; + + if (!pushdata) + return true; + + // The pushdata size can never + // be greater than the buffer. + return pushdata.size === value.length; }; /** * Check to see if a pushdata Buffer abides by minimaldata. - * @static * @param {Buffer} value - Pushdata op from script code * (must be from a deserialized script). * @param {Number?} flags * @returns {Boolean} */ -Script.checkPush = function checkPush(value, flags) { +Script.checkMinimal = function checkMinimal(value, flags) { var pushdata = value.pushdata; if (flags == null) @@ -2012,6 +2101,9 @@ Script.createCommitment = function createCommitment(hash) { */ Script.prototype.getRedeem = function getRedeem() { + if (this.mutable) + return Script.getRedeem(this.code); + if (!this.redeem) this.redeem = Script.getRedeem(this.code); @@ -2020,7 +2112,6 @@ Script.prototype.getRedeem = function getRedeem() { /** * Grab and deserialize the redeem script from script code. - * @static * @param {Array} code * @returns {Script|null} Redeem script. */ @@ -2028,7 +2119,7 @@ Script.prototype.getRedeem = function getRedeem() { Script.getRedeem = function getRedeem(code) { var redeem = code[code.length - 1]; - if (!Buffer.isBuffer(redeem)) + if (!Script.isPush(redeem)) return; return new Script(redeem); @@ -2086,9 +2177,6 @@ Script.prototype.isStandard = function isStandard() { if (m < 1 || m > n) return false; - } else if (type === 'nulldata') { - if (this.getSize() > constants.script.MAX_OP_RETURN_BYTES) - return false; } return type !== 'unknown'; @@ -2136,10 +2224,14 @@ Script.prototype.isStandardProgram = function isStandardProgram(witness, flags) }; /** - * @returns {Number} Size of script excluding the varint size bytes. + * Calculate size of script excluding the varint size bytes. + * @returns {Number} */ Script.prototype.getSize = function getSize() { + if (this.mutable) + return Script.encode(this.code, new BufferWriter()).written; + return this.encode().length; }; @@ -2365,9 +2457,29 @@ Script.prototype.isScripthash = function isScripthash() { */ Script.prototype.isNulldata = function isNulldata() { - return this.code.length === 2 - && this.code[0] === opcodes.OP_RETURN - && Script.isData(this.code[1]); + var i, op; + + if (this.raw && this.raw.length > constants.script.MAX_OP_RETURN_BYTES) + return false; + + if (this.code.length === 0) + return false; + + if (this.code[0] !== opcodes.OP_RETURN) + return false; + + for (i = 1; i < this.code.length; i++) { + op = this.code[i]; + if (Buffer.isBuffer(op)) { + if (!Script.checkPush(op)) + return false; + continue; + } + if (op > opcodes.OP_16) + return false; + } + + return true; }; /** @@ -2380,9 +2492,20 @@ Script.prototype.isNulldata = function isNulldata() { */ Script.prototype.isCommitment = function isCommitment() { + if (this.raw) { + if (this.raw.length < 38) + return false; + if (this.raw[0] !== opcodes.OP_RETURN) + return false; + if (this.raw[1] !== 0x24) + return false; + if (utils.readU32BE(this.raw, 2) !== 0xaa21a9ed) + return false; + return true; + } return this.code.length >= 2 && this.code[0] === opcodes.OP_RETURN - && Buffer.isBuffer(this.code[1]) + && Script.isPush(this.code[1]) && this.code[1].length === 36 && utils.readU32BE(this.code[1], 0) === 0xaa21a9ed; }; @@ -2407,13 +2530,29 @@ Script.prototype.getCommitmentHash = function getCommitmentHash() { */ Script.prototype.isWitnessProgram = function isWitnessProgram() { + // Witness programs are strict minimaldata. + if (this.raw) { + if (!(this.raw.length >= 4 && this.raw.length <= 34)) + return false; + + if (this.raw[0] !== opcodes.OP_0 + && !(this.raw[0] >= opcodes.OP_1 && this.raw[0] <= opcodes.OP_16)) { + return false; + } + + if (this.raw[1] + 2 !== this.raw.length) + return false; + + return true; + } + if (this.code.length !== 2) return false; if (typeof this.code[0] !== 'number') return false; - if (!Buffer.isBuffer(this.code[1])) + if (!Script.isPush(this.code[1])) return false; return (this.code[0] === opcodes.OP_0 @@ -2531,7 +2670,6 @@ Script.isUnknownInput = function isUnknownInput(code, isWitness) { /** * Automatically build an output script from any number of options. - * @static * @example * Script.createOutputScript({ address: '1HT7xU2Ngenf7D4yocz2SAcnNLW7rK8d4E' }); * @param {Object} options @@ -2712,7 +2850,7 @@ Script.isScripthashInput = function isScripthashInput(code, isWitness) { // Last data element should be an array // for the redeem script. - if (!Buffer.isBuffer(raw)) + if (!Script.isPush(raw)) return false; // Testing for scripthash inputs requires @@ -2739,6 +2877,39 @@ Script.isScripthashInput = function isScripthashInput(code, isWitness) { return true; }; +/** + * Get coinbase height. + * @returns {Number} `-1` if not present. + */ + +Script.prototype.getCoinbaseHeight = function getCoinbaseHeight() { + return Script.getCoinbaseHeight(this.code); +}; + +/** + * Get coinbase height. + * @returns {Number} `-1` if not present. + */ + +Script.getCoinbaseHeight = function getCoinbaseHeight(code) { + if (code.length === 0) + return -1; + + if (!Buffer.isBuffer(code[0])) + return -1; + + if (!Script.checkPush(code[0])) + return -1; + + if (!Script.checkMinimal(code[0])) + return -1; + + if (code[0].length > 6) + return -1; + + return new bn(code[0], 'le').toNumber(); +}; + /** * Get info about a coinbase script. * @returns {Object} Object containing `height`, @@ -2749,10 +2920,7 @@ Script.prototype.getCoinbaseData = function getCoinbaseData() { var coinbase = {}; var flags; - if (Buffer.isBuffer(this.code[0]) && this.code[0].length <= 6) - coinbase.height = new bn(this.code[0], 'le').toNumber(); - else - coinbase.height = -1; + coinbase.height = this.getCoinbaseHeight(); if (Buffer.isBuffer(this.code[1])) coinbase.extraNonce = new bn(this.code[1], 'le'); @@ -2781,7 +2949,7 @@ Script.prototype.getCoinbaseData = function getCoinbaseData() { */ Script.isHash = function isHash(hash) { - return Buffer.isBuffer(hash) && hash.length === 20; + return Script.isPush(hash) && hash.length === 20; }; /** @@ -2792,7 +2960,7 @@ Script.isHash = function isHash(hash) { */ Script.isKey = function isKey(key) { - return Buffer.isBuffer(key) && key.length >= 33 && key.length <= 65; + return Script.isPush(key) && key.length >= 33 && key.length <= 65; }; /** @@ -2803,7 +2971,7 @@ Script.isKey = function isKey(key) { */ Script.isSignature = function isSignature(sig) { - return Buffer.isBuffer(sig) && sig.length >= 9 && sig.length <= 73; + return Script.isPush(sig) && sig.length >= 9 && sig.length <= 73; }; /** @@ -2813,7 +2981,7 @@ Script.isSignature = function isSignature(sig) { */ Script.isDummy = function isDummy(data) { - return Buffer.isBuffer(data) && data.length === 0; + return Script.isPush(data) && data.length === 0; }; /** @@ -2830,16 +2998,6 @@ Script.isZero = function isZero(op) { return Script.isDummy(op); }; -/** - * Test whether the data element is a valid nulldata push. - * @param {Buffer?} data - * @returns {Boolean} - */ - -Script.isData = function isData(data) { - return Buffer.isBuffer(data) && data.length <= constants.script.MAX_OP_RETURN; -}; - /** * Test whether the data element is a valid key if VERIFY_STRICTENC is enabled. * @param {Buffer} key @@ -2851,7 +3009,7 @@ Script.isValidKey = function isValidKey(key, flags) { if (flags == null) flags = constants.flags.STANDARD_VERIFY_FLAGS; - if (!Buffer.isBuffer(key)) + if (!Script.isPush(key)) return false; if (flags & constants.flags.VERIFY_STRICTENC) { @@ -2871,7 +3029,7 @@ Script.isValidKey = function isValidKey(key, flags) { */ Script.isKeyEncoding = function isKeyEncoding(key) { - if (!Buffer.isBuffer(key)) + if (!Script.isPush(key)) return false; if (key.length < 33) @@ -2905,7 +3063,7 @@ Script.isValidSignature = function isValidSignature(sig, flags) { if (flags == null) flags = constants.flags.STANDARD_VERIFY_FLAGS; - if (!Buffer.isBuffer(sig)) + if (!Script.isPush(sig)) return false; // Allow empty sigs @@ -2948,7 +3106,7 @@ Script.isValidSignature = function isValidSignature(sig, flags) { Script.isSignatureEncoding = function isSignatureEncoding(sig) { var lenR, lenS; - if (!Buffer.isBuffer(sig)) + if (!Script.isPush(sig)) return false; // Format: 0x30 [total-length] 0x02 [R-length] [R] 0x02 [S-length] [S] [sighash] @@ -3039,7 +3197,7 @@ Script.isSignatureEncoding = function isSignatureEncoding(sig) { Script.isHashType = function isHashType(sig) { var type; - if (!Buffer.isBuffer(sig)) + if (!Script.isPush(sig)) return false; if (sig.length === 0) @@ -3061,7 +3219,7 @@ Script.isHashType = function isHashType(sig) { Script.isLowDER = function isLowDER(sig) { if (!sig.s) { - if (!Buffer.isBuffer(sig)) + if (!Script.isPush(sig)) return false; if (!Script.isSignatureEncoding(sig)) @@ -3130,13 +3288,13 @@ Script.prototype.isPushOnly = function isPushOnly() { var i, op; for (i = 0; i < this.code.length; i++) { op = this.code[i]; - if (Buffer.isBuffer(op) - || op === opcodes.OP_1NEGATE - || op === opcodes.OP_0 - || (op >= opcodes.OP_1 && op <= opcodes.OP_16)) { + if (Buffer.isBuffer(op)) { + if (!Script.checkPush(op)) + return false; continue; } - return false; + if (op > opcodes.OP_16) + return false; } return true; }; @@ -3156,7 +3314,7 @@ Script.prototype.getSigops = function getSigops(accurate) { for (i = 0; i < this.code.length; i++) { op = this.code[i]; - if (Buffer.isBuffer(op)) + if (Script.isPush(op)) continue; if (constants.opcodesByVal[op] == null) @@ -3407,7 +3565,7 @@ Script.verify = function verify(input, witness, output, tx, i, flags) { // Grab the real redeem script raw = stack.pop(); - if (!Buffer.isBuffer(raw)) + if (!Script.isPush(raw)) return false; redeem = new Script(raw); @@ -3628,6 +3786,20 @@ Script.parseScript = function parseScript(buf) { * this will apply hidden `pushdata` properties * to each Buffer if the buffer was created from * a non-standard pushdata. + * + * This function does not do bounds checking + * on buffers because some jackass could do a + * direct push of 30 bytes with only 20 bytes + * after it. That script would be perfectly + * fine _until_ it is executed. There are + * output scripts on the blockchain that can + * never be redeemed due to this, but they are + * in valid blocks, therefore we cannot fail + * parsing them. + * + * Also note that this function uses reference + * Buffer slices. Larger buffer slices should + * _never_ be passed in here. * @param {Buffer} buf - Serialized script. * @returns {Array} Script code. */ @@ -3639,12 +3811,6 @@ Script.decode = function decode(buf) { assert(Buffer.isBuffer(buf)); - // NOTE: We can't use a BufferReader here since - // script parsing was originally non-strict/ridiculous. - // Something could do a direct push of 30 bytes with - // only 20 bytes after it. - // NOTE 2: We use reference Buffer slices here. Larger - // buffer slices should _never_ be passed in here. while (off < buf.length) { op = buf[off++]; if (op >= 0x01 && op <= 0x4b) { @@ -3708,8 +3874,8 @@ Script.decode = function decode(buf) { * @returns {Buffer} Serialized script. */ -Script.encode = function encode(code) { - var p = new BufferWriter(); +Script.encode = function encode(code, writer) { + var p = new BufferWriter(writer); var i = 0; var op; @@ -3772,7 +3938,10 @@ Script.encode = function encode(code) { p.writeU8(op); } - return p.render(); + if (!writer) + p = p.render(); + + return p; }; /** diff --git a/lib/bcoin/tx.js b/lib/bcoin/tx.js index bc7ac612..c8d0423f 100644 --- a/lib/bcoin/tx.js +++ b/lib/bcoin/tx.js @@ -82,12 +82,12 @@ function TX(data, block, index) { if (data.inputs) { for (i = 0; i < data.inputs.length; i++) - this.inputs.push(new bcoin.input(data.inputs[i], this)); + this.inputs.push(new bcoin.input(data.inputs[i])); } if (data.outputs) { for (i = 0; i < data.outputs.length; i++) - this.outputs.push(new bcoin.output(data.outputs[i], this)); + this.outputs.push(new bcoin.output(data.outputs[i])); } if (block && this.ts === 0) {