Merge pull request #270 from dcousens/txfix
TransactionBuilder to correctly handle non-zero vin in inputs
This commit is contained in:
commit
0312f7ff60
6 changed files with 99 additions and 8 deletions
|
@ -238,9 +238,13 @@ function multisigInput(signatures, scriptPubKey) {
|
||||||
if (scriptPubKey) {
|
if (scriptPubKey) {
|
||||||
assert(isMultisigOutput.call(scriptPubKey))
|
assert(isMultisigOutput.call(scriptPubKey))
|
||||||
|
|
||||||
var m = scriptPubKey.chunks[0]
|
var mOp = scriptPubKey.chunks[0]
|
||||||
var k = m - (opcodes.OP_1 - 1)
|
var nOp = scriptPubKey.chunks[scriptPubKey.chunks.length - 2]
|
||||||
assert(k <= signatures.length, 'Not enough signatures provided')
|
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))
|
return Script.fromChunks([].concat(opcodes.OP_0, signatures))
|
||||||
|
|
|
@ -52,6 +52,7 @@ Transaction.prototype.addInput = function(tx, index, sequence) {
|
||||||
assert(Buffer.isBuffer(hash), 'Expected Transaction, txId or txHash, got ' + tx)
|
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(hash.length, 32, 'Expected hash length of 32, got ' + hash.length)
|
||||||
assert.equal(typeof index, 'number', 'Expected number index, got ' + index)
|
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
|
// Add the input and return the input's index
|
||||||
return (this.ins.push({
|
return (this.ins.push({
|
||||||
|
|
|
@ -30,10 +30,14 @@ TransactionBuilder.fromTransaction = function(transaction) {
|
||||||
})
|
})
|
||||||
|
|
||||||
// Extract/add signatures
|
// Extract/add signatures
|
||||||
transaction.ins.forEach(function(txin) {
|
transaction.ins.forEach(function(txin, i) {
|
||||||
// Ignore empty scripts
|
// Ignore empty scripts
|
||||||
if (txin.script.buffer.length === 0) return
|
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 redeemScript
|
||||||
var scriptSig = txin.script
|
var scriptSig = txin.script
|
||||||
var scriptType = scripts.classifyInput(scriptSig)
|
var scriptType = scripts.classifyInput(scriptSig)
|
||||||
|
@ -83,10 +87,10 @@ TransactionBuilder.fromTransaction = function(transaction) {
|
||||||
break
|
break
|
||||||
|
|
||||||
default:
|
default:
|
||||||
assert(false, scriptType + ' not supported')
|
assert(false, scriptType + ' inputs not supported')
|
||||||
}
|
}
|
||||||
|
|
||||||
txb.signatures[txin.index] = {
|
txb.signatures[i] = {
|
||||||
hashType: hashType,
|
hashType: hashType,
|
||||||
pubKeys: pubKeys,
|
pubKeys: pubKeys,
|
||||||
redeemScript: redeemScript,
|
redeemScript: redeemScript,
|
||||||
|
@ -231,13 +235,18 @@ TransactionBuilder.prototype.sign = function(index, privKey, redeemScript, hashT
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
prevOutScript = prevOutScript || privKey.pub.getAddress().toOutputScript()
|
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)
|
hash = this.tx.hashForSignature(index, prevOutScript, hashType)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.prevOutScripts[index] = prevOutScript
|
||||||
|
this.prevOutTypes[index] = prevOutType
|
||||||
|
|
||||||
if (!(index in this.signatures)) {
|
if (!(index in this.signatures)) {
|
||||||
this.signatures[index] = {
|
this.signatures[index] = {
|
||||||
hashType: hashType,
|
hashType: hashType,
|
||||||
|
@ -246,6 +255,8 @@ TransactionBuilder.prototype.sign = function(index, privKey, redeemScript, hashT
|
||||||
scriptType: scriptType,
|
scriptType: scriptType,
|
||||||
signatures: []
|
signatures: []
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
assert.equal(scriptType, 'multisig', scriptType + ' doesn\'t support multiple signatures')
|
||||||
}
|
}
|
||||||
|
|
||||||
var input = this.signatures[index]
|
var input = this.signatures[index]
|
||||||
|
|
13
test/fixtures/scripts.json
vendored
13
test/fixtures/scripts.json
vendored
|
@ -101,6 +101,19 @@
|
||||||
"304402207515cf147d201f411092e6be5a64a6006f9308fad7b2a8fdaab22cd86ce764c202200974b8aca7bf51dbf54150d3884e1ae04f675637b926ec33bf75939446f6ca2801"
|
"304402207515cf147d201f411092e6be5a64a6006f9308fad7b2a8fdaab22cd86ce764c202200974b8aca7bf51dbf54150d3884e1ae04f675637b926ec33bf75939446f6ca2801"
|
||||||
],
|
],
|
||||||
"scriptPubKey": false
|
"scriptPubKey": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"exception": "Too many signatures provided",
|
||||||
|
"pubKeys": [
|
||||||
|
"02359c6e3f04cefbf089cf1d6670dc47c3fb4df68e2bad1fa5a369f9ce4b42bbd1",
|
||||||
|
"0395a9d84d47d524548f79f435758c01faec5da2b7e551d3b8c995b7e06326ae4a"
|
||||||
|
],
|
||||||
|
"signatures": [
|
||||||
|
"304402207515cf147d201f411092e6be5a64a6006f9308fad7b2a8fdaab22cd86ce764c202200974b8aca7bf51dbf54150d3884e1ae04f675637b926ec33bf75939446f6ca2801",
|
||||||
|
"3045022100ef253c1faa39e65115872519e5f0a33bbecf430c0f35cf562beabbad4da24d8d02201742be8ee49812a73adea3007c9641ce6725c32cd44ddb8e3a3af460015d140501",
|
||||||
|
"3045022100ef253c1faa39e65115872519e5f0a33bbecf430c0f35cf562beabbad4da24d8d02201742be8ee49812a73adea3007c9641ce6725c32cd44ddb8e3a3af460015d140501"
|
||||||
|
],
|
||||||
|
"scriptPubKey": false
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
30
test/fixtures/transaction_builder.json
vendored
30
test/fixtures/transaction_builder.json
vendored
|
@ -63,6 +63,26 @@
|
||||||
"value": 10000
|
"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"
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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() {
|
describe('when redeemScript is defined', function() {
|
||||||
it('assumes scriptHash', function() {
|
it('assumes scriptHash', function() {
|
||||||
txb.addInput(prevTxHash, 0)
|
txb.addInput(prevTxHash, 0)
|
||||||
|
@ -182,6 +203,7 @@ describe('TransactionBuilder', function() {
|
||||||
var tx = txb.build()
|
var tx = txb.build()
|
||||||
|
|
||||||
assert.equal(tx.getId(), f.txid)
|
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() {
|
it('works for the P2SH multisig case', function() {
|
||||||
var privKeys = [
|
var privKeys = [
|
||||||
"91avARGdfge8E4tZfYLoxeJ5sGBdNJQH4kvjJoQFacbgwmaKkrx",
|
"91avARGdfge8E4tZfYLoxeJ5sGBdNJQH4kvjJoQFacbgwmaKkrx",
|
||||||
|
|
Loading…
Reference in a new issue