From 41eab6e27c366053eb9685bfddb31124edadc2e1 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Tue, 15 Mar 2016 14:14:53 -0700 Subject: [PATCH] sighashing. --- lib/bcoin/tx.js | 148 +++++++++++++++++--------------------------- lib/bcoin/writer.js | 13 ++++ 2 files changed, 71 insertions(+), 90 deletions(-) diff --git a/lib/bcoin/tx.js b/lib/bcoin/tx.js index b419d813..c6c2a4f2 100644 --- a/lib/bcoin/tx.js +++ b/lib/bcoin/tx.js @@ -176,18 +176,33 @@ TX.prototype._inputIndex = function _inputIndex(hash, index) { return -1; }; -TX.prototype.signatureHash = function signatureHash(index, s, type, version) { +TX.prototype.signatureHash = function signatureHash(index, prev, type, version) { assert(version >= 0 && version <= 1); + + // Traditional sighashing if (version === 0) - return this.signatureHashV0(index, s, type); + return this.signatureHashV0(index, prev, type); + + // Segwit sighashing if (version === 1) - return this.signatureHashV1(index, s, type); + return this.signatureHashV1(index, prev, type); }; -TX.prototype.signatureHashV0 = function signatureHashV0(index, s, type) { - var i, msg, hash; +TX.prototype.signatureHashV0 = function signatureHashV0(index, prev, type) { + var p = new BufferWriter(); + var i, copy; - var copy = { + if (typeof index !== 'number') + index = this.inputs.indexOf(index); + + if (typeof type === 'string') + type = constants.hashType[type]; + + assert(index >= 0 && index < copy.inputs.length); + assert(prev instanceof bcoin.script); + + // Clone the transaction. + copy = { version: this.version, inputs: [], outputs: [], @@ -210,31 +225,16 @@ TX.prototype.signatureHashV0 = function signatureHashV0(index, s, type) { }); } - if (typeof index !== 'number') - index = this.inputs.indexOf(index); - - if (typeof type === 'string') - type = constants.hashType[type]; - - assert(index >= 0 && index < copy.inputs.length); - assert(s instanceof bcoin.script); - - // Disable this for now. We allow null hash types - // because bitcoind allows empty signatures. On - // another note, we allow all weird sighash types - // if strictenc is not enabled. - // assert(utils.isFinite(type)); - // Remove all signatures. for (i = 0; i < copy.inputs.length; i++) copy.inputs[i].script = new Script([]); // Set our input to previous output's script - copy.inputs[index].script = s; + copy.inputs[index].script = prev; if ((type & 0x1f) === constants.hashType.none) { // Drop all outputs. We don't want to sign them. - copy.outputs = []; + copy.outputs.length = 0; // Allow input sequence updates for other inputs. for (i = 0; i < copy.inputs.length; i++) { @@ -271,20 +271,16 @@ TX.prototype.signatureHashV0 = function signatureHashV0(index, s, type) { copy.inputs.length = 1; } - copy = bcoin.protocol.framer.tx(copy); + // Render the copy and append the hashtype. + bcoin.protocol.framer.tx(copy, p); + p.writeU32(type); - msg = new Buffer(copy.length + 4); - utils.copy(copy, msg, 0); - utils.writeU32(msg, type, copy.length); - - hash = utils.dsha256(msg); - - return hash; + return utils.dsha256(p.render()); }; -TX.prototype.signatureHashV1 = function signatureHashV1(index, s, type) { - var i, msg, hash, hashPrevouts, hashSequence, hashOutputs; - var size, outputs, output, off, prev; +TX.prototype.signatureHashV1 = function signatureHashV1(index, prev, type) { + var p = new BufferWriter(); + var i, prevout, hashPrevouts, hashSequence, hashOutputs; if (typeof index !== 'number') index = this.inputs.indexOf(index); @@ -293,85 +289,57 @@ TX.prototype.signatureHashV1 = function signatureHashV1(index, s, type) { type = constants.hashType[type]; assert(index >= 0 && index < this.inputs.length); - assert(s instanceof bcoin.script); + assert(prev instanceof bcoin.script); if (!(type & constants.hashType.anyonecanpay)) { - hashPrevouts = new Buffer(36 * this.inputs.length); - off = 0; + hashPrevouts = new BufferWriter(); for (i = 0; i < this.inputs.length; i++) { - prev = this.inputs[i].prevout; - off += utils.copy(new Buffer(prev.hash, 'hex'), hashPrevouts, off); - off += utils.writeU32(hashPrevouts, prev.index, off); + prevout = this.inputs[i].prevout; + hashPrevouts.writeHash(prevout.hash); + hashPrevouts.writeU32(prevout.index); } - hashPrevouts = utils.dsha256(hashPrevouts); + hashPrevouts = utils.dsha256(hashPrevouts.render()); } else { - hashPrevouts = new Buffer(32); - hashPrevouts.fill(0); + hashPrevouts = utils.slice(constants.zeroHash); } if (!(type & constants.hashType.anyonecanpay) && (type & 0x1f) !== constants.hashType.single && (type & 0x1f) !== constants.hashType.none) { - hashSequence = new Buffer(4 * this.inputs.length); - off = 0; + hashSequence = new BufferWriter(); for (i = 0; i < this.inputs.length; i++) - off += utils.writeU32(hashSequence, this.inputs[i].sequence, off); - hashSequence = utils.dsha256(hashSequence); + p.writeU32(this.inputs[i].sequence); + hashSequence = utils.dsha256(hashSequence.render()); } else { - hashSequence = new Buffer(32); - hashSequence.fill(0); + hashSequence = utils.slice(constants.zeroHash); } if ((type & 0x1f) !== constants.hashType.single && (type & 0x1f) !== constants.hashType.none) { - size = 0; - outputs = []; - for (i = 0; i < this.outputs.length; i++) { - output = bcoin.protocol.framer.output(this.outputs[i]); - size += output.length; - outputs.push(output); - } - hashOutputs = new Buffer(size); - off = 0; - for (i = 0; i < outputs.length; i++) - off += utils.copy(outputs[i], hashOutputs, off); - hashOutputs = utils.dsha256(hashOutputs); + hashOutputs = new BufferWriter(); + for (i = 0; i < this.outputs.length; i++) + bcoin.protocol.framer.output(this.outputs[i], hashOutputs); + hashOutputs = utils.dsha256(hashOutputs.render()); } else if ((type & 0x1f) === constants.hashType.single && index < this.outputs.length) { hashOutputs = bcoin.protocol.framer.output(this.outputs[index]); hashOutputs = utils.dsha256(hashOutputs); } else { - hashOutputs = new Buffer(32); - hashOutputs.fill(0); + hashOutputs = utils.slice(constants.zeroHash); } - s = s.encode(); + p.write32(this.version); + p.writeBytes(hashPrevouts); + p.writeBytes(hashSequence); + p.writeHash(this.inputs[index].prevout.hash); + p.writeU32(this.inputs[index].prevout.index); + p.writeVarBytes(prev.encode()); + p.write64(this.inputs[index].output.value); + p.writeU32(this.inputs[index].sequence); + p.writeBytes(hashOutputs); + p.writeU32(this.locktime); + p.writeU32(type); - msg = new Buffer( - 4 + 32 + 32 + 36 - + utils.sizeIntv(s.length) - + s.length - + 8 + 4 + 32 + 4 + 4); - - off = 0; - off += utils.write32(msg, this.version, off); - off += utils.copy(hashPrevouts, msg, off); - off += utils.copy(hashSequence, msg, off); - off += utils.copy(new Buffer(this.inputs[index].prevout.hash, 'hex'), msg, off); - off += utils.writeU32(msg, this.inputs[index].prevout.index, off); - assert(off === 4 + 32 + 32 + 36); - off += utils.writeIntv(msg, s.length, off); - off += utils.copy(s, msg, off); - off += utils.write64(msg, this.inputs[index].output.value, off); - off += utils.writeU32(msg, this.inputs[index].sequence, off); - off += utils.copy(hashOutputs, msg, off); - off += utils.writeU32(msg, this.locktime, off); - assert(off === msg.length - 4); - off += utils.writeU32(msg, type, off); - assert(off === msg.length); - - hash = utils.dsha256(msg); - - return hash; + return utils.dsha256(p.render()); }; TX.prototype.verify = function verify(index, force, flags) { diff --git a/lib/bcoin/writer.js b/lib/bcoin/writer.js index ee812e7c..a59ce125 100644 --- a/lib/bcoin/writer.js +++ b/lib/bcoin/writer.js @@ -52,6 +52,12 @@ BufferWriter.prototype.render = function render() { return data; }; +BufferWriter.prototype.destroy = function destroy() { + this.data.length = 0; + delete this.data; + delete this.written; +}; + BufferWriter.prototype.writeU8 = function writeU8(value) { this.written += 1; this.data.push(['u8', value]); @@ -171,6 +177,13 @@ BufferWriter.prototype.writeUIntv = function writeUIntv(value) { this.data.push(['varint', value]); }; +BufferWriter.prototype.fill = function fill(value, size) { + var buf = new Buffer(size); + buf.fill(value); + this.written += buf.length; + this.data.push(['bytes', buf]); +}; + /** * Expose */