From 833bf9fa863fa2616639b1fd810f39cd02213bd6 Mon Sep 17 00:00:00 2001 From: Daniel Cousens Date: Tue, 28 Apr 2015 10:35:32 +1000 Subject: [PATCH] transaction: add SIGHASH_* implementations --- src/transaction.js | 77 ++++++++++++++++++++++++++++++++++++---------- 1 file changed, 61 insertions(+), 16 deletions(-) diff --git a/src/transaction.js b/src/transaction.js index 6e8fc6c..a0db8b0 100644 --- a/src/transaction.js +++ b/src/transaction.js @@ -177,6 +177,8 @@ Transaction.prototype.clone = function () { return newTx } +var one = new Buffer('0000000000000000000000000000000000000000000000000000000000000001', 'hex') + /** * Hash transaction for signing a specific input. * @@ -191,33 +193,71 @@ Transaction.prototype.hashForSignature = function (inIndex, prevOutScript, hashT typeForce('Number', hashType) assert(inIndex >= 0, 'Invalid vin index') - assert(inIndex < this.ins.length, 'Invalid vin index') + + // https://github.com/bitcoin/bitcoin/blob/master/src/test/sighash_tests.cpp#L29 + if (inIndex >= this.ins.length) return one var txTmp = this.clone() - var hashScript = prevOutScript.without(opcodes.OP_CODESEPARATOR) - // Blank out other inputs' signatures - txTmp.ins.forEach(function (txIn) { - txIn.script = Script.EMPTY - }) + // in case concatenating two scripts ends up with two codeseparators, + // or an extra one at the end, this prevents all those possible incompatibilities. + var hashScript = prevOutScript.without(opcodes.OP_CODESEPARATOR) + var i + + // blank out other inputs' signatures + txTmp.ins.forEach(function (input) { input.script = Script.EMPTY }) txTmp.ins[inIndex].script = hashScript - var hashTypeModifier = hashType & 0x1f + // blank out some of the inputs + if ((hashType & 0x1f) === Transaction.SIGHASH_NONE) { + // wildcard payee + txTmp.outs = [] - if (hashTypeModifier === Transaction.SIGHASH_NONE) { - assert(false, 'SIGHASH_NONE not yet supported') - } else if (hashTypeModifier === Transaction.SIGHASH_SINGLE) { - assert(false, 'SIGHASH_SINGLE not yet supported') + // let the others update at will + txTmp.ins.forEach(function (input, i) { + if (i !== inIndex) { + input.sequence = 0 + } + }) + + } else if ((hashType & 0x1f) === Transaction.SIGHASH_SINGLE) { + var nOut = inIndex + + // only lock-in the txout payee at same index as txIn + // https://github.com/bitcoin/bitcoin/blob/master/src/test/sighash_tests.cpp#L60 + if (nOut >= this.outs.length) return one + + txTmp.outs = txTmp.outs.slice(0, nOut + 1) + + // blank all other outputs (clear scriptPubKey, value === -1) + var stubOut = { + script: Script.EMPTY, + valueBuffer: new Buffer('ffffffffffffffff', 'hex') + } + + for (i = 0; i < nOut; i++) { + txTmp.outs[i] = stubOut + } + + // let the others update at will + txTmp.ins.forEach(function (input, i) { + if (i !== inIndex) { + input.sequence = 0 + } + }) } + // blank out other inputs completely, not recommended for open transactions if (hashType & Transaction.SIGHASH_ANYONECANPAY) { - assert(false, 'SIGHASH_ANYONECANPAY not yet supported') + txTmp.ins[0] = txTmp.ins[inIndex] + txTmp.ins = txTmp.ins.slice(0, 1) } - var hashTypeBuffer = new Buffer(4) - hashTypeBuffer.writeInt32LE(hashType, 0) + // serialize and hash + var buffer = new Buffer(txTmp.byteLength() + 4) + buffer.writeInt32LE(hashType, buffer.length - 4) + txTmp.toBuffer().copy(buffer, 0) - var buffer = Buffer.concat([txTmp.toBuffer(), hashTypeBuffer]) return crypto.hash256(buffer) } @@ -267,7 +307,12 @@ Transaction.prototype.toBuffer = function () { writeVarInt(this.outs.length) this.outs.forEach(function (txOut) { - writeUInt64(txOut.value) + if (!txOut.valueBuffer) { + writeUInt64(txOut.value) + } else { + writeSlice(txOut.valueBuffer) + } + writeVarInt(txOut.script.buffer.length) writeSlice(txOut.script.buffer) })