Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions src/templates/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,60 @@ 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,
requiredSigs: requiredSigs
}
}

module.exports = {
classifyInput: classifyInput,
classifyOutput: classifyOutput,
classifyWitness: classifyWitness,
solveOutput: solveOutput,
multisig: multisig,
nullData: nullData,
pubKey: pubKey,
Expand Down
24 changes: 24 additions & 0 deletions test/script.js
Original file line number Diff line number Diff line change
Expand Up @@ -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, 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) {
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 () {
Expand Down
72 changes: 72 additions & 0 deletions test/transaction.js
Original file line number Diff line number Diff line change
Expand Up @@ -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')

Expand Down Expand Up @@ -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 () {
Expand Down