From 476cc4870244dd74155bb31f7ea95142a7dbc02c Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Mon, 3 Oct 2016 04:55:32 -0700 Subject: [PATCH] tx: improve witness parsing. --- lib/primitives/tx.js | 111 ++++++++++++++++++++++++------------------- 1 file changed, 63 insertions(+), 48 deletions(-) diff --git a/lib/primitives/tx.js b/lib/primitives/tx.js index fbc31cca..77fe2356 100644 --- a/lib/primitives/tx.js +++ b/lib/primitives/tx.js @@ -2128,7 +2128,7 @@ TX.fromRaw = function fromRaw(data, enc) { */ TX.prototype.fromRaw = function fromRaw(data) { - var p, i, inCount, outCount; + var p, i, count; if (TX.isWitness(data)) return this.fromWitness(data); @@ -2138,14 +2138,14 @@ TX.prototype.fromRaw = function fromRaw(data) { this.version = p.readU32(); // Technically signed - inCount = p.readVarint(); + count = p.readVarint(); - for (i = 0; i < inCount; i++) + for (i = 0; i < count; i++) this.inputs.push(Input.fromRaw(p)); - outCount = p.readVarint(); + count = p.readVarint(); - for (i = 0; i < outCount; i++) + for (i = 0; i < count; i++) this.outputs.push(Output.fromRaw(p)); this.locktime = p.readU32(); @@ -2170,48 +2170,60 @@ TX.prototype.fromRaw = function fromRaw(data) { TX.prototype.fromWitness = function fromWitness(data) { var p = BufferReader(data); - var i, marker, inCount, outCount, input, hasWitness, witnessSize; + var flag = 0; + var witnessSize = 0; + var hasWitness = false; + var i, count, input; p.start(); this.version = p.readU32(); // Technically signed - marker = p.readU8(); - this.flag = p.readU8(); + assert(p.readU8() === 0, 'Non-zero marker.'); - if (marker !== 0) - throw new Error('Invalid witness tx (marker != 0)'); + flag = p.readU8(); - if (this.flag === 0) - throw new Error('Invalid witness tx (flag == 0)'); + assert(flag !== 0, 'Flag byte is zero.'); - inCount = p.readVarint(); + this.flag = flag; - for (i = 0; i < inCount; i++) + count = p.readVarint(); + + for (i = 0; i < count; i++) this.inputs.push(Input.fromRaw(p)); - outCount = p.readVarint(); + count = p.readVarint(); - for (i = 0; i < outCount; i++) + for (i = 0; i < count; i++) this.outputs.push(Output.fromRaw(p)); - p.start(); + if (flag & 1) { + flag ^= 1; - for (i = 0; i < inCount; i++) { - input = this.inputs[i]; - input.witness.fromRaw(p); - if (input.witness.items.length > 0) - hasWitness = true; + p.start(); + + for (i = 0; i < this.inputs.length; i++) { + input = this.inputs[i]; + input.witness.fromRaw(p); + if (input.witness.items.length > 0) + hasWitness = true; + } + + witnessSize = p.end() + 2; } - if (!hasWitness) - throw new Error('Witness tx has an empty witness.'); + if (flag !== 0) + throw new Error('Unknown witness flag.'); - witnessSize = p.end() + 2; + // We'll never be able to reserialize + // this to get the regular txid, and + // there's no way it's valid anyway. + if (this.inputs.length === 0 && this.outputs.length !== 0) + throw new Error('Zero input witness tx.'); this.locktime = p.readU32(); - if (!this.mutable) { + if (!this.mutable && hasWitness) { this._raw = p.endData(); this._size = this._raw.length; this._witnessSize = witnessSize; @@ -2222,27 +2234,6 @@ TX.prototype.fromWitness = function fromWitness(data) { return this; }; -/** - * Test whether data is a witness transaction. - * @param {Buffer|BufferReader} data - * @returns {Boolean} - */ - -TX.isWitness = function isWitness(data) { - if (Buffer.isBuffer(data)) { - if (data.length < 12) - return false; - - return data[4] === 0 && data[5] !== 0; - } - - if (data.left() < 12) - return false; - - return data.data[data.offset + 4] === 0 - && data.data[data.offset + 5] !== 0; -}; - /** * Serialize transaction without witness. * @private @@ -2254,7 +2245,7 @@ TX.prototype.frameNormal = function frameNormal(writer) { var p = BufferWriter(writer); var i; - if (this.inputs.length === 0 && this.outputs.length === 1) + if (this.inputs.length === 0 && this.outputs.length !== 0) throw new Error('Cannot serialize zero-input tx.'); p.write32(this.version); @@ -2292,6 +2283,9 @@ TX.prototype.frameWitness = function frameWitness(writer) { var witnessSize = 0; var i, start; + if (this.inputs.length === 0 && this.outputs.length !== 0) + throw new Error('Cannot serialize zero-input tx.'); + p.write32(this.version); p.writeU8(0); p.writeU8(this.flag); @@ -2326,6 +2320,27 @@ TX.prototype.frameWitness = function frameWitness(writer) { return p; }; +/** + * Test whether data is a witness transaction. + * @param {Buffer|BufferReader} data + * @returns {Boolean} + */ + +TX.isWitness = function isWitness(data) { + if (Buffer.isBuffer(data)) { + if (data.length < 6) + return false; + + return data[4] === 0 && data[5] !== 0; + } + + if (data.left() < 6) + return false; + + return data.data[data.offset + 4] === 0 + && data.data[data.offset + 5] !== 0; +}; + /** * Serialize a transaction to BCoin "extended format". * This is the serialization format BCoin uses internally