From b020fe1e8098e49739b83fe929529bc8e8d8d09c Mon Sep 17 00:00:00 2001 From: Manuel Araoz Date: Wed, 26 Mar 2014 11:51:28 -0300 Subject: [PATCH] add sighash tests --- Transaction.js | 19 ++---- test/test.sighash.js | 158 +++++++++++++++++++++++++++++++++++++++---- 2 files changed, 153 insertions(+), 24 deletions(-) diff --git a/Transaction.js b/Transaction.js index 8c81bbf..c449117 100644 --- a/Transaction.js +++ b/Transaction.js @@ -445,15 +445,7 @@ Transaction.prototype.hashForSignature = } // Clone transaction - var txTmp = new Transaction(); - this.ins.forEach(function(txin, i) { - txTmp.ins.push(new TransactionIn(txin)); - }); - this.outs.forEach(function(txout) { - txTmp.outs.push(new TransactionOut(txout)); - }); - txTmp.version = this.version; - txTmp.lock_time = this.lock_time; + var txTmp = new Transaction(this); // In case concatenating two scripts ends up with two codeseparators, // or an extra one at the end, this prevents all those possible @@ -505,10 +497,13 @@ Transaction.prototype.hashForSignature = } else { var outsLen; if (hashTypeMode === SIGHASH_SINGLE) { - // TODO: Untested if (inIndex >= txTmp.outs.length) { - throw new Error("Transaction.hashForSignature(): SIGHASH_SINGLE " + - "no corresponding txout found - out of bounds"); + // 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 { diff --git a/test/test.sighash.js b/test/test.sighash.js index 8ef6d1f..eb0471d 100644 --- a/test/test.sighash.js +++ b/test/test.sighash.js @@ -9,9 +9,20 @@ var bitcore = bitcore || require('../bitcore'); var Transaction = bitcore.Transaction; var Script = bitcore.Script; var Opcode = bitcore.Opcode; +var util = bitcore.util; +var Put = bitcore.Put; +var Put = require('bufferput'); +var buffertools = require('buffertools'); + +var seed = 1; +// seedable pseudo-random function +var random = function() { + var x = Math.sin(seed++) * 10000; + return x - Math.floor(x); +}; var randInt = function(low, high) { - return Math.floor(Math.random() * (high - low + 1) + low); + return Math.floor(random() * (high - low + 1) + low); }; var randUIntN = function(nBits) { return randInt(0, Math.pow(2, nBits)); @@ -20,7 +31,7 @@ var randUInt32 = function() { return randUIntN(32); }; var randBool = function() { - return Math.random() < 0.5; + return random() < 0.5; }; var hexAlphabet = '0123456789abcdef'; var randHex = function() { @@ -37,7 +48,7 @@ var randTxHash = function() { return randHexN(64); }; var randPick = function(list) { - return list[randInt(0, list.length-1)]; + return list[randInt(0, list.length - 1)]; }; @@ -45,8 +56,8 @@ var opList = Opcode.asList(); var randomScript = function() { var s = new Script(); - var ops = randInt(0,10); - for (var i=0; i= tx.ins.length) { + throw new Error('Input index "' + inIndex + '" invalid or out of bounds ' + + '(' + tx.ins.length + ' inputs)'); + } + + // 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 + 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); + } + } + } + + // 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(); + + // Append hashType + buffer = Buffer.concat([buffer, new Buffer([parseInt(hashType), 0, 0, 0])]); + + return util.twoSha256(buffer); +}; + + + + + + + + describe('Transaction sighash (#hashForSignature)', function() { - for (var i = 0; i < 10; i++) { - it('should hash correctly random tx #'+(i+1), function() { + for (var i = 0; i < 250; i++) { + it('should hash correctly random tx #' + (i + 1), function() { var tx = randomTx(); - console.log(tx); - tx.hashForSignature(script, inIndex, hashType); - return; + var l = tx.ins.length; + for (var i = 0; i < l; i++) { + var script = randomScript(); + var hashType = randUInt32(); + var h = buffertools.toHex(tx.hashForSignature(script, i, hashType)); + var oh = buffertools.toHex(signatureHashOld(tx, script, i, hashType)); + h.should.equal(oh); + } }); } });