Merge pull request #400 from bitcoinjs/hashfor
Add non SIGHASH_ALL flag support
This commit is contained in:
commit
7b0060d4a5
2 changed files with 83 additions and 43 deletions
|
@ -177,6 +177,8 @@ Transaction.prototype.clone = function () {
|
||||||
return newTx
|
return newTx
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var one = new Buffer('0000000000000000000000000000000000000000000000000000000000000001', 'hex')
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hash transaction for signing a specific input.
|
* Hash transaction for signing a specific input.
|
||||||
*
|
*
|
||||||
|
@ -191,33 +193,71 @@ Transaction.prototype.hashForSignature = function (inIndex, prevOutScript, hashT
|
||||||
typeForce('Number', hashType)
|
typeForce('Number', hashType)
|
||||||
|
|
||||||
assert(inIndex >= 0, 'Invalid vin index')
|
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()
|
var txTmp = this.clone()
|
||||||
var hashScript = prevOutScript.without(opcodes.OP_CODESEPARATOR)
|
|
||||||
|
|
||||||
// Blank out other inputs' signatures
|
// in case concatenating two scripts ends up with two codeseparators,
|
||||||
txTmp.ins.forEach(function (txIn) {
|
// or an extra one at the end, this prevents all those possible incompatibilities.
|
||||||
txIn.script = Script.EMPTY
|
var hashScript = prevOutScript.without(opcodes.OP_CODESEPARATOR)
|
||||||
})
|
var i
|
||||||
|
|
||||||
|
// blank out other inputs' signatures
|
||||||
|
txTmp.ins.forEach(function (input) { input.script = Script.EMPTY })
|
||||||
txTmp.ins[inIndex].script = hashScript
|
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 = []
|
||||||
|
|
||||||
if (hashTypeModifier === Transaction.SIGHASH_NONE) {
|
// let the others update at will
|
||||||
assert(false, 'SIGHASH_NONE not yet supported')
|
txTmp.ins.forEach(function (input, i) {
|
||||||
} else if (hashTypeModifier === Transaction.SIGHASH_SINGLE) {
|
if (i !== inIndex) {
|
||||||
assert(false, 'SIGHASH_SINGLE not yet supported')
|
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)
|
||||||
|
|
||||||
|
// 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) {
|
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)
|
// serialize and hash
|
||||||
hashTypeBuffer.writeInt32LE(hashType, 0)
|
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)
|
return crypto.hash256(buffer)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -267,7 +307,12 @@ Transaction.prototype.toBuffer = function () {
|
||||||
|
|
||||||
writeVarInt(this.outs.length)
|
writeVarInt(this.outs.length)
|
||||||
this.outs.forEach(function (txOut) {
|
this.outs.forEach(function (txOut) {
|
||||||
writeUInt64(txOut.value)
|
if (!txOut.valueBuffer) {
|
||||||
|
writeUInt64(txOut.value)
|
||||||
|
} else {
|
||||||
|
writeSlice(txOut.valueBuffer)
|
||||||
|
}
|
||||||
|
|
||||||
writeVarInt(txOut.script.buffer.length)
|
writeVarInt(txOut.script.buffer.length)
|
||||||
writeSlice(txOut.script.buffer)
|
writeSlice(txOut.script.buffer)
|
||||||
})
|
})
|
||||||
|
|
|
@ -11,6 +11,7 @@ var ECSignature = Bitcoin.ECSignature
|
||||||
var Transaction = Bitcoin.Transaction
|
var Transaction = Bitcoin.Transaction
|
||||||
var Script = Bitcoin.Script
|
var Script = Bitcoin.Script
|
||||||
|
|
||||||
|
var bufferutils = Bitcoin.bufferutils
|
||||||
var networks = Bitcoin.networks
|
var networks = Bitcoin.networks
|
||||||
|
|
||||||
var base58_encode_decode = require('./fixtures/core/base58_encode_decode.json')
|
var base58_encode_decode = require('./fixtures/core/base58_encode_decode.json')
|
||||||
|
@ -103,7 +104,7 @@ describe('Bitcoin-core', function () {
|
||||||
if (!params.isPrivkey) return
|
if (!params.isPrivkey) return
|
||||||
var keyPair = ECPair.fromWIF(string)
|
var keyPair = ECPair.fromWIF(string)
|
||||||
|
|
||||||
it('imports ' + string + ' correctly', function () {
|
it('imports ' + string, function () {
|
||||||
assert.equal(keyPair.d.toHex(), hex)
|
assert.equal(keyPair.d.toHex(), hex)
|
||||||
assert.equal(keyPair.compressed, params.isCompressed)
|
assert.equal(keyPair.compressed, params.isCompressed)
|
||||||
})
|
})
|
||||||
|
@ -158,21 +159,18 @@ describe('Bitcoin-core', function () {
|
||||||
it('can decode ' + fhex, function () {
|
it('can decode ' + fhex, function () {
|
||||||
var transaction = Transaction.fromHex(fhex)
|
var transaction = Transaction.fromHex(fhex)
|
||||||
|
|
||||||
transaction.ins.forEach(function (txin, i) {
|
transaction.ins.forEach(function (txIn, i) {
|
||||||
var input = inputs[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 prevOutIndex = input[1]
|
||||||
// var prevOutScriptPubKey = input[2] // TODO: we don't have a ASM parser
|
// var prevOutScriptPubKey = input[2] // TODO: we don't have a ASM parser
|
||||||
|
|
||||||
var actualHash = txin.hash
|
assert.deepEqual(txIn.hash, prevOutHash)
|
||||||
|
|
||||||
// Test data is big-endian
|
|
||||||
Array.prototype.reverse.call(actualHash)
|
|
||||||
|
|
||||||
assert.equal(actualHash.toString('hex'), prevOutHash)
|
|
||||||
|
|
||||||
// we read UInt32, not Int32
|
// we read UInt32, not Int32
|
||||||
assert.equal(txin.index & 0xffffffff, prevOutIndex)
|
assert.equal(txIn.index & 0xffffffff, prevOutIndex)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -188,30 +186,27 @@ describe('Bitcoin-core', function () {
|
||||||
var scriptHex = f[1]
|
var scriptHex = f[1]
|
||||||
var inIndex = f[2]
|
var inIndex = f[2]
|
||||||
var hashType = f[3]
|
var hashType = f[3]
|
||||||
var expectedHash = f[4]
|
|
||||||
|
|
||||||
it('should hash ' + txHex + ' correctly', function () {
|
// reverse because test data is big-endian
|
||||||
|
var expectedHash = bufferutils.reverse(new Buffer(f[4], 'hex'))
|
||||||
|
|
||||||
|
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)
|
var transaction = Transaction.fromHex(txHex)
|
||||||
assert.equal(transaction.toHex(), txHex)
|
assert.equal(transaction.toHex(), txHex)
|
||||||
|
|
||||||
var script = Script.fromHex(scriptHex)
|
var script = Script.fromHex(scriptHex)
|
||||||
assert.equal(script.toHex(), scriptHex)
|
assert.equal(script.toHex(), scriptHex)
|
||||||
|
|
||||||
var actualHash
|
var hash = transaction.hashForSignature(inIndex, script, hashType)
|
||||||
try {
|
assert.deepEqual(hash, expectedHash)
|
||||||
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) {
|
|
||||||
// Test data is big-endian
|
|
||||||
Array.prototype.reverse.call(actualHash)
|
|
||||||
|
|
||||||
assert.equal(actualHash.toString('hex'), expectedHash)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
Loading…
Reference in a new issue