Merge pull request #598 from bitcoinjs/codesep
Add Transaction.hashForSignature tests (and OP_CODESEPARATOR)
This commit is contained in:
commit
27a3f9a326
4 changed files with 86 additions and 41 deletions
|
@ -168,6 +168,10 @@ Transaction.prototype.clone = function () {
|
||||||
|
|
||||||
var ONE = new Buffer('0000000000000000000000000000000000000000000000000000000000000001', 'hex')
|
var ONE = new Buffer('0000000000000000000000000000000000000000000000000000000000000001', 'hex')
|
||||||
var VALUE_UINT64_MAX = new Buffer('ffffffffffffffff', 'hex')
|
var VALUE_UINT64_MAX = new Buffer('ffffffffffffffff', 'hex')
|
||||||
|
var BLANK_OUTPUT = {
|
||||||
|
script: EMPTY_SCRIPT,
|
||||||
|
valueBuffer: VALUE_UINT64_MAX
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hash transaction for signing a specific input.
|
* Hash transaction for signing a specific input.
|
||||||
|
@ -183,61 +187,55 @@ Transaction.prototype.hashForSignature = function (inIndex, prevOutScript, hashT
|
||||||
// https://github.com/bitcoin/bitcoin/blob/master/src/test/sighash_tests.cpp#L29
|
// https://github.com/bitcoin/bitcoin/blob/master/src/test/sighash_tests.cpp#L29
|
||||||
if (inIndex >= this.ins.length) return ONE
|
if (inIndex >= this.ins.length) return ONE
|
||||||
|
|
||||||
var txTmp = this.clone()
|
// ignore OP_CODESEPARATOR
|
||||||
|
var ourScript = bscript.compile(bscript.decompile(prevOutScript).filter(function (x) {
|
||||||
// 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 = bscript.compile(bscript.decompile(prevOutScript).filter(function (x) {
|
|
||||||
return x !== opcodes.OP_CODESEPARATOR
|
return x !== opcodes.OP_CODESEPARATOR
|
||||||
}))
|
}))
|
||||||
var i
|
|
||||||
|
|
||||||
// blank out other inputs' signatures
|
var txTmp = this.clone()
|
||||||
txTmp.ins.forEach(function (input) { input.script = EMPTY_SCRIPT })
|
|
||||||
txTmp.ins[inIndex].script = hashScript
|
|
||||||
|
|
||||||
// blank out some of the inputs
|
// SIGHASH_NONE: ignore all outputs? (wildcard payee)
|
||||||
if ((hashType & 0x1f) === Transaction.SIGHASH_NONE) {
|
if ((hashType & 0x1f) === Transaction.SIGHASH_NONE) {
|
||||||
// wildcard payee
|
|
||||||
txTmp.outs = []
|
txTmp.outs = []
|
||||||
|
|
||||||
// let the others update at will
|
// ignore sequence numbers (except at inIndex)
|
||||||
txTmp.ins.forEach(function (input, i) {
|
txTmp.ins.forEach(function (input, i) {
|
||||||
if (i !== inIndex) {
|
if (i === inIndex) return
|
||||||
input.sequence = 0
|
|
||||||
}
|
input.sequence = 0
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// SIGHASH_SINGLE: ignore all outputs, except at the same index?
|
||||||
} else if ((hashType & 0x1f) === Transaction.SIGHASH_SINGLE) {
|
} 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
|
// https://github.com/bitcoin/bitcoin/blob/master/src/test/sighash_tests.cpp#L60
|
||||||
if (nOut >= this.outs.length) return ONE
|
if (inIndex >= this.outs.length) return ONE
|
||||||
|
|
||||||
txTmp.outs = txTmp.outs.slice(0, nOut + 1)
|
// truncate outputs after
|
||||||
|
txTmp.outs.length = inIndex + 1
|
||||||
|
|
||||||
// blank all other outputs (clear scriptPubKey, value === -1)
|
// "blank" outputs before
|
||||||
var stubOut = {
|
for (var i = 0; i < inIndex; i++) {
|
||||||
script: EMPTY_SCRIPT,
|
txTmp.outs[i] = BLANK_OUTPUT
|
||||||
valueBuffer: VALUE_UINT64_MAX
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (i = 0; i < nOut; i++) {
|
// ignore sequence numbers (except at inIndex)
|
||||||
txTmp.outs[i] = stubOut
|
|
||||||
}
|
|
||||||
|
|
||||||
// let the others update at will
|
|
||||||
txTmp.ins.forEach(function (input, i) {
|
txTmp.ins.forEach(function (input, i) {
|
||||||
if (i !== inIndex) {
|
if (i === inIndex) return
|
||||||
input.sequence = 0
|
|
||||||
}
|
input.sequence = 0
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// blank out other inputs completely, not recommended for open transactions
|
// SIGHASH_ANYONECANPAY: ignore inputs entirely?
|
||||||
if (hashType & Transaction.SIGHASH_ANYONECANPAY) {
|
if (hashType & Transaction.SIGHASH_ANYONECANPAY) {
|
||||||
txTmp.ins[0] = txTmp.ins[inIndex]
|
txTmp.ins = [txTmp.ins[inIndex]]
|
||||||
txTmp.ins = txTmp.ins.slice(0, 1)
|
txTmp.ins[0].script = ourScript
|
||||||
|
|
||||||
|
// SIGHASH_ALL: only ignore input scripts
|
||||||
|
} else {
|
||||||
|
// "blank" others input scripts
|
||||||
|
txTmp.ins.forEach(function (input) { input.script = EMPTY_SCRIPT })
|
||||||
|
txTmp.ins[inIndex].script = ourScript
|
||||||
}
|
}
|
||||||
|
|
||||||
// serialize and hash
|
// serialize and hash
|
||||||
|
|
|
@ -201,9 +201,7 @@ 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]
|
||||||
// reverse because test data is reversed
|
|
||||||
var expectedHash = [].reverse.call(new Buffer(f[4], 'hex'))
|
|
||||||
|
|
||||||
var hashTypes = []
|
var hashTypes = []
|
||||||
if ((hashType & 0x1f) === bitcoin.Transaction.SIGHASH_NONE) hashTypes.push('SIGHASH_NONE')
|
if ((hashType & 0x1f) === bitcoin.Transaction.SIGHASH_NONE) hashTypes.push('SIGHASH_NONE')
|
||||||
|
@ -222,7 +220,9 @@ describe('Bitcoin-core', function () {
|
||||||
assert.strictEqual(bitcoin.script.compile(scriptChunks).toString('hex'), scriptHex)
|
assert.strictEqual(bitcoin.script.compile(scriptChunks).toString('hex'), scriptHex)
|
||||||
|
|
||||||
var hash = transaction.hashForSignature(inIndex, script, hashType)
|
var hash = transaction.hashForSignature(inIndex, script, hashType)
|
||||||
assert.deepEqual(hash, expectedHash)
|
|
||||||
|
// reverse because test data is reversed
|
||||||
|
assert.equal([].reverse.call(hash).toString('hex'), expectedHash)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
39
test/fixtures/transaction.json
vendored
39
test/fixtures/transaction.json
vendored
|
@ -231,6 +231,45 @@
|
||||||
"coinbase": false
|
"coinbase": false
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"hashForSignature": [
|
||||||
|
{
|
||||||
|
"description": "Out of range inIndex",
|
||||||
|
"txHex": "010000000200000000000000000000000000000000000000000000000000000000000000000000000000ffffffff00000000000000000000000000000000000000000000000000000000000000000000000000ffffffff01e8030000000000000000000000",
|
||||||
|
"inIndex": 2,
|
||||||
|
"script": "OP_0",
|
||||||
|
"type": 0,
|
||||||
|
"hash": "0000000000000000000000000000000000000000000000000000000000000001"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "inIndex > nOutputs (SIGHASH_SINGLE)",
|
||||||
|
"txHex": "010000000200000000000000000000000000000000000000000000000000000000000000000000000000ffffffff00000000000000000000000000000000000000000000000000000000000000000000000000ffffffff01e8030000000000000000000000",
|
||||||
|
"inIndex": 2,
|
||||||
|
"script": "OP_0",
|
||||||
|
"type": 3,
|
||||||
|
"hash": "0000000000000000000000000000000000000000000000000000000000000001"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"txHex": "010000000200000000000000000000000000000000000000000000000000000000000000000000000000ffffffff00000000000000000000000000000000000000000000000000000000000000000000000000ffffffff01e8030000000000000000000000",
|
||||||
|
"inIndex": 0,
|
||||||
|
"script": "OP_0 OP_3",
|
||||||
|
"type": 0,
|
||||||
|
"hash": "3d56a632462b9fc9b89eeddcad7dbe476297f34aff7e5f9320e2a99fb5e97136"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"txHex": "010000000200000000000000000000000000000000000000000000000000000000000000000000000000ffffffff00000000000000000000000000000000000000000000000000000000000000000000000000ffffffff01e8030000000000000000000000",
|
||||||
|
"inIndex": 0,
|
||||||
|
"script": "OP_0 OP_CODESEPARATOR OP_3",
|
||||||
|
"type": 0,
|
||||||
|
"hash": "3d56a632462b9fc9b89eeddcad7dbe476297f34aff7e5f9320e2a99fb5e97136"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"txHex": "010000000200000000000000000000000000000000000000000000000000000000000000000000000000ffffffff00000000000000000000000000000000000000000000000000000000000000000000000000ffffffff01e8030000000000000000000000",
|
||||||
|
"inIndex": 0,
|
||||||
|
"script": "OP_0 OP_CODESEPARATOR OP_4",
|
||||||
|
"type": 0,
|
||||||
|
"hash": "fa075877cb54916236806a6562e4a8cdad48adf1268e73d72d1f9fdd867df463"
|
||||||
|
}
|
||||||
|
],
|
||||||
"invalid": {
|
"invalid": {
|
||||||
"addInput": [
|
"addInput": [
|
||||||
{
|
{
|
||||||
|
|
|
@ -172,6 +172,14 @@ describe('Transaction', function () {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
// TODO:
|
describe('hashForSignature', function () {
|
||||||
// hashForSignature: [Function],
|
fixtures.hashForSignature.forEach(function (f) {
|
||||||
|
it('should return ' + f.hash + ' for ' + (f.description ? ('case "' + f.description + '"') : f.script), function () {
|
||||||
|
var tx = Transaction.fromHex(f.txHex)
|
||||||
|
var script = bscript.fromASM(f.script)
|
||||||
|
|
||||||
|
assert.strictEqual(tx.hashForSignature(f.inIndex, script, f.type).toString('hex'), f.hash)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
Loading…
Reference in a new issue