From 998368168c83f275e84a258357f53099c9b96e10 Mon Sep 17 00:00:00 2001 From: Thomas Kerin Date: Wed, 9 Nov 2016 17:13:14 +0100 Subject: [PATCH 1/3] Solve Output: take an output script, returns {type, script, solvedBy, requiredSigs, canSign} --- src/templates/index.js | 58 +++++++++++++++++++++++++++++++++++++++++- test/script.js | 24 +++++++++++++++++ 2 files changed, 81 insertions(+), 1 deletion(-) diff --git a/src/templates/index.js b/src/templates/index.js index 78a8eba97..666075e1d 100644 --- a/src/templates/index.js +++ b/src/templates/index.js @@ -17,6 +17,9 @@ var types = { P2WSH: 'witnessscripthash' } +var SIGNABLE_SCRIPTS = [types.MULTISIG, types.P2PKH, types.P2PK] +var P2SH_SCRIPTS = SIGNABLE_SCRIPTS.concat([types.P2WPKH, types.P2WSH]) + function classifyOutput (script) { if (witnessPubKeyHash.output.check(script)) return types.P2WPKH if (witnessScriptHash.output.check(script)) return types.P2WSH @@ -54,10 +57,61 @@ function classifyWitness (script, allowIncomplete) { return types.NONSTANDARD } +function solveOutput (scriptCode) { + if (!(scriptCode instanceof Buffer)) { + throw new Error('Argument 0 for solveScript must be a Buffer') + } + + var outputType = classifyOutput(scriptCode) + var solvedBy = null + var requiredSigs = null + + switch (outputType) { + // We can only determine the relevant hash from these, not if it's signable + case types.P2SH: + solvedBy = scriptHash.output.decode(scriptCode) + break + case types.P2WSH: + solvedBy = witnessScriptHash.output.decode(scriptCode) + break + + // P2WPKH is separate from other signable types, it's best viewed as a special case for P2PKH + // Not included in canSign. + case types.P2WPKH: + solvedBy = witnessPubKeyHash.output.decode(scriptCode) + requiredSigs = 1 + break + + // We can immediately solve signatures for these + // When adding a new script type, edit here + case types.P2PK: + solvedBy = pubKey.output.decode(scriptCode) + requiredSigs = 1 + break + case types.P2PKH: + solvedBy = pubKeyHash.output.decode(scriptCode) + requiredSigs = 1 + break + case types.MULTISIG: + solvedBy = multisig.output.decode(scriptCode) + requiredSigs = solvedBy.m + break + } + + return { + type: outputType, + script: scriptCode, + solvedBy: solvedBy, + canSign: SIGNABLE_SCRIPTS.indexOf(outputType) !== -1, + requiredSigs: requiredSigs + } +} + module.exports = { classifyInput: classifyInput, classifyOutput: classifyOutput, classifyWitness: classifyWitness, + solveOutput: solveOutput, multisig: multisig, nullData: nullData, pubKey: pubKey, @@ -65,5 +119,7 @@ module.exports = { scriptHash: scriptHash, witnessPubKeyHash: witnessPubKeyHash, witnessScriptHash: witnessScriptHash, - types: types + types: types, + SIGNABLE_SCRIPTS: SIGNABLE_SCRIPTS, + P2SH_SCRIPTS: P2SH_SCRIPTS } diff --git a/test/script.js b/test/script.js index b9680bf33..8c7dd1e48 100644 --- a/test/script.js +++ b/test/script.js @@ -111,6 +111,30 @@ describe('script', function () { }) }) + describe('solveOutput', function () { + fixtures.valid.forEach(function (f) { + var script + if (f.scriptPubKeyHex) { + script = new Buffer(f.scriptPubKeyHex, 'hex') + } else if (f.scriptPubKey) { + script = bscript.fromASM(f.scriptPubKey) + } else { + return + } + + it('solves ' + bscript.toASM(script) + ' as ' + f.type, function () { + var solution = bscript.solveOutput(script) + assert.equal(solution.type, f.type) + if ([bscript.types.P2SH].concat(bscript.P2SH_SCRIPTS).indexOf(f.type) === -1) { + assert.equal(solution.solvedBy, null) + } + if (solution.canSign) { + assert.notEqual(solution.requiredSigs, null) + } + }) + }) + }) + describe('SCRIPT_VERIFY_MINIMALDATA policy', function () { fixtures.valid.forEach(function (f) { it('compliant for ' + f.type + ' scriptSig ' + f.asm, function () { From 685f6522221d4be9df93d51aa9dc7e5e6d8aaa5d Mon Sep 17 00:00:00 2001 From: Thomas Kerin Date: Thu, 10 Nov 2016 10:12:18 +0100 Subject: [PATCH 2/3] remove SIGNABLE_TYPES and P2SH_scripts --- src/templates/index.js | 8 +------- test/script.js | 2 +- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/src/templates/index.js b/src/templates/index.js index 666075e1d..1994032b5 100644 --- a/src/templates/index.js +++ b/src/templates/index.js @@ -17,9 +17,6 @@ var types = { P2WSH: 'witnessscripthash' } -var SIGNABLE_SCRIPTS = [types.MULTISIG, types.P2PKH, types.P2PK] -var P2SH_SCRIPTS = SIGNABLE_SCRIPTS.concat([types.P2WPKH, types.P2WSH]) - function classifyOutput (script) { if (witnessPubKeyHash.output.check(script)) return types.P2WPKH if (witnessScriptHash.output.check(script)) return types.P2WSH @@ -102,7 +99,6 @@ function solveOutput (scriptCode) { type: outputType, script: scriptCode, solvedBy: solvedBy, - canSign: SIGNABLE_SCRIPTS.indexOf(outputType) !== -1, requiredSigs: requiredSigs } } @@ -119,7 +115,5 @@ module.exports = { scriptHash: scriptHash, witnessPubKeyHash: witnessPubKeyHash, witnessScriptHash: witnessScriptHash, - types: types, - SIGNABLE_SCRIPTS: SIGNABLE_SCRIPTS, - P2SH_SCRIPTS: P2SH_SCRIPTS + types: types } diff --git a/test/script.js b/test/script.js index 8c7dd1e48..c58e942df 100644 --- a/test/script.js +++ b/test/script.js @@ -125,7 +125,7 @@ describe('script', function () { it('solves ' + bscript.toASM(script) + ' as ' + f.type, function () { var solution = bscript.solveOutput(script) assert.equal(solution.type, f.type) - if ([bscript.types.P2SH].concat(bscript.P2SH_SCRIPTS).indexOf(f.type) === -1) { + if ([bscript.types.P2SH, bscript.types.P2WSH, bscript.types.P2WPKH, bscript.types.P2PKH, bscript.types.P2PK, bscript.types.MULTISIG].indexOf(f.type) === -1) { assert.equal(solution.solvedBy, null) } if (solution.canSign) { From 9c32d2e19e6a4b8b42b8ffa59e719627c27e8763 Mon Sep 17 00:00:00 2001 From: Thomas Kerin Date: Mon, 14 Nov 2016 11:59:35 +0100 Subject: [PATCH 3/3] Add tests for transaction sighashes, usage of solveOutput --- test/transaction.js | 72 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/test/transaction.js b/test/transaction.js index cbc1cc948..46a7c934e 100644 --- a/test/transaction.js +++ b/test/transaction.js @@ -2,6 +2,8 @@ var assert = require('assert') var bscript = require('../src/script') +var bcrypto = require('../src/crypto') + var fixtures = require('./fixtures/transaction') var Transaction = require('../src/transaction') @@ -258,6 +260,76 @@ describe('Transaction', function () { }) }) + describe('signature hashing', function () { + function unsignedTransactionFromRaw (raw) { + var unsigned = new Transaction() + unsigned.version = raw.version + unsigned.ins = raw.ins.map(function (input) { + return { + hash: new Buffer(input.hash, 'hex'), + index: input.index, + script: new Buffer(0), // Empty for now + witness: [], // Empty for now + sequence: 4294967295 + } + }) + unsigned.outs = raw.outs.map(function (output) { + return { + value: output.value, + script: new Buffer(output.scriptHex, 'hex') + } + }) + unsigned.locktime = 0 + return unsigned + } + + function checkInputsMatchSigHash (f) { + if (!f.raw.ins.every(function (input) { return input.sigHash !== undefined })) { + return + } + + var unsigned = unsignedTransactionFromRaw(f.raw) + f.raw.ins.forEach(function (input, idx) { + it('determines the correct sighash for a ' + f.description + ' input', function () { + var prevOutScript = new Buffer(input.scriptPubKey, 'hex') + var valueOut = input.value ? input.value : 0 + var redeemScript = input.redeemScript ? new Buffer(input.redeemScript, 'hex') : undefined + var witnessScript = input.witnessScript ? new Buffer(input.witnessScript, 'hex') : undefined + var expectedSigHash = new Buffer(input.sigHash, 'hex') + var tx = unsigned + + var sigVersion = 0 + var solution = bscript.solveOutput(prevOutScript) + if (solution.type === bscript.types.P2SH) { + // This line is useless given the example, but illustrates usage + if (solution.solvedBy.equals(bcrypto.hash160(redeemScript))) { + solution = bscript.solveOutput(redeemScript) + } + } + + // We again use solvedBy (result of the script.*.output.decode() function) + // But notice, here we don't know if it's witness due to (i) scriptPubKey or (ii) redeemScript + // so it's nice solveOutput also returns solvedBy + if (solution.type === bscript.types.P2WPKH) { + sigVersion = 1 + solution = bscript.solveOutput(bscript.pubKeyHash.output.encode(solution.solvedBy)) + } else if (solution.type === bscript.types.P2WSH) { + sigVersion = 1 + solution = bscript.solveOutput(witnessScript) + } + assert([ bscript.types.P2PKH, bscript.types.P2PK, bscript.types.MULTISIG ].indexOf(solution.type) !== -1, 'should have found a signable type') + + var hash = sigVersion === 1 + ? tx.hashForWitnessV0(idx, solution.script, valueOut, Transaction.SIGHASH_ALL) + : tx.hashForSignature(idx, solution.script, Transaction.SIGHASH_ALL) + + assert.equal(hash.toString('hex'), expectedSigHash.toString('hex')) + }) + }) + } + + fixtures.valid.forEach(checkInputsMatchSigHash) + }) describe('setWitness', function () { it('only accepts a a witness stack (Array of Buffers)', function () { assert.throws(function () {