diff --git a/Transaction.js b/Transaction.js index e013d55..a8dbe45 100644 --- a/Transaction.js +++ b/Transaction.js @@ -294,15 +294,6 @@ Transaction.SIGHASH_NONE = SIGHASH_NONE; Transaction.SIGHASH_SINGLE = SIGHASH_SINGLE; Transaction.SIGHASH_ANYONECANPAY = SIGHASH_ANYONECANPAY; -var oneBuffer = function() { - // bug present in bitcoind which must be also present in bitcore - // see https://bitcointalk.org/index.php?topic=260595 - var ret = new Buffer(1); - ret.writeUInt8(1, 0); - return ret; // return 1 bug - -}; - var TransactionSignatureSerializer = function(txTo, scriptCode, nIn, nHashType) { this.txTo = txTo; this.scriptCode = scriptCode; @@ -406,6 +397,16 @@ TransactionSignatureSerializer.prototype.buffer = function() { return this.bytes.buffer(); }; +Transaction.Serializer = TransactionSignatureSerializer; + +var oneBuffer = function() { + // bug present in bitcoind which must be also present in bitcore + // see https://bitcointalk.org/index.php?topic=260595 + var ret = new Buffer(1); + ret.writeUInt8(1, 0); + return ret; // return 1 bug +}; + Transaction.prototype.hashForSignature = function hashForSignature(script, inIndex, hashType) { @@ -428,108 +429,7 @@ Transaction.prototype.hashForSignature = // Append hashType var hashBuf = new Put().word32le(hashType).buffer(); buffer = Buffer.concat([buffer, hashBuf]); - var bth = buffertools.toHex(buffer); - console.log('tx sig b ' + bth); - var expected = 'f40a750701b5788174aef79788716f96af779d7959147a0c2e0e5bfb6c2dba2df5b4b978940300000004005163acffffffff0445e6fd0200000000096aac536365526a526aa6546b000000000008acab656a6552535141a0fd010000000000c897ea030000000008526500ab526a6a631b39dba395e20496'; - var rawtx = 'f40a750702af06efff3ea68e5d56e42bc41cdb8b6065c98f1221fe04a325a898cb61f3d7ee030000000363acacffffffffb5788174aef79788716f96af779d7959147a0c2e0e5bfb6c2dba2df5b4b97894030000000965510065535163ac6affffffff0445e6fd0200000000096aac536365526a526aa6546b000000000008acab656a6552535141a0fd010000000000c897ea030000000008526500ab526a6a631b39dba3'; - console.log('expected '+expected); - for (var i=0; i= this.outs.length) { - return oneBuffer(); - } - outsLen = inIndex + 1; - } else { - outsLen = this.outs.length; - } - - // TODO: If hashTypeMode !== SIGHASH_SINGLE, we could memcpy this whole - // section from the original transaction as is. - bytes.varint(outsLen); - for (var i = 0; i < outsLen; i++) { - if (hashTypeMode === SIGHASH_SINGLE && i !== inIndex) { - // Zero all outs except the one we want to keep - bytes.put(util.INT64_MAX); - bytes.varint(0); - } else { - bytes.put(this.outs[i].v); - bytes.varint(this.outs[i].s.length); - bytes.put(this.outs[i].s); - } - } - } - - bytes.word32le(this.lock_time); - - var buffer = bytes.buffer(); - - // Append hashType - buffer = Buffer.concat([buffer, new Buffer([parseInt(hashType), 0, 0, 0])]); - - return util.twoSha256(buffer); }; /** diff --git a/test/test.sighash.js b/test/test.sighash.js index 5fa7f87..77ad82b 100644 --- a/test/test.sighash.js +++ b/test/test.sighash.js @@ -95,111 +95,37 @@ var randomTx = function(single) { +var oneBuffer = function() { + // bug present in bitcoind which must be also present in bitcore + // see https://bitcointalk.org/index.php?topic=260595 + var ret = new Buffer(1); + ret.writeUInt8(1, 0); + return ret; // return 1 bug +}; + var signatureHashOld = function(tx, script, inIndex, hashType) { if (+inIndex !== inIndex || inIndex < 0 || inIndex >= tx.ins.length) { - throw new Error('Input index "' + inIndex + '" invalid or out of bounds ' + - '(' + tx.ins.length + ' inputs)'); + return oneBuffer(); } - - // Clone transaction - var txTmp = new Transaction(); - tx.ins.forEach(function(txin) { - txTmp.ins.push(new Transaction.In(txin)); - }); - tx.outs.forEach(function(txout) { - txTmp.outs.push(new Transaction.Out(txout)); - }); - txTmp.version = tx.version; - txTmp.lock_time = tx.lock_time; - - // In case concatenating two scripts ends up with two codeseparators, - // or an extra one at the end, this prevents all those possible - // incompatibilities. - script.findAndDelete(Opcode.map.OP_CODESEPARATOR); - - // Get mode portion of hashtype + // Check for invalid use of SIGHASH_SINGLE var hashTypeMode = hashType & 0x1f; - - // Generate modified transaction data for hash - var bytes = (new Put()); - bytes.word32le(tx.version); - - // Serialize inputs - if (hashType & Transaction.SIGHASH_ANYONECANPAY) { - // Blank out all inputs except current one, not recommended for open - // transactions. - bytes.varint(1); - bytes.put(tx.ins[inIndex].o); - bytes.varint(script.buffer.length); - bytes.put(script.buffer); - bytes.word32le(tx.ins[inIndex].q); - } else { - bytes.varint(tx.ins.length); - for (var i = 0, l = tx.ins.length; i < l; i++) { - var txin = tx.ins[i]; - bytes.put(txin.o); - - // Current input's script gets set to the script to be signed, all others - // get blanked. - if (inIndex === i) { - bytes.varint(script.buffer.length); - bytes.put(script.buffer); - } else { - bytes.varint(0); - } - - if (hashTypeMode === Transaction.SIGHASH_NONE && inIndex !== i) { - bytes.word32le(0); - } else { - bytes.word32le(tx.ins[i].q); - } + if (hashTypeMode === Transaction.SIGHASH_SINGLE) { + if (inIndex >= tx.outs.length) { + return oneBuffer(); } } - // Serialize outputs - if (hashTypeMode === Transaction.SIGHASH_NONE) { - bytes.varint(0); - } else { - var outsLen; - if (hashTypeMode === Transaction.SIGHASH_SINGLE) { - if (inIndex >= txTmp.outs.length) { - // bug present in bitcoind which must be also present in bitcore - // Transaction.hashForSignature(): SIGHASH_SINGLE - // no corresponding txout found - out of bounds - var ret = new Buffer(1); - ret.writeUInt8(1, 0); - return ret; // return 1 bug - } - outsLen = inIndex + 1; - } else { - outsLen = tx.outs.length; - } - - bytes.varint(outsLen); - for (var i = 0; i < outsLen; i++) { - if (hashTypeMode === Transaction.SIGHASH_SINGLE && i !== inIndex) { - // Zero all outs except the one we want to keep - bytes.put(util.INT64_MAX); - bytes.varint(0); - } else { - bytes.put(tx.outs[i].v); - bytes.varint(tx.outs[i].s.length); - bytes.put(tx.outs[i].s); - } - } - } - - bytes.word32le(tx.lock_time); - - var buffer = bytes.buffer(); - + // Wrapper to serialize only the necessary parts of the transaction being signed + var serializer = new Transaction.Serializer(tx, script, inIndex, hashType); + // Serialize + var buffer = serializer.buffer(); // Append hashType - buffer = Buffer.concat([buffer, new Buffer([parseInt(hashType), 0, 0, 0])]); - - return util.twoSha256(buffer); + var hashBuf = new Put().word32le(hashType).buffer(); + buffer = Buffer.concat([buffer, hashBuf]); + return buffertools.reverse(util.twoSha256(buffer)); }; @@ -211,7 +137,7 @@ var signatureHashOld = function(tx, script, inIndex, hashType) { describe('Transaction sighash (#hashForSignature)', function() { for (var i = 0; i < 250; i++) { - it.skip('should hash correctly random tx #' + (i + 1), function() { + it('should hash correctly random tx #' + (i + 1), function() { var tx = randomTx(); var l = tx.ins.length; for (var i = 0; i < l; i++) { @@ -237,7 +163,7 @@ describe('Transaction sighash (#hashForSignature)', function() { var ser_tx = buffertools.toHex(tx.serialize()); ser_tx.should.equal(buffertools.toHex(raw_tx)); var h = buffertools.toHex(tx.hashForSignature(scriptPubKey, input_index, hashType)); - h.should.equal(sighash); // compare our output with bitcoind's + h.should.equal(sighash); // compare our output with bitcoind's output }); });