diff --git a/src/scripts.js b/src/scripts.js index 661285a..88e3e61 100644 --- a/src/scripts.js +++ b/src/scripts.js @@ -238,9 +238,13 @@ function multisigInput(signatures, scriptPubKey) { if (scriptPubKey) { assert(isMultisigOutput.call(scriptPubKey)) - var m = scriptPubKey.chunks[0] - var k = m - (opcodes.OP_1 - 1) - assert(k <= signatures.length, 'Not enough signatures provided') + var mOp = scriptPubKey.chunks[0] + var nOp = scriptPubKey.chunks[scriptPubKey.chunks.length - 2] + var m = mOp - (opcodes.OP_1 - 1) + var n = nOp - (opcodes.OP_1 - 1) + + assert(signatures.length >= m, 'Not enough signatures provided') + assert(signatures.length <= n, 'Too many signatures provided') } return Script.fromChunks([].concat(opcodes.OP_0, signatures)) diff --git a/src/transaction.js b/src/transaction.js index a9a0899..ffb9522 100644 --- a/src/transaction.js +++ b/src/transaction.js @@ -52,6 +52,7 @@ Transaction.prototype.addInput = function(tx, index, sequence) { assert(Buffer.isBuffer(hash), 'Expected Transaction, txId or txHash, got ' + tx) assert.equal(hash.length, 32, 'Expected hash length of 32, got ' + hash.length) assert.equal(typeof index, 'number', 'Expected number index, got ' + index) + assert.equal(typeof sequence, 'number', 'Expected number sequence, got ' + sequence) // Add the input and return the input's index return (this.ins.push({ diff --git a/src/transaction_builder.js b/src/transaction_builder.js index 05c0591..c0acb09 100644 --- a/src/transaction_builder.js +++ b/src/transaction_builder.js @@ -30,10 +30,14 @@ TransactionBuilder.fromTransaction = function(transaction) { }) // Extract/add signatures - transaction.ins.forEach(function(txin) { + transaction.ins.forEach(function(txin, i) { // Ignore empty scripts if (txin.script.buffer.length === 0) return + assert(!Array.prototype.every.call(txin.hash, function(x) { + return x === 0 + }), 'coinbase inputs not supported') + var redeemScript var scriptSig = txin.script var scriptType = scripts.classifyInput(scriptSig) @@ -83,10 +87,10 @@ TransactionBuilder.fromTransaction = function(transaction) { break default: - assert(false, scriptType + ' not supported') + assert(false, scriptType + ' inputs not supported') } - txb.signatures[txin.index] = { + txb.signatures[i] = { hashType: hashType, pubKeys: pubKeys, redeemScript: redeemScript, @@ -231,13 +235,18 @@ TransactionBuilder.prototype.sign = function(index, privKey, redeemScript, hashT } else { prevOutScript = prevOutScript || privKey.pub.getAddress().toOutputScript() - scriptType = prevOutType || 'pubkeyhash' + prevOutType = prevOutType || 'pubkeyhash' - assert.notEqual(scriptType, 'scripthash', 'PrevOutScript requires redeemScript') + assert.notEqual(prevOutType, 'scripthash', 'PrevOutScript is P2SH, missing redeemScript') + + scriptType = prevOutType hash = this.tx.hashForSignature(index, prevOutScript, hashType) } + this.prevOutScripts[index] = prevOutScript + this.prevOutTypes[index] = prevOutType + if (!(index in this.signatures)) { this.signatures[index] = { hashType: hashType, @@ -246,6 +255,8 @@ TransactionBuilder.prototype.sign = function(index, privKey, redeemScript, hashT scriptType: scriptType, signatures: [] } + } else { + assert.equal(scriptType, 'multisig', scriptType + ' doesn\'t support multiple signatures') } var input = this.signatures[index] diff --git a/test/fixtures/scripts.json b/test/fixtures/scripts.json index 666ea59..e264a4a 100644 --- a/test/fixtures/scripts.json +++ b/test/fixtures/scripts.json @@ -101,6 +101,19 @@ "304402207515cf147d201f411092e6be5a64a6006f9308fad7b2a8fdaab22cd86ce764c202200974b8aca7bf51dbf54150d3884e1ae04f675637b926ec33bf75939446f6ca2801" ], "scriptPubKey": false + }, + { + "exception": "Too many signatures provided", + "pubKeys": [ + "02359c6e3f04cefbf089cf1d6670dc47c3fb4df68e2bad1fa5a369f9ce4b42bbd1", + "0395a9d84d47d524548f79f435758c01faec5da2b7e551d3b8c995b7e06326ae4a" + ], + "signatures": [ + "304402207515cf147d201f411092e6be5a64a6006f9308fad7b2a8fdaab22cd86ce764c202200974b8aca7bf51dbf54150d3884e1ae04f675637b926ec33bf75939446f6ca2801", + "3045022100ef253c1faa39e65115872519e5f0a33bbecf430c0f35cf562beabbad4da24d8d02201742be8ee49812a73adea3007c9641ce6725c32cd44ddb8e3a3af460015d140501", + "3045022100ef253c1faa39e65115872519e5f0a33bbecf430c0f35cf562beabbad4da24d8d02201742be8ee49812a73adea3007c9641ce6725c32cd44ddb8e3a3af460015d140501" + ], + "scriptPubKey": false } ] } diff --git a/test/fixtures/transaction_builder.json b/test/fixtures/transaction_builder.json index c2444ea..a44078b 100644 --- a/test/fixtures/transaction_builder.json +++ b/test/fixtures/transaction_builder.json @@ -63,6 +63,26 @@ "value": 10000 } ] + }, + { + "description": "Transaction w/ non-zero vin inputs", + "txid": "7d9b699f26765fdfdd598223a952a6e129f8c159e2e05e911af822ee743fa745", + "txhex": "0100000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff010000006a47304402205c80bbb5125b35d5e5a8324b1336832d29a6fc004859c8a9ff6bef47ba7fc348022018612216e57a521b2c4543f1f4fd738a76814c37c074e88adfe12464fff31cf901210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ffffffff0110270000000000001976a914aa4d7985c57e011a8b3dd8e0e5a73aaef41629c588ac00000000", + "inputs": [ + { + "index": 1, + "prevTx": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "privKeys": [ + "KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn" + ] + } + ], + "outputs": [ + { + "script": "OP_DUP OP_HASH160 aa4d7985c57e011a8b3dd8e0e5a73aaef41629c5 OP_EQUALVERIFY OP_CHECKSIG", + "value": 10000 + } + ] } ] }, @@ -147,6 +167,16 @@ } ] } + ], + "fromTransaction": [ + { + "exception": "coinbase inputs not supported", + "hex":"01000000010000000000000000000000000000000000000000000000000000000000000000000000006b483045022100a3b254e1c10b5d039f36c05f323995d6e5a367d98dd78a13d5bbc3991b35720e022022fccea3897d594de0689601fbd486588d5bfa6915be2386db0397ee9a6e80b601210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ffffffff0110270000000000001976a914aa4d7985c57e011a8b3dd8e0e5a73aaef41629c588ac00000000" + }, + { + "exception": "nonstandard inputs not supported", + "hex": "0100000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000023aa206fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d619000000000087ffffffff0110270000000000001976a914aa4d7985c57e011a8b3dd8e0e5a73aaef41629c588ac00000000" + } ] } } diff --git a/test/transaction_builder.js b/test/transaction_builder.js index d6ab2f0..0bc1032 100644 --- a/test/transaction_builder.js +++ b/test/transaction_builder.js @@ -110,6 +110,27 @@ describe('TransactionBuilder', function() { }) }) + it('throws if scriptType doesn\'t support multiple signatures', function() { + txb.addInput(prevTxHash, 0) + txb.sign(0, privKey) + + assert.throws(function() { + txb.sign(0, privKey) + }, /pubkeyhash doesn\'t support multiple signatures/) + }) + + describe('when redeemScript is undefined', function() { + it('throws if prevOutScript is P2SH', function() { + var privScriptP2SH = scripts.scriptHashOutput(privScript.getHash()) + + txb.addInput(prevTxHash, 0, undefined, privScriptP2SH) + + assert.throws(function() { + txb.sign(0, privKey) + }, /PrevOutScript is P2SH, missing redeemScript/) + }) + }) + describe('when redeemScript is defined', function() { it('assumes scriptHash', function() { txb.addInput(prevTxHash, 0) @@ -182,6 +203,7 @@ describe('TransactionBuilder', function() { var tx = txb.build() assert.equal(tx.getId(), f.txid) + assert.equal(tx.toHex(), f.txhex) }) }) @@ -234,6 +256,16 @@ describe('TransactionBuilder', function() { }) }) + fixtures.invalid.fromTransaction.forEach(function(f,i) { + it('throws on ' + f.exception, function() { + var tx = Transaction.fromHex(f.hex) + + assert.throws(function() { + TransactionBuilder.fromTransaction(tx) + }, new RegExp(f.exception)) + }) + }) + it('works for the P2SH multisig case', function() { var privKeys = [ "91avARGdfge8E4tZfYLoxeJ5sGBdNJQH4kvjJoQFacbgwmaKkrx",