From 6d224f6910f77a7552e732ef41a86d18b8bc1baa Mon Sep 17 00:00:00 2001 From: Daniel Cousens Date: Tue, 28 Apr 2015 10:58:00 +1000 Subject: [PATCH 1/5] tests: core tests to use bufferutils.reverse --- test/bitcoin.core.js | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/test/bitcoin.core.js b/test/bitcoin.core.js index bce7741..12ae2ce 100644 --- a/test/bitcoin.core.js +++ b/test/bitcoin.core.js @@ -11,6 +11,7 @@ var ECSignature = Bitcoin.ECSignature var Transaction = Bitcoin.Transaction var Script = Bitcoin.Script +var bufferutils = Bitcoin.bufferutils var networks = Bitcoin.networks var base58_encode_decode = require('./fixtures/core/base58_encode_decode.json') @@ -158,21 +159,18 @@ describe('Bitcoin-core', function () { it('can decode ' + fhex, function () { var transaction = Transaction.fromHex(fhex) - transaction.ins.forEach(function (txin, i) { + transaction.ins.forEach(function (txIn, i) { var input = inputs[i] - var prevOutHash = input[0] + + // reverse because test data is big-endian + var prevOutHash = bufferutils.reverse(new Buffer(input[0], 'hex')) var prevOutIndex = input[1] // var prevOutScriptPubKey = input[2] // TODO: we don't have a ASM parser - var actualHash = txin.hash - - // Test data is big-endian - Array.prototype.reverse.call(actualHash) - - assert.equal(actualHash.toString('hex'), prevOutHash) + assert.deepEqual(txIn.hash, prevOutHash) // we read UInt32, not Int32 - assert.equal(txin.index & 0xffffffff, prevOutIndex) + assert.equal(txIn.index & 0xffffffff, prevOutIndex) }) }) }) @@ -188,7 +186,9 @@ describe('Bitcoin-core', function () { var scriptHex = f[1] var inIndex = f[2] var hashType = f[3] - var expectedHash = f[4] + + // reverse because test data is big-endian + var expectedHash = bufferutils.reverse(new Buffer(f[4], 'hex')) it('should hash ' + txHex + ' correctly', function () { var transaction = Transaction.fromHex(txHex) @@ -207,10 +207,7 @@ describe('Bitcoin-core', function () { } if (actualHash !== undefined) { - // Test data is big-endian - Array.prototype.reverse.call(actualHash) - - assert.equal(actualHash.toString('hex'), expectedHash) + assert.deepEqual(actualHash, expectedHash) } }) }) From ab83f8ffe498bd5a5158ab7874583874e86ab1ea Mon Sep 17 00:00:00 2001 From: Daniel Cousens Date: Tue, 28 Apr 2015 10:47:29 +1000 Subject: [PATCH 2/5] tests: no need to specify 'correctly' --- test/bitcoin.core.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/bitcoin.core.js b/test/bitcoin.core.js index 12ae2ce..57e8168 100644 --- a/test/bitcoin.core.js +++ b/test/bitcoin.core.js @@ -104,7 +104,7 @@ describe('Bitcoin-core', function () { if (!params.isPrivkey) return var keyPair = ECPair.fromWIF(string) - it('imports ' + string + ' correctly', function () { + it('imports ' + string, function () { assert.equal(keyPair.d.toHex(), hex) assert.equal(keyPair.compressed, params.isCompressed) }) From 7ce286654cf0682c179fe8f059b0f679f56cb72e Mon Sep 17 00:00:00 2001 From: Daniel Cousens Date: Tue, 28 Apr 2015 10:38:10 +1000 Subject: [PATCH 3/5] tests: enable all core SIGHASH_* tests --- test/bitcoin.core.js | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/test/bitcoin.core.js b/test/bitcoin.core.js index 57e8168..2861da4 100644 --- a/test/bitcoin.core.js +++ b/test/bitcoin.core.js @@ -190,25 +190,23 @@ describe('Bitcoin-core', function () { // reverse because test data is big-endian var expectedHash = bufferutils.reverse(new Buffer(f[4], 'hex')) - it('should hash ' + txHex + ' correctly', function () { + var hashTypes = [] + if ((hashType & 0x1f) === Transaction.SIGHASH_NONE) hashTypes.push('SIGHASH_NONE') + else if ((hashType & 0x1f) === Transaction.SIGHASH_SINGLE) hashTypes.push('SIGHASH_SINGLE') + else hashTypes.push('SIGHASH_ALL') + if (hashType & Transaction.SIGHASH_ANYONECANPAY) hashTypes.push('SIGHASH_ANYONECANPAY') + + var hashTypeName = hashTypes.join(' | ') + + it('should hash ' + txHex.slice(0, 40) + '... (' + hashTypeName + ')', function () { var transaction = Transaction.fromHex(txHex) assert.equal(transaction.toHex(), txHex) var script = Script.fromHex(scriptHex) assert.equal(script.toHex(), scriptHex) - var actualHash - try { - actualHash = transaction.hashForSignature(inIndex, script, hashType) - } catch (e) { - // don't fail if we don't support it yet, TODO - if (!e.message.match(/not yet supported/)) - throw e - } - - if (actualHash !== undefined) { - assert.deepEqual(actualHash, expectedHash) - } + var hash = transaction.hashForSignature(inIndex, script, hashType) + assert.deepEqual(hash, expectedHash) }) }) }) From 833bf9fa863fa2616639b1fd810f39cd02213bd6 Mon Sep 17 00:00:00 2001 From: Daniel Cousens Date: Tue, 28 Apr 2015 10:35:32 +1000 Subject: [PATCH 4/5] transaction: add SIGHASH_* implementations --- src/transaction.js | 75 ++++++++++++++++++++++++++++++++++++---------- 1 file changed, 60 insertions(+), 15 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() + + // 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 (txIn) { - txIn.script = Script.EMPTY - }) + // 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 = [] + + // 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) - 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') + // 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) }) From 5d36135c919668e4b0a1a4daf63ad1287834775b Mon Sep 17 00:00:00 2001 From: Daniel Cousens Date: Tue, 28 Apr 2015 11:08:23 +1000 Subject: [PATCH 5/5] transaction: s/txout/txOut/ --- src/transaction.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/transaction.js b/src/transaction.js index a0db8b0..63705fa 100644 --- a/src/transaction.js +++ b/src/transaction.js @@ -223,7 +223,7 @@ Transaction.prototype.hashForSignature = function (inIndex, prevOutScript, hashT } else if ((hashType & 0x1f) === Transaction.SIGHASH_SINGLE) { var nOut = inIndex - // only lock-in the txout payee at same index as txIn + // 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