Repository: bitcoinjs/bitcoinjs-lib
Pull request #682 (2016-11-02 02:48:33)

Scripts: transition to encode/decode/check style
Other considerations:

* `bitcoin.script.*.output(...)` could be (more visually/backwards compatibly similar) short-hand for `bitcoin.script.*.output.encode(...)`

* `bitcoin.script.*.input/output` could be `bitcoin.templates.*`,  which would make the importing cleaner,  but obviously means we are separating it from `script`.
* Alternatively,  `bitcoin.script.templates.*.input/output` would be nice... but oh my god its so much text.
* Alternate alternatively,  `require('bitcoinjs-script')`,  with a `.templates` export


dcousens: `bscript.witness*.input`'s since they are witness, not inputs.
Maybe `bscript.witness*.witness.encode` would be the best way forward there.

Also,  `classifyWitness` was introduced,  but remains untested for now.
Tests fixtures wanted! :+1: 
dcousens: ping @afk11 this is what I was thinking in terms of #681 
dcousens: ping @jprichardson - I think this is worth it,  but I don't like the verbosity.  Penny for your thoughts?
jprichardson: > It does smell strongly like a great time for breaking it out to another module.

😊 
dcousens: @jprichardson after this PR though?
Shouldn't affect the API for now IMHO.

Any other comments?
jprichardson: I actually really love the concept of script templates. I hadn't thought of that before. I also really like the consistent interface provided. Not a fan of the API `bscript.script.XXXX.YYYY`... but I like it better than anything else AND it's consistent and predictable - that's the most important.

So a big LGTM.
afk11: echo @jprichardson's thoughts - this will do nicely!

@dcousens: For witness inputs, better to follow with another PR, since they're not scripts. The are easily produced if you have a scriptSig: decompile - rejecting on any non-pushdata opcodes - and handle OP_1NEGATE, OP_1-16 using scriptNumber.
dcousens: @afk11 agreed.

Patch:
diff --git a/src/address.js b/src/address.js
index 656ec371..cb7248f0 100644
--- a/src/address.js
+++ b/src/address.js
@@ -28,8 +28,8 @@ function toBase58Check (hash, version) {
 function fromOutputScript (scriptPubKey, network) {
   network = network || networks.bitcoin
 
-  if (bscript.isPubKeyHashOutput(scriptPubKey)) return toBase58Check(bscript.compile(scriptPubKey).slice(3, 23), network.pubKeyHash)
-  if (bscript.isScriptHashOutput(scriptPubKey)) return toBase58Check(bscript.compile(scriptPubKey).slice(2, 22), network.scriptHash)
+  if (bscript.pubKeyHash.output.check(scriptPubKey)) return toBase58Check(bscript.compile(scriptPubKey).slice(3, 23), network.pubKeyHash)
+  if (bscript.scriptHash.output.check(scriptPubKey)) return toBase58Check(bscript.compile(scriptPubKey).slice(2, 22), network.scriptHash)
 
   throw new Error(bscript.toASM(scriptPubKey) + ' has no matching Address')
 }
@@ -38,8 +38,8 @@ function toOutputScript (address, network) {
   network = network || networks.bitcoin
 
   var decode = fromBase58Check(address)
-  if (decode.version === network.pubKeyHash) return bscript.pubKeyHashOutput(decode.hash)
-  if (decode.version === network.scriptHash) return bscript.scriptHashOutput(decode.hash)
+  if (decode.version === network.pubKeyHash) return bscript.pubKeyHash.output.encode(decode.hash)
+  if (decode.version === network.scriptHash) return bscript.scriptHash.output.encode(decode.hash)
 
   throw new Error(address + ' has no matching Script')
 }
diff --git a/src/script.js b/src/script.js
index 052175ee..7d1c7760 100644
--- a/src/script.js
+++ b/src/script.js
@@ -12,16 +12,6 @@ var REVERSE_OPS = (function () {
   return result
 })()
 
-var LIST_DECODE_NAMES = [
-  decodePubKeyOutput,
-  decodePubKeyHashOutput,
-  decodeMultisigOutput,
-  decodeNullDataOutput,
-  decodeScriptHashOutput,
-  decodeWitnessPubKeyHashOutput,
-  decodeWitnessScriptHashOutput
-]
-
 var OP_INT_BASE = OPS.OP_RESERVED // OP_1 - 1
 
 function compile (chunks) {
@@ -169,410 +159,19 @@ function isCanonicalSignature (buffer) {
   return bip66.check(buffer.slice(0, -1))
 }
 
-function isPubKeyHashInput (script) {
-  var chunks = decompile(script)
-
-  return chunks.length === 2 &&
-    isCanonicalSignature(chunks[0]) &&
-    isCanonicalPubKey(chunks[1])
-}
-
-function decodePubKeyHashOutput (buffer, chunks) {
-  if (buffer.length !== 25) {
-    throw new Error('pub-key-hash output script is 25 bytes')
-  }
-  if (buffer[0] !== OPS.OP_DUP) {
-    throw new Error('PubKeyHash script missing OP_DUP')
-  }
-  if (buffer[1] !== OPS.OP_HASH160) {
-    throw new Error('PubKeyHash script missing OP_HASH160')
-  }
-  if (buffer[2] !== 0x14) {
-    throw new Error('Incorrect opcode for pubkeyhash')
-  }
-  if (buffer[23] !== OPS.OP_EQUALVERIFY) {
-    throw new Error('PubKeyHash script missing OP_EQUALVERIFY')
-  }
-  if (buffer[24] !== OPS.OP_CHECKSIG) {
-    throw new Error('PubKeyHash script missing OP_CHECKSIG')
-  }
-
-  return {
-    type: 'pubkeyhash',
-    pubKeyHash: chunks[2]
-  }
-}
-
-function isPubKeyHashOutput (script) {
-  return determinesTypeOrNonstandard([decodePubKeyHashOutput], script) === 'pubkeyhash'
-}
-
-function isPubKeyInput (script) {
-  var chunks = decompile(script)
-
-  return chunks.length === 1 &&
-    isCanonicalSignature(chunks[0])
-}
-
-function decodePubKeyOutput (script, chunks) {
-  if (script[0] !== 0x21 && script[0] !== 0x41) {
-    throw new Error('Bad (or non-minimal) public key length for pub')
-  }
-  if (!isCanonicalPubKey(chunks[0])) {
-    throw new Error('pub-key output does not have a canonical public key')
-  }
-  if (chunks[1] !== OPS.OP_CHECKSIG) {
-    throw new Error('pub-key output missing OP_CHECKSIG operator')
-  }
-  if (chunks.length !== 2) {
-    throw new Error('pub-key output has two elements')
-  }
-  return {
-    type: 'pubkey',
-    publicKey: chunks[0]
-  }
-}
-
-function isPubKeyOutput (script) {
-  return determinesTypeOrNonstandard([decodePubKeyOutput], script) === 'pubkey'
-}
-
-function isScriptHashInput (script, allowIncomplete) {
-  var chunks = decompile(script)
-  if (chunks.length < 2) return false
-
-  var lastChunk = chunks[chunks.length - 1]
-  if (!Buffer.isBuffer(lastChunk)) return false
-
-  var scriptSigChunks = chunks.slice(0, -1)
-  var redeemScriptChunks = decompile(lastChunk)
-
-  // is redeemScript a valid script?
-  if (redeemScriptChunks.length === 0) return false
-
-  return classifyInput(scriptSigChunks, allowIncomplete) === classifyOutput(lastChunk)
-}
-
-function decodeScriptHashOutput (chunks) {
-  if (chunks[0] !== OPS.OP_HASH160) {
-    throw new Error()
-  }
-  if (chunks[1] !== 0x14) {
-    throw new Error()
-  }
-  if (chunks[22] !== OPS.OP_EQUAL) {
-    throw new Error()
-  }
-  return {
-    type: 'scripthash',
-    scriptHash: chunks[1]
-  }
-}
-
-function isScriptHashOutput (script) {
-  return determinesTypeOrNonstandard([decodeScriptHashOutput], script) === 'scripthash'
-}
-
-function decodeWitnessPubKeyHashOutput (script, chunks) {
-  if (script.length !== 22) {
-    throw new Error('P2WPKH script should be 22 bytes')
-  }
-  if (script[0] !== OPS.OP_0) {
-    throw new Error('Missing v0 prefix for witness keyhash')
-  }
-  if (script[1] !== 0x14) {
-    throw new Error('Witness keyhash length marker is wrong')
-  }
-
-  return {
-    type: 'witnesspubkeyhash',
-    witnessKeyHash: chunks[2]
-  }
-}
-
-function isWitnessPubKeyHashOutput (script) {
-  return determinesTypeOrNonstandard([decodeWitnessPubKeyHashOutput], script) === 'witnesspubkeyhash'
-}
-
-function decodeWitnessScriptHashOutput (script, chunks) {
-  if (script.length !== 34) {
-    throw new Error('P2WSH script should be 34 bytes')
-  }
-  if (script[0] !== OPS.OP_0) {
-    throw new Error('Missing v0 prefix for witness script hash')
-  }
-  if (script[1] !== 0x20) {
-    throw new Error('Witness program length marker is wrong')
-  }
-
-  return {
-    type: 'witnessscripthash',
-    witnessScriptHash: chunks[2]
-  }
-}
-
-function isWitnessScriptHashOutput (script) {
-  return determinesTypeOrNonstandard([decodeWitnessScriptHashOutput], script) === 'witnessscripthash'
-}
-
-// allowIncomplete is to account for combining signatures
-// See https://github.com/bitcoin/bitcoin/blob/f425050546644a36b0b8e0eb2f6934a3e0f6f80f/src/script/sign.cpp#L195-L197
-function isMultisigInput (script, allowIncomplete) {
-  var chunks = decompile(script)
-  if (chunks.length < 2) return false
-  if (chunks[0] !== OPS.OP_0) return false
-
-  if (allowIncomplete) {
-    return chunks.slice(1).every(function (chunk) {
-      return chunk === OPS.OP_0 || isCanonicalSignature(chunk)
-    })
-  }
-
-  return chunks.slice(1).every(isCanonicalSignature)
-}
-
-function decodeMultisigOutput (scriptPubKey, chunks) {
-  if (chunks.length < 4) {
-    throw new Error('Multisig script should contain at least 4 elements')
-  }
-  if (chunks[chunks.length - 1] !== OPS.OP_CHECKMULTISIG) {
-    throw new Error('Final opcode should be OP_CHECKMULTISIG')
-  }
-  if (!types.Number(chunks[0])) {
-    throw new Error('First element must be a number-push opcode')
-  }
-  if (!types.Number(chunks[chunks.length - 2])) {
-    throw new Error('Second-last element must be a number-push opcode')
-  }
-  var m = chunks[0] - OP_INT_BASE
-  var n = chunks[chunks.length - 2] - OP_INT_BASE
-
-  if (m <= 0) {
-    throw new Error('Number of signatures required must be greater than zero')
-  }
-  if (n > 16) {
-    throw new Error('Number of public keys must be less than 17')
-  }
-  if (m > n) {
-    throw new Error('Number of signatures cannot exceed the number of public keys')
-  }
-  if (n !== chunks.length - 3) {
-    throw new Error('n does not match the number of public keys')
-  }
-
-  var keys = chunks.slice(1, -2)
-  if (!keys.every(isCanonicalPubKey)) {
-    throw new Error('Non-canonical pubic key found')
-  }
-
-  return {
-    type: 'multisig',
-    nRequiredSigs: m,
-    nPublicKeys: n,
-    publicKeyBuffers: keys
-  }
-}
-
-function isMultisigOutput (script) {
-  return determinesTypeOrNonstandard([decodeMultisigOutput], script) === 'multisig'
-}
-
-function isNullDataOutput (script) {
-  return determinesTypeOrNonstandard([decodeNullDataOutput], script) === 'nulldata'
-}
-
-function decodeNullDataOutput (script, chunks) {
-  if (script[0] !== OPS.OP_RETURN) {
-    throw new Error('Missing OP_RETURN at start of script')
-  }
-
-  return {
-    type: 'nulldata'
-  }
-}
-
-function determinesTypeOrNonstandard (functions, script) {
-  if (!types.Array(functions)) {
-    throw new Error('Must provide an array of functions to determinesTypeOrNonstandard')
-  }
-  if (!types.Buffer(script)) {
-    throw new Error('Must provide a script to determinesTypeOrNonstandard')
-  }
-  var decoded
-  var decompiled = decompile(script)
-  var type = 'nonstandard'
-  for (var i = 0; i < functions.length && type === 'nonstandard'; i++) {
-    try {
-      decoded = functions[i](script, decompiled)
-      type = decoded.type
-    } catch (e) {
-
-    }
-  }
-
-  return type
-}
-
-function classifyOutput (script) {
-  return determinesTypeOrNonstandard(LIST_DECODE_NAMES, script)
-}
-
-function classifyInput (script, allowIncomplete) {
-  var chunks = decompile(script)
-
-  if (isPubKeyHashInput(chunks)) {
-    return 'pubkeyhash'
-  } else if (isMultisigInput(chunks, allowIncomplete)) {
-    return 'multisig'
-  } else if (isScriptHashInput(chunks, allowIncomplete)) {
-    return 'scripthash'
-  } else if (isPubKeyInput(chunks)) {
-    return 'pubkey'
-  }
-
-  return 'nonstandard'
-}
-
-// Standard Script Templates
-// {pubKey} OP_CHECKSIG
-function pubKeyOutput (pubKey) {
-  return compile([pubKey, OPS.OP_CHECKSIG])
-}
-
-// OP_DUP OP_HASH160 {pubKeyHash} OP_EQUALVERIFY OP_CHECKSIG
-function pubKeyHashOutput (pubKeyHash) {
-  typeforce(types.Hash160bit, pubKeyHash)
-
-  return compile([OPS.OP_DUP, OPS.OP_HASH160, pubKeyHash, OPS.OP_EQUALVERIFY, OPS.OP_CHECKSIG])
-}
-
-// OP_HASH160 {scriptHash} OP_EQUAL
-function scriptHashOutput (scriptHash) {
-  typeforce(types.Hash160bit, scriptHash)
-
-  return compile([OPS.OP_HASH160, scriptHash, OPS.OP_EQUAL])
-}
-
-// m [pubKeys ...] n OP_CHECKMULTISIG
-function multisigOutput (m, pubKeys) {
-  typeforce(types.tuple(types.Number, [types.Buffer]), arguments)
-
-  var n = pubKeys.length
-  if (n < m) throw new Error('Not enough pubKeys provided')
-
-  return compile([].concat(
-    OP_INT_BASE + m,
-    pubKeys,
-    OP_INT_BASE + n,
-    OPS.OP_CHECKMULTISIG
-  ))
-}
-
-// OP_0 {pubKeyHash}
-function witnessPubKeyHashOutput (pubKeyHash) {
-  typeforce(types.Hash160bit, pubKeyHash)
-
-  return compile([OPS.OP_0, pubKeyHash])
-}
-
-// OP_0 {scriptHash}
-function witnessScriptHashOutput (scriptHash) {
-  typeforce(types.Hash256bit, scriptHash)
-
-  return compile([OPS.OP_0, scriptHash])
-}
-
-// {signature}
-function pubKeyInput (signature) {
-  typeforce(types.Buffer, signature)
-
-  return compile([signature])
-}
-
-// {signature} {pubKey}
-function pubKeyHashInput (signature, pubKey) {
-  typeforce(types.tuple(types.Buffer, types.Buffer), arguments)
-
-  return compile([signature, pubKey])
-}
-
-// <scriptSig> {serialized scriptPubKey script}
-function scriptHashInput (scriptSig, scriptPubKey) {
-  var scriptSigChunks = decompile(scriptSig)
-  var serializedScriptPubKey = compile(scriptPubKey)
-
-  return compile([].concat(
-    scriptSigChunks,
-    serializedScriptPubKey
-  ))
-}
-
-// <scriptSig> {serialized scriptPubKey script}
-function witnessScriptHashInput (scriptSig, scriptPubKey) {
-  return scriptHashInput(scriptSig, scriptPubKey)
-}
-
-// OP_0 [signatures ...]
-function multisigInput (signatures, scriptPubKey) {
-  if (scriptPubKey) {
-    var scriptData = decodeMultisigOutput(scriptPubKey, decompile(scriptPubKey))
-    if (signatures.length < scriptData.nRequiredSigs) throw new Error('Not enough signatures provided')
-    if (signatures.length > scriptData.nPublicKeys) throw new Error('Too many signatures provided')
-  }
-
-  return compile([].concat(OPS.OP_0, signatures))
-}
-
-function nullDataOutput (data) {
-  return compile([OPS.OP_RETURN, data])
-}
-
 module.exports = {
   compile: compile,
   decompile: decompile,
   fromASM: fromASM,
   toASM: toASM,
-
   number: require('./script_number'),
 
   isCanonicalPubKey: isCanonicalPubKey,
   isCanonicalSignature: isCanonicalSignature,
-  isDefinedHashType: isDefinedHashType,
-
-  decodePubKeyOutput: decodePubKeyOutput,
-  decodePubKeyHashOutput: decodePubKeyHashOutput,
-  decodeMultisigOutput: decodeMultisigOutput,
-  decodeScriptHashOutput: decodeScriptHashOutput,
-  decodeNullDataOutput: decodeNullDataOutput,
-  decodeWitnessPubKeyHashOutput: decodeWitnessPubKeyHashOutput,
-  decodeWitnessScriptHashOutput: decodeWitnessScriptHashOutput,
-
-  isPubKeyOutput: isPubKeyOutput,
-  isPubKeyHashOutput: isPubKeyHashOutput,
-  isMultisigOutput: isMultisigOutput,
-  isScriptHashOutput: isScriptHashOutput,
-  isNullDataOutput: isNullDataOutput,
-  isWitnessPubKeyHashOutput: isWitnessPubKeyHashOutput,
-  isWitnessScriptHashOutput: isWitnessScriptHashOutput,
-  classifyOutput: classifyOutput,
-
-  isPubKeyInput: isPubKeyInput,
-  isPubKeyHashInput: isPubKeyHashInput,
-  isMultisigInput: isMultisigInput,
-  isScriptHashInput: isScriptHashInput,
-  classifyInput: classifyInput,
-
-  pubKeyOutput: pubKeyOutput,
-  pubKeyHashOutput: pubKeyHashOutput,
-  multisigOutput: multisigOutput,
-  scriptHashOutput: scriptHashOutput,
-  nullDataOutput: nullDataOutput,
-  witnessPubKeyHashOutput: witnessPubKeyHashOutput,
-  witnessScriptHashOutput: witnessScriptHashOutput,
-
-  pubKeyInput: pubKeyInput,
-  pubKeyHashInput: pubKeyHashInput,
-  multisigInput: multisigInput,
-  scriptHashInput: scriptHashInput,
-  witnessScriptHashInput: witnessScriptHashInput
+  isDefinedHashType: isDefinedHashType
+}
+
+var templates = require('./templates')
+for (var key in templates) {
+  module.exports[key] = templates[key]
 }
diff --git a/src/templates/index.js b/src/templates/index.js
new file mode 100644
index 00000000..aa45a3c4
--- /dev/null
+++ b/src/templates/index.js
@@ -0,0 +1,57 @@
+var decompile = require('../script').decompile
+var multisig = require('./multisig')
+var nullData = require('./nulldata')
+var pubKey = require('./pubkey')
+var pubKeyHash = require('./pubkeyhash')
+var scriptHash = require('./scripthash')
+var witnessPubKeyHash = require('./witnesspubkeyhash')
+var witnessScriptHash = require('./witnessscripthash')
+
+function classifyOutput (script) {
+  if (witnessPubKeyHash.output.check(script)) return 'witnesspubkeyhash'
+  if (witnessScriptHash.output.check(script)) return 'witnessscripthash'
+  if (pubKeyHash.output.check(script)) return 'pubkeyhash'
+  if (scriptHash.output.check(script)) return 'scripthash'
+
+  // XXX: optimization, below functions .decompile before use
+  var chunks = decompile(script)
+  if (multisig.output.check(chunks)) return 'multisig'
+  if (pubKey.output.check(chunks)) return 'pubkey'
+  if (nullData.output.check(chunks)) return 'nulldata'
+
+  return 'nonstandard'
+}
+
+function classifyInput (script, allowIncomplete) {
+  // XXX: optimization, below functions .decompile before use
+  var chunks = decompile(script)
+
+  if (pubKeyHash.input.check(chunks)) return 'pubkeyhash'
+  if (scriptHash.input.check(chunks, allowIncomplete)) return 'scripthash'
+  if (multisig.input.check(chunks, allowIncomplete)) return 'multisig'
+  if (pubKey.input.check(chunks)) return 'pubkey'
+
+  return 'nonstandard'
+}
+
+function classifyWitness (script, allowIncomplete) {
+  // XXX: optimization, below functions .decompile before use
+  var chunks = decompile(script)
+
+  if (pubKeyHash.input.check(chunks)) return 'witnesspubkeyhash'
+  if (scriptHash.input.check(chunks)) return 'witnessscripthash'
+  return 'nonstandard'
+}
+
+module.exports = {
+  classifyInput: classifyInput,
+  classifyOutput: classifyOutput,
+  classifyWitness: classifyWitness,
+  multisig: multisig,
+  nullData: nullData,
+  pubKey: pubKey,
+  pubKeyHash: pubKeyHash,
+  scriptHash: scriptHash,
+  witnessPubKeyHash: witnessPubKeyHash,
+  witnessScriptHash: witnessScriptHash
+}
diff --git a/src/templates/multisig/index.js b/src/templates/multisig/index.js
new file mode 100644
index 00000000..46863d67
--- /dev/null
+++ b/src/templates/multisig/index.js
@@ -0,0 +1,4 @@
+module.exports = {
+  input: require('./input'),
+  output: require('./output')
+}
diff --git a/src/templates/multisig/input.js b/src/templates/multisig/input.js
new file mode 100644
index 00000000..6fadf8cd
--- /dev/null
+++ b/src/templates/multisig/input.js
@@ -0,0 +1,53 @@
+// OP_0 [signatures ...]
+
+var bscript = require('../../script')
+var typeforce = require('typeforce')
+var OPS = require('../../opcodes.json')
+
+function partialSignature (value) {
+  return value === OPS.OP_0 || bscript.isCanonicalSignature(value)
+}
+
+function check (script, allowIncomplete) {
+  var chunks = bscript.decompile(script)
+  if (chunks.length < 2) return false
+  if (chunks[0] !== OPS.OP_0) return false
+
+  if (allowIncomplete) {
+    return chunks.slice(1).every(partialSignature)
+  }
+
+  return chunks.slice(1).every(bscript.isCanonicalSignature)
+}
+check.toJSON = function () { return 'multi-sig input' }
+
+function encode (signatures, scriptPubKey) {
+  typeforce([partialSignature], signatures)
+
+  if (scriptPubKey) {
+    var scriptData = bscript.multisig.output.decode(scriptPubKey)
+
+    if (signatures.length < scriptData.m) {
+      throw new TypeError('Not enough signatures provided')
+    }
+
+    if (signatures.length > scriptData.pubKeys.length) {
+      throw new TypeError('Too many signatures provided')
+    }
+  }
+
+  return bscript.compile([].concat(OPS.OP_0, signatures))
+}
+
+function decode (buffer, allowIncomplete) {
+  var chunks = bscript.decompile(buffer)
+  typeforce(check, chunks, allowIncomplete)
+
+  return chunks.slice(1)
+}
+
+module.exports = {
+  check: check,
+  decode: decode,
+  encode: encode
+}
diff --git a/src/templates/multisig/output.js b/src/templates/multisig/output.js
new file mode 100644
index 00000000..0f4fe7d1
--- /dev/null
+++ b/src/templates/multisig/output.js
@@ -0,0 +1,64 @@
+// m [pubKeys ...] n OP_CHECKMULTISIG
+
+var bscript = require('../../script')
+var types = require('../../types')
+var typeforce = require('typeforce')
+var OPS = require('../../opcodes.json')
+var OP_INT_BASE = OPS.OP_RESERVED // OP_1 - 1
+
+function check (script, allowIncomplete) {
+  var chunks = bscript.decompile(script)
+
+  if (chunks.length < 4) return false
+  if (chunks[chunks.length - 1] !== OPS.OP_CHECKMULTISIG) return false
+  if (!types.Number(chunks[0])) return false
+  if (!types.Number(chunks[chunks.length - 2])) return false
+  var m = chunks[0] - OP_INT_BASE
+  var n = chunks[chunks.length - 2] - OP_INT_BASE
+
+  if (m <= 0) return false
+  if (n > 16) return false
+  if (m > n) return false
+  if (n !== chunks.length - 3) return false
+  if (allowIncomplete) return true
+
+  var keys = chunks.slice(1, -2)
+  return keys.every(bscript.isCanonicalPubKey)
+}
+check.toJSON = function () { return 'multi-sig output' }
+
+function encode (m, pubKeys) {
+  typeforce({
+    m: types.Number,
+    pubKeys: [bscript.isCanonicalPubKey]
+  }, {
+    m: m,
+    pubKeys: pubKeys
+  })
+
+  var n = pubKeys.length
+  if (n < m) throw new TypeError('Not enough pubKeys provided')
+
+  return bscript.compile([].concat(
+    OP_INT_BASE + m,
+    pubKeys,
+    OP_INT_BASE + n,
+    OPS.OP_CHECKMULTISIG
+  ))
+}
+
+function decode (buffer, allowIncomplete) {
+  var chunks = bscript.decompile(buffer)
+  typeforce(check, chunks, allowIncomplete)
+
+  return {
+    m: chunks[0] - OP_INT_BASE,
+    pubKeys: chunks.slice(1, -2)
+  }
+}
+
+module.exports = {
+  check: check,
+  decode: decode,
+  encode: encode
+}
diff --git a/src/templates/nulldata.js b/src/templates/nulldata.js
new file mode 100644
index 00000000..163249fd
--- /dev/null
+++ b/src/templates/nulldata.js
@@ -0,0 +1,35 @@
+// OP_RETURN {data}
+
+var bscript = require('../script')
+var types = require('../types')
+var typeforce = require('typeforce')
+var OPS = require('../opcodes.json')
+
+function check (script) {
+  var buffer = bscript.compile(script)
+
+  return buffer.length > 1 &&
+    buffer[0] === OPS.OP_RETURN
+}
+check.toJSON = function () { return 'null data output' }
+
+function encode (data) {
+  typeforce(types.Buffer, data)
+
+  return bscript.compile([OPS.OP_RETURN, data])
+}
+
+function decode (buffer) {
+  typeforce(check, buffer)
+
+  return buffer.slice(2)
+}
+
+module.exports = {
+  output: {
+    check: check,
+    decode: decode,
+    encode: encode
+  }
+}
+
diff --git a/src/templates/pubkey/index.js b/src/templates/pubkey/index.js
new file mode 100644
index 00000000..46863d67
--- /dev/null
+++ b/src/templates/pubkey/index.js
@@ -0,0 +1,4 @@
+module.exports = {
+  input: require('./input'),
+  output: require('./output')
+}
diff --git a/src/templates/pubkey/input.js b/src/templates/pubkey/input.js
new file mode 100644
index 00000000..7357adc5
--- /dev/null
+++ b/src/templates/pubkey/input.js
@@ -0,0 +1,32 @@
+// {signature}
+
+var bscript = require('../../script')
+var types = require('../../types')
+var typeforce = require('typeforce')
+
+function check (script) {
+  var chunks = bscript.decompile(script)
+
+  return chunks.length === 1 &&
+    bscript.isCanonicalSignature(chunks[0])
+}
+check.toJSON = function () { return 'pubKey input' }
+
+function encode (signature) {
+  typeforce(types.Buffer, signature)
+
+  return bscript.compile([signature])
+}
+
+function decode (buffer) {
+  var chunks = bscript.decompile(buffer)
+  typeforce(check, chunks)
+
+  return chunks[0]
+}
+
+module.exports = {
+  check: check,
+  decode: decode,
+  encode: encode
+}
diff --git a/src/templates/pubkey/output.js b/src/templates/pubkey/output.js
new file mode 100644
index 00000000..83b5c50a
--- /dev/null
+++ b/src/templates/pubkey/output.js
@@ -0,0 +1,33 @@
+// {pubKey} OP_CHECKSIG
+
+var bscript = require('../../script')
+var typeforce = require('typeforce')
+var OPS = require('../../opcodes.json')
+
+function check (script) {
+  var chunks = bscript.decompile(script)
+
+  return chunks.length === 2 &&
+    bscript.isCanonicalPubKey(chunks[0]) &&
+    chunks[1] === OPS.OP_CHECKSIG
+}
+check.toJSON = function () { return 'pubKey output' }
+
+function encode (pubKey) {
+  typeforce(bscript.isCanonicalPubKey, pubKey)
+
+  return bscript.compile([pubKey, OPS.OP_CHECKSIG])
+}
+
+function decode (buffer) {
+  var chunks = bscript.decompile(buffer)
+  typeforce(check, chunks)
+
+  return chunks[0]
+}
+
+module.exports = {
+  check: check,
+  decode: decode,
+  encode: encode
+}
diff --git a/src/templates/pubkeyhash/index.js b/src/templates/pubkeyhash/index.js
new file mode 100644
index 00000000..46863d67
--- /dev/null
+++ b/src/templates/pubkeyhash/index.js
@@ -0,0 +1,4 @@
+module.exports = {
+  input: require('./input'),
+  output: require('./output')
+}
diff --git a/src/templates/pubkeyhash/input.js b/src/templates/pubkeyhash/input.js
new file mode 100644
index 00000000..f91a322e
--- /dev/null
+++ b/src/templates/pubkeyhash/input.js
@@ -0,0 +1,40 @@
+// {pubKey} OP_CHECKSIG
+
+var bscript = require('../../script')
+var types = require('../../types')
+var typeforce = require('typeforce')
+
+function check (script) {
+  var chunks = bscript.decompile(script)
+
+  return chunks.length === 2 &&
+    bscript.isCanonicalSignature(chunks[0]) &&
+    bscript.isCanonicalPubKey(chunks[1])
+}
+check.toJSON = function () { return 'pubKeyHash input' }
+
+function encode (signature, pubKey) {
+  typeforce({
+    signature: types.Buffer, pubKey: types.Buffer
+  }, {
+    signature: signature, pubKey: pubKey
+  })
+
+  return bscript.compile([signature, pubKey])
+}
+
+function decode (buffer) {
+  var chunks = bscript.decompile(buffer)
+  typeforce(check, chunks)
+
+  return {
+    signature: chunks[0],
+    pubKey: chunks[1]
+  }
+}
+
+module.exports = {
+  check: check,
+  decode: decode,
+  encode: encode
+}
diff --git a/src/templates/pubkeyhash/output.js b/src/templates/pubkeyhash/output.js
new file mode 100644
index 00000000..4c11006a
--- /dev/null
+++ b/src/templates/pubkeyhash/output.js
@@ -0,0 +1,42 @@
+// OP_DUP OP_HASH160 {pubKeyHash} OP_EQUALVERIFY OP_CHECKSIG
+
+var bscript = require('../../script')
+var types = require('../../types')
+var typeforce = require('typeforce')
+var OPS = require('../../opcodes.json')
+
+function check (script) {
+  var buffer = bscript.compile(script)
+
+  return buffer.length === 25 &&
+    buffer[0] === OPS.OP_DUP &&
+    buffer[1] === OPS.OP_HASH160 &&
+    buffer[2] === 0x14 &&
+    buffer[23] === OPS.OP_EQUALVERIFY &&
+    buffer[24] === OPS.OP_CHECKSIG
+}
+check.toJSON = function () { return 'pubKeyHash output' }
+
+function encode (pubKeyHash) {
+  typeforce(types.Hash160bit, pubKeyHash)
+
+  return bscript.compile([
+    OPS.OP_DUP,
+    OPS.OP_HASH160,
+    pubKeyHash,
+    OPS.OP_EQUALVERIFY,
+    OPS.OP_CHECKSIG
+  ])
+}
+
+function decode (buffer) {
+  typeforce(check, buffer)
+
+  return buffer.slice(3, 23)
+}
+
+module.exports = {
+  check: check,
+  decode: decode,
+  encode: encode
+}
diff --git a/src/templates/scripthash/index.js b/src/templates/scripthash/index.js
new file mode 100644
index 00000000..46863d67
--- /dev/null
+++ b/src/templates/scripthash/index.js
@@ -0,0 +1,4 @@
+module.exports = {
+  input: require('./input'),
+  output: require('./output')
+}
diff --git a/src/templates/scripthash/input.js b/src/templates/scripthash/input.js
new file mode 100644
index 00000000..87f2d7f1
--- /dev/null
+++ b/src/templates/scripthash/input.js
@@ -0,0 +1,49 @@
+// <scriptSig> {serialized scriptPubKey script}
+
+var bscript = require('../../script')
+var typeforce = require('typeforce')
+
+function check (script, allowIncomplete) {
+  var chunks = bscript.decompile(script)
+  if (chunks.length < 2) return false
+
+  var lastChunk = chunks[chunks.length - 1]
+  if (!Buffer.isBuffer(lastChunk)) return false
+
+  var scriptSigChunks = chunks.slice(0, -1)
+  var redeemScriptChunks = bscript.decompile(lastChunk)
+
+  // is redeemScript a valid script?
+  if (redeemScriptChunks.length === 0) return false
+
+  var inputType = bscript.classifyInput(scriptSigChunks, allowIncomplete)
+  var outputType = bscript.classifyOutput(redeemScriptChunks)
+  return inputType === outputType
+}
+check.toJSON = function () { return 'scriptHash input' }
+
+function encode (redeemScriptSig, redeemScript) {
+  var scriptSigChunks = bscript.decompile(redeemScriptSig)
+  var serializedScriptPubKey = bscript.compile(redeemScript)
+
+  return bscript.compile([].concat(
+    scriptSigChunks,
+    serializedScriptPubKey
+  ))
+}
+
+function decode (buffer) {
+  var chunks = bscript.decompile(buffer)
+  typeforce(check, chunks)
+
+  return {
+    redeemScriptSig: bscript.compile(chunks.slice(0, -1)),
+    redeemScript: chunks[chunks.length - 1]
+  }
+}
+
+module.exports = {
+  check: check,
+  decode: decode,
+  encode: encode
+}
diff --git a/src/templates/scripthash/output.js b/src/templates/scripthash/output.js
new file mode 100644
index 00000000..fe3b5ce1
--- /dev/null
+++ b/src/templates/scripthash/output.js
@@ -0,0 +1,34 @@
+// OP_HASH160 {scriptHash} OP_EQUAL
+
+var bscript = require('../../script')
+var types = require('../../types')
+var typeforce = require('typeforce')
+var OPS = require('../../opcodes.json')
+
+function check (script) {
+  var buffer = bscript.compile(script)
+
+  return buffer.length === 23 &&
+    buffer[0] === OPS.OP_HASH160 &&
+    buffer[1] === 0x14 &&
+    buffer[22] === OPS.OP_EQUAL
+}
+check.toJSON = function () { return 'scriptHash output' }
+
+function encode (scriptHash) {
+  typeforce(types.Hash160bit, scriptHash)
+
+  return bscript.compile([OPS.OP_HASH160, scriptHash, OPS.OP_EQUAL])
+}
+
+function decode (buffer) {
+  typeforce(check, buffer)
+
+  return buffer.slice(2, 22)
+}
+
+module.exports = {
+  check: check,
+  decode: decode,
+  encode: encode
+}
diff --git a/src/templates/witnesspubkeyhash/index.js b/src/templates/witnesspubkeyhash/index.js
new file mode 100644
index 00000000..d4590381
--- /dev/null
+++ b/src/templates/witnesspubkeyhash/index.js
@@ -0,0 +1,3 @@
+module.exports = {
+  output: require('./output')
+}
diff --git a/src/templates/witnesspubkeyhash/output.js b/src/templates/witnesspubkeyhash/output.js
new file mode 100644
index 00000000..0eae63ea
--- /dev/null
+++ b/src/templates/witnesspubkeyhash/output.js
@@ -0,0 +1,33 @@
+// OP_0 {pubKeyHash}
+
+var bscript = require('../../script')
+var types = require('../../types')
+var typeforce = require('typeforce')
+var OPS = require('../../opcodes.json')
+
+function check (script) {
+  var buffer = bscript.compile(script)
+
+  return buffer.length === 22 &&
+    buffer[0] === OPS.OP_0 &&
+    buffer[1] === 0x14
+}
+check.toJSON = function () { return 'Witness pubKeyHash output' }
+
+function encode (pubKeyHash) {
+  typeforce(types.Hash160bit, pubKeyHash)
+
+  return bscript.compile([OPS.OP_0, pubKeyHash])
+}
+
+function decode (buffer) {
+  typeforce(check, buffer)
+
+  return buffer.slice(2)
+}
+
+module.exports = {
+  check: check,
+  decode: decode,
+  encode: encode
+}
diff --git a/src/templates/witnessscripthash/index.js b/src/templates/witnessscripthash/index.js
new file mode 100644
index 00000000..d4590381
--- /dev/null
+++ b/src/templates/witnessscripthash/index.js
@@ -0,0 +1,3 @@
+module.exports = {
+  output: require('./output')
+}
diff --git a/src/templates/witnessscripthash/output.js b/src/templates/witnessscripthash/output.js
new file mode 100644
index 00000000..00fb838f
--- /dev/null
+++ b/src/templates/witnessscripthash/output.js
@@ -0,0 +1,33 @@
+// OP_0 {scriptHash}
+
+var bscript = require('../../script')
+var types = require('../../types')
+var typeforce = require('typeforce')
+var OPS = require('../../opcodes.json')
+
+function check (script) {
+  var buffer = bscript.compile(script)
+
+  return buffer.length === 34 &&
+    buffer[0] === OPS.OP_0 &&
+    buffer[1] === 0x20
+}
+check.toJSON = function () { return 'Witness scriptHash output' }
+
+function encode (scriptHash) {
+  typeforce(types.Hash256bit, scriptHash)
+
+  return bscript.compile([OPS.OP_0, scriptHash])
+}
+
+function decode (buffer) {
+  typeforce(check, buffer)
+
+  return buffer.slice(2)
+}
+
+module.exports = {
+  check: check,
+  decode: decode,
+  encode: encode
+}
diff --git a/src/transaction_builder.js b/src/transaction_builder.js
index 402917f2..bf3ccd17 100644
--- a/src/transaction_builder.js
+++ b/src/transaction_builder.js
@@ -29,7 +29,7 @@ function expandInput (scriptSig, redeemScript) {
       var result = expandInput(redeemScriptSig, redeemScript)
       result.redeemScript = redeemScript
       result.redeemScriptType = result.prevOutType
-      result.prevOutScript = bscript.scriptHashOutput(bcrypto.hash160(redeemScript))
+      result.prevOutScript = bscript.scriptHash.output.encode(bcrypto.hash160(redeemScript))
       result.prevOutType = 'scripthash'
       return result
 
@@ -39,7 +39,7 @@ function expandInput (scriptSig, redeemScript) {
       signatures = scriptSigChunks.slice(0, 1)
 
       if (redeemScript) break
-      prevOutScript = bscript.pubKeyHashOutput(bcrypto.hash160(pubKeys[0]))
+      prevOutScript = bscript.pubKeyHash.output.encode(bcrypto.hash160(pubKeys[0]))
       break
 
     case 'pubkey':
@@ -161,7 +161,7 @@ function prepareInput (input, kpPubKey, redeemScript) {
     input.signatures = expanded.signatures
     input.redeemScript = redeemScript
     input.redeemScriptType = expanded.scriptType
-    input.prevOutScript = input.prevOutScript || bscript.scriptHashOutput(redeemScriptHash)
+    input.prevOutScript = input.prevOutScript || bscript.scriptHash.output.encode(redeemScriptHash)
     input.prevOutType = 'scripthash'
 
   // maybe we have some prevOut knowledge
@@ -178,7 +178,7 @@ function prepareInput (input, kpPubKey, redeemScript) {
 
   // no prior knowledge, assume pubKeyHash
   } else {
-    input.prevOutScript = bscript.pubKeyHashOutput(bcrypto.hash160(kpPubKey))
+    input.prevOutScript = bscript.pubKeyHash.output.encode(bcrypto.hash160(kpPubKey))
     input.prevOutType = 'pubkeyhash'
     input.pubKeys = [kpPubKey]
     input.signatures = [undefined]
@@ -195,9 +195,9 @@ function buildInput (input, allowIncomplete) {
     case 'pubkey':
       if (signatures.length < 1 || !signatures[0]) throw new Error('Not enough signatures provided')
       if (scriptType === 'pubkeyhash') {
-        scriptSig = bscript.pubKeyHashInput(signatures[0], input.pubKeys[0])
+        scriptSig = bscript.pubKeyHash.input.encode(signatures[0], input.pubKeys[0])
       } else {
-        scriptSig = bscript.pubKeyInput(signatures[0])
+        scriptSig = bscript.pubKey.input.encode(signatures[0])
       }
 
       break
@@ -213,7 +213,7 @@ function buildInput (input, allowIncomplete) {
         signatures = signatures.filter(function (x) { return x !== ops.OP_0 })
       }
 
-      scriptSig = bscript.multisigInput(signatures, allowIncomplete ? undefined : input.redeemScript)
+      scriptSig = bscript.multisig.input.encode(signatures, allowIncomplete ? undefined : input.redeemScript)
       break
 
     default: return
@@ -221,7 +221,7 @@ function buildInput (input, allowIncomplete) {
 
   // wrap as scriptHash if necessary
   if (input.prevOutType === 'scripthash') {
-    scriptSig = bscript.scriptHashInput(scriptSig, input.redeemScript)
+    scriptSig = bscript.scriptHash.input.encode(scriptSig, input.redeemScript)
   }
 
   return scriptSig


Test patch:
diff --git a/test/fixtures/script.json b/test/fixtures/script.json
index 86fc8f8a..7e41e967 100644
--- a/test/fixtures/script.json
+++ b/test/fixtures/script.json
@@ -188,249 +188,253 @@
         "hex": "4effffffff01"
       }
     ],
-    "isPubKeyHashInput": [
-      {
-        "description": "pubKeyHash input : extraneous data",
-        "scriptSig": "304402207515cf147d201f411092e6be5a64a6006f9308fad7b2a8fdaab22cd86ce764c202200974b8aca7bf51dbf54150d3884e1ae04f675637b926ec33bf75939446f6ca2801 02359c6e3f04cefbf089cf1d6670dc47c3fb4df68e2bad1fa5a369f9ce4b42bbd1 ffffffff"
-      }
-    ],
-    "isScriptHashInput": [
-      {
-        "description": "redeemScript not data",
-        "scriptSig": "OP_0 304402207515cf147d201f411092e6be5a64a6006f9308fad7b2a8fdaab22cd86ce764c202200974b8aca7bf51dbf54150d3884e1ae04f675637b926ec33bf75939446f6ca2801 3045022100ef253c1faa39e65115872519e5f0a33bbecf430c0f35cf562beabbad4da24d8d02201742be8ee49812a73adea3007c9641ce6725c32cd44ddb8e3a3af460015d140501 OP_RESERVED"
-      },
-      {
-        "description": "redeemScript is a signature, therefore not a valid script",
-        "scriptSig": "OP_0 3045022100e12b17b3a4c80c401a1687487bd2bafee9e5f1f8f1ffc6180ce186672ad7b43a02205e316d1e5e71822f5ef301b694e578fa9c94af4f5f098c952c833f4691307f4e01"
-      }
-    ],
-    "isPubKeyInput": [
-      {
-        "description": "non-canonical signature (too short)",
-        "scriptSig": "304402207515cf147d201f411092e6be5a64a6006f9308fad7b2a8fdaab22cd86ce764c202200974b8aca7bf51dbf54150d3884e1ae04f675637b926ec33bf7593"
-      },
-      {
-        "description": "non-canonical signature (too long)",
-        "scriptSig": "304402207515cf147d201f411092e6be5a64a6006f9308fad7b2a8fdaab22cd86ce764c202200974b8aca7bf51dbf54150d3884e1ae04f675637b926ec33bf75939446f6ca28ffffffff01"
-      },
-      {
-        "description": "non-canonical signature (invalid hashType)",
-        "scriptSig": "304402207515cf147d201f411092e6be5a64a6006f9308fad7b2a8fdaab22cd86ce764c202200974b8aca7bf51dbf54150d3884e1ae04f675637b926ec33bf75939446f6ca28ff"
-      }
-    ],
-    "isPubKeyOutput": [
-      {
-        "description": "non-canonical pubkey (too short)",
-        "scriptPubKey": "02359c6e3f04cefbf089cf1d6670dc47c3fb4df68e2bad1fa5a369f9ce OP_CHECKSIG"
-      },
-      {
-        "description": "non-canonical pubkey (too long)",
-        "scriptPubKey": "02359c6e3f04cefbf089cf1d6670dc47c3fb4df68e2bad1fa5a369f9ce4b42bbd1ffffff OP_CHECKSIG"
-      },
-      {
-        "description": "last operator is wrong for pubkey-output",
-        "scriptPubKeyHex": "21027a71801ab59336de37785c50005b6abd8ea859eecce1edbe8e81afa74ee5c752ae"
-      },
-      {
-        "description": "missing OP_CHECKSIG",
-        "scriptPubKeyHex": "21027a71801ab59336de37785c50005b6abd8ea859eecce1edbe8e81afa74ee5c752"
-      },
-      {
-        "description": "non-canonical pubkey (bad prefix)",
-        "scriptPubKey": "427a71801ab59336de37785c50005b6abd8ea859eecce1edbe8e81afa74ee5c752 OP_CHECKSIG"
-      },
-      {
-        "description": "has extra opcode at the end isPubKeyOutput",
-        "scriptPubKey": "027a71801ab59336de37785c50005b6abd8ea859eecce1edbe8e81afa74ee5c752 OP_CHECKSIG OP_0"
-      }
-    ],
-    "isPubKeyHashOutput": [
-      {
-        "description": "non-minimal encoded isPubKeyHashOutput (non BIP62 compliant)",
-        "scriptPubKeyHex": "76a94c14aa4d7985c57e011a8b3dd8e0e5a73aaef41629c588ac"
-      },
-      {
-        "description": "bad OP_DUP isPubKeyHashOutput",
-        "scriptPubKeyHex": "aca914aa4d7985c57e011a8b3dd8e0e5a73aaef41629c588ac"
-      },
-      {
-        "description": "bad OP_HASH160 isPubKeyHashOutput",
-        "scriptPubKeyHex": "76ac14aa4d7985c57e011a8b3dd8e0e5a73aaef41629c588ac"
-      },
-      {
-        "description": "bad OP_EQUALVERIFY isPubKeyHashOutput",
-        "scriptPubKeyHex": "76a914aa4d7985c57e011a8b3dd8e0e5a73aaef41629c5acac"
-      },
-      {
-        "description": "bad OP_CHECKSIG isPubKeyHashOutput",
-        "scriptPubKeyHex": "76a914aa4d7985c57e011a8b3dd8e0e5a73aaef41629c58888"
-      },
-      {
-        "description": "bad length isPubKeyHashOutput",
-        "scriptPubKeyHex": "76a920aa4d7985c57e011a8b3dd8e0e5a73aaef41629c588ac"
-      },
-      {
-        "description": "has something at the end isPubKeyHashOutput",
-        "scriptPubKeyHex": "76a920aa4d7985c57e011a8b3dd8e0e5a73aaef41629c588ac00"
-      }
-    ],
-    "isScriptHashOutput": [
-      {
-        "description": "non-minimal encoded isScriptHashOutput (non BIP62 compliant)",
-        "scriptPubKeyHex": "a94c14c286a1af0947f58d1ad787385b1c2c4a976f9e7187"
-      },
-      {
-        "description": "wrong OP_HASH160 opcode",
-        "scriptPubKeyHex": "ac4c14c286a1af0947f58d1ad787385b1c2c4a976f9e7187"
-      },
-      {
-        "description": "wrong length marker",
-        "scriptPubKeyHex": "a916c286a1af0947f58d1ad787385b1c2c4a976f9e7187"
-      },
-      {
-        "description": "wrong OP_EQUAL opcode",
-        "scriptPubKeyHex": "a914c286a1af0947f58d1ad787385b1c2c4a976f9e7188"
-      }
-    ],
-    "isWitnessPubKeyHashOutput": [
-      {
-        "description": "wrong version",
-        "scriptPubKeyHex": "51149090909090909090909090909090909090909090"
-      },
-      {
-        "description": "wrong length marker",
-        "scriptPubKeyHex": "00209090909090909090909090909090909090909090"
-      }
-    ],
-    "isWitnessScriptHashOutput": [
-      {
-        "description": "wrong version",
-        "scriptPubKeyHex": "51209090909090909090909090909090909090909090909090909090909090909090"
-      },
-      {
-        "description": "wrong length marker",
-        "scriptPubKeyHex": "00219090909090909090909090909090909090909090909090909090909090909090"
-      }
-    ],
-    "isMultisigOutput": [
-      {
-        "description": "OP_CHECKMULTISIG not found",
-        "scriptPubKey": "OP_0 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 02b80011a883a0fd621ad46dfc405df1e74bf075cbaf700fd4aebef6e96f848340 OP_2 OP_HASH160"
-      },
-      {
-        "description": "less than 4 chunks",
-        "scriptPubKey": "OP_0 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 OP_HASH160"
-      },
-      {
-        "description": "m === 0",
-        "scriptPubKey": "OP_0 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 02b80011a883a0fd621ad46dfc405df1e74bf075cbaf700fd4aebef6e96f848340 OP_2 OP_CHECKMULTISIG"
-      },
-      {
-        "description": "m < OP_1",
-        "scriptPubKey": "OP_1NEGATE 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 02b80011a883a0fd621ad46dfc405df1e74bf075cbaf700fd4aebef6e96f848340 OP_2 OP_CHECKMULTISIG"
-      },
-      {
-        "description": "m > OP_16",
-        "scriptPubKey": "OP_NOP 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 02b80011a883a0fd621ad46dfc405df1e74bf075cbaf700fd4aebef6e96f848340 OP_2 OP_CHECKMULTISIG"
-      },
-      {
-        "description": "n === 0",
-        "scriptPubKey": "OP_1 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 02b80011a883a0fd621ad46dfc405df1e74bf075cbaf700fd4aebef6e96f848340 OP_0 OP_CHECKMULTISIG"
-      },
-      {
-        "description": "n < OP_1",
-        "scriptPubKey": "OP_1 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 02b80011a883a0fd621ad46dfc405df1e74bf075cbaf700fd4aebef6e96f848340 OP_1NEGATE OP_CHECKMULTISIG"
-      },
-      {
-        "description": "n > OP_16",
-        "scriptPubKey": "OP_1 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 02b80011a883a0fd621ad46dfc405df1e74bf075cbaf700fd4aebef6e96f848340 OP_NOP OP_CHECKMULTISIG"
-      },
-      {
-        "description": "n < m",
-        "scriptPubKey": "OP_2 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 02b80011a883a0fd621ad46dfc405df1e74bf075cbaf700fd4aebef6e96f848340 OP_1 OP_CHECKMULTISIG"
-      },
-      {
-        "description": "n < len(pubKeys)",
-        "scriptPubKey": "OP_2 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 02b80011a883a0fd621ad46dfc405df1e74bf075cbaf700fd4aebef6e96f848340 024289801366bcee6172b771cf5a7f13aaecd237a0b9a1ff9d769cabc2e6b70a34 OP_2 OP_CHECKMULTISIG"
-      },
-      {
-        "description": "n > len(pubKeys)",
-        "scriptPubKey": "OP_1 024289801366bcee6172b771cf5a7f13aaecd237a0b9a1ff9d769cabc2e6b70a34 OP_2 OP_CHECKMULTISIG"
-      },
-      {
-        "description": "m is data",
-        "scriptPubKey": "ffff 024289801366bcee6172b771cf5a7f13aaecd237a0b9a1ff9d769cabc2e6b70a34 OP_1 OP_CHECKMULTISIG"
-      },
-      {
-        "description": "n is data",
-        "scriptPubKey": "OP_1 024289801366bcee6172b771cf5a7f13aaecd237a0b9a1ff9d769cabc2e6b70a34 ffff OP_CHECKMULTISIG"
-      },
-      {
-        "description": "non-canonical pubKey (bad length)",
-        "scriptPubKey": "OP_1 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ffff OP_1 OP_CHECKMULTISIG"
-      }
-    ],
-    "multisigInput": [
-      {
-        "description": "Not enough signatures provided",
-        "type": "multisig",
-        "scriptPubKey": "OP_2 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 02b80011a883a0fd621ad46dfc405df1e74bf075cbaf700fd4aebef6e96f848340 OP_2 OP_CHECKMULTISIG",
-        "signatures": []
-      },
-      {
-        "exception": "Not enough signatures provided",
-        "scriptPubKey": "OP_2 02359c6e3f04cefbf089cf1d6670dc47c3fb4df68e2bad1fa5a369f9ce4b42bbd1 0395a9d84d47d524548f79f435758c01faec5da2b7e551d3b8c995b7e06326ae4a OP_2 OP_CHECKMULTISIG",
-        "signatures": [
-          "304402207515cf147d201f411092e6be5a64a6006f9308fad7b2a8fdaab22cd86ce764c202200974b8aca7bf51dbf54150d3884e1ae04f675637b926ec33bf75939446f6ca2801"
-        ]
-      },
-      {
-        "exception": "Too many signatures provided",
-        "scriptPubKey": "OP_2 02359c6e3f04cefbf089cf1d6670dc47c3fb4df68e2bad1fa5a369f9ce4b42bbd1 0395a9d84d47d524548f79f435758c01faec5da2b7e551d3b8c995b7e06326ae4a OP_2 OP_CHECKMULTISIG",
-        "signatures": [
-          "304402207515cf147d201f411092e6be5a64a6006f9308fad7b2a8fdaab22cd86ce764c202200974b8aca7bf51dbf54150d3884e1ae04f675637b926ec33bf75939446f6ca2801",
-          "3045022100ef253c1faa39e65115872519e5f0a33bbecf430c0f35cf562beabbad4da24d8d02201742be8ee49812a73adea3007c9641ce6725c32cd44ddb8e3a3af460015d140501",
-          "3045022100ef253c1faa39e65115872519e5f0a33bbecf430c0f35cf562beabbad4da24d8d02201742be8ee49812a73adea3007c9641ce6725c32cd44ddb8e3a3af460015d140501"
-        ]
-      }
-    ],
-    "multisigOutput": [
-      {
-        "exception": "Not enough pubKeys provided",
-        "m": 4,
-        "pubKeys": [
-          "02ea1297665dd733d444f31ec2581020004892cdaaf3dd6c0107c615afb839785f",
-          "02fab2dea1458990793f56f42e4a47dbf35a12a351f26fa5d7e0cc7447eaafa21f",
-          "036c6802ce7e8113723dd92cdb852e492ebb157a871ca532c3cb9ed08248ff0e19"
-        ],
-        "signatures": [
-          "304402207515cf147d201f411092e6be5a64a6006f9308fad7b2a8fdaab22cd86ce764c202200974b8aca7bf51dbf54150d3884e1ae04f675637b926ec33bf75939446f6ca2801"
-        ]
-      }
-    ],
-    "pubKeyHashOutput": [
-      {
-        "exception": "Expected Buffer\\(Length: 20\\), got Buffer\\(Length: 2\\)",
-        "hash": "ffff"
-      }
-    ],
-    "scriptHashOutput": [
-      {
-        "exception": "Expected Buffer\\(Length: 20\\), got Buffer\\(Length: 3\\)",
-        "hash": "ffffff"
-      }
-    ],
-    "witnessPubKeyHashOutput": [
-      {
-        "exception": "Expected Buffer\\(Length: 20\\), got Buffer\\(Length: 3\\)",
-        "hash": "ffffff"
-      }
-    ],
-    "witnessScriptHashOutput": [
-      {
-        "exception": "Expected Buffer\\(Length: 32\\), got Buffer\\(Length: 3\\)",
-        "hash": "ffffff"
-      }
-    ]
+    "pubKey": {
+      "inputs": [
+        {
+          "description": "non-canonical signature (too short)",
+          "scriptSig": "304402207515cf147d201f411092e6be5a64a6006f9308fad7b2a8fdaab22cd86ce764c202200974b8aca7bf51dbf54150d3884e1ae04f675637b926ec33bf7593"
+        },
+        {
+          "description": "non-canonical signature (too long)",
+          "scriptSig": "304402207515cf147d201f411092e6be5a64a6006f9308fad7b2a8fdaab22cd86ce764c202200974b8aca7bf51dbf54150d3884e1ae04f675637b926ec33bf75939446f6ca28ffffffff01"
+        },
+        {
+          "description": "non-canonical signature (invalid hashType)",
+          "scriptSig": "304402207515cf147d201f411092e6be5a64a6006f9308fad7b2a8fdaab22cd86ce764c202200974b8aca7bf51dbf54150d3884e1ae04f675637b926ec33bf75939446f6ca28ff"
+        }
+      ],
+      "outputs": [
+        {
+          "description": "non-canonical pubkey (too short)",
+          "scriptPubKey": "02359c6e3f04cefbf089cf1d6670dc47c3fb4df68e2bad1fa5a369f9ce OP_CHECKSIG"
+        },
+        {
+          "description": "non-canonical pubkey (too long)",
+          "scriptPubKey": "02359c6e3f04cefbf089cf1d6670dc47c3fb4df68e2bad1fa5a369f9ce4b42bbd1ffffff OP_CHECKSIG"
+        },
+        {
+          "description": "last operator is wrong for pubkey-output",
+          "scriptPubKeyHex": "21027a71801ab59336de37785c50005b6abd8ea859eecce1edbe8e81afa74ee5c752ae"
+        },
+        {
+          "description": "missing OP_CHECKSIG",
+          "scriptPubKeyHex": "21027a71801ab59336de37785c50005b6abd8ea859eecce1edbe8e81afa74ee5c752"
+        },
+        {
+          "description": "non-canonical pubkey (bad prefix)",
+          "scriptPubKey": "427a71801ab59336de37785c50005b6abd8ea859eecce1edbe8e81afa74ee5c752 OP_CHECKSIG"
+        },
+        {
+          "description": "has extra opcode at the end isPubKeyOutput",
+          "scriptPubKey": "027a71801ab59336de37785c50005b6abd8ea859eecce1edbe8e81afa74ee5c752 OP_CHECKSIG OP_0"
+        }
+      ]
+    },
+    "pubKeyHash": {
+      "inputs": [
+        {
+          "description": "pubKeyHash input : extraneous data",
+          "scriptSig": "304402207515cf147d201f411092e6be5a64a6006f9308fad7b2a8fdaab22cd86ce764c202200974b8aca7bf51dbf54150d3884e1ae04f675637b926ec33bf75939446f6ca2801 02359c6e3f04cefbf089cf1d6670dc47c3fb4df68e2bad1fa5a369f9ce4b42bbd1 ffffffff"
+        }
+      ],
+      "outputs": [
+        {
+          "description": "non-minimal encoded isPubKeyHashOutput (non BIP62 compliant)",
+          "scriptPubKeyHex": "76a94c14aa4d7985c57e011a8b3dd8e0e5a73aaef41629c588ac"
+        },
+        {
+          "description": "bad OP_DUP isPubKeyHashOutput",
+          "scriptPubKeyHex": "aca914aa4d7985c57e011a8b3dd8e0e5a73aaef41629c588ac"
+        },
+        {
+          "description": "bad OP_HASH160 isPubKeyHashOutput",
+          "scriptPubKeyHex": "76ac14aa4d7985c57e011a8b3dd8e0e5a73aaef41629c588ac"
+        },
+        {
+          "description": "bad OP_EQUALVERIFY isPubKeyHashOutput",
+          "scriptPubKeyHex": "76a914aa4d7985c57e011a8b3dd8e0e5a73aaef41629c5acac"
+        },
+        {
+          "description": "bad OP_CHECKSIG isPubKeyHashOutput",
+          "scriptPubKeyHex": "76a914aa4d7985c57e011a8b3dd8e0e5a73aaef41629c58888"
+        },
+        {
+          "description": "bad length isPubKeyHashOutput",
+          "scriptPubKeyHex": "76a920aa4d7985c57e011a8b3dd8e0e5a73aaef41629c588ac"
+        },
+        {
+          "description": "has something at the end isPubKeyHashOutput",
+          "scriptPubKeyHex": "76a920aa4d7985c57e011a8b3dd8e0e5a73aaef41629c588ac00"
+        },
+        {
+          "exception": "Expected Buffer\\(Length: 20\\), got Buffer\\(Length: 2\\)",
+          "hash": "ffff"
+        }
+      ]
+    },
+    "scriptHash": {
+      "inputs": [
+        {
+          "description": "redeemScript not data",
+          "scriptSig": "OP_0 304402207515cf147d201f411092e6be5a64a6006f9308fad7b2a8fdaab22cd86ce764c202200974b8aca7bf51dbf54150d3884e1ae04f675637b926ec33bf75939446f6ca2801 3045022100ef253c1faa39e65115872519e5f0a33bbecf430c0f35cf562beabbad4da24d8d02201742be8ee49812a73adea3007c9641ce6725c32cd44ddb8e3a3af460015d140501 OP_RESERVED"
+        },
+        {
+          "description": "redeemScript is a signature, therefore not a valid script",
+          "scriptSig": "OP_0 3045022100e12b17b3a4c80c401a1687487bd2bafee9e5f1f8f1ffc6180ce186672ad7b43a02205e316d1e5e71822f5ef301b694e578fa9c94af4f5f098c952c833f4691307f4e01"
+        }
+      ],
+      "outputs": [
+        {
+          "description": "non-minimal encoded isScriptHashOutput (non BIP62 compliant)",
+          "scriptPubKeyHex": "a94c14c286a1af0947f58d1ad787385b1c2c4a976f9e7187"
+        },
+        {
+          "description": "wrong OP_HASH160 opcode",
+          "scriptPubKeyHex": "ac4c14c286a1af0947f58d1ad787385b1c2c4a976f9e7187"
+        },
+        {
+          "description": "wrong length marker",
+          "scriptPubKeyHex": "a916c286a1af0947f58d1ad787385b1c2c4a976f9e7187"
+        },
+        {
+          "description": "wrong OP_EQUAL opcode",
+          "scriptPubKeyHex": "a914c286a1af0947f58d1ad787385b1c2c4a976f9e7188"
+        },
+        {
+          "exception": "Expected Buffer\\(Length: 20\\), got Buffer\\(Length: 3\\)",
+          "hash": "ffffff"
+        }
+      ]
+    },
+    "multisig": {
+      "inputs": [
+        {
+          "description": "Not enough signatures provided",
+          "type": "multisig",
+          "scriptPubKey": "OP_2 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 02b80011a883a0fd621ad46dfc405df1e74bf075cbaf700fd4aebef6e96f848340 OP_2 OP_CHECKMULTISIG",
+          "signatures": []
+        },
+        {
+          "exception": "Not enough signatures provided",
+          "scriptPubKey": "OP_2 02359c6e3f04cefbf089cf1d6670dc47c3fb4df68e2bad1fa5a369f9ce4b42bbd1 0395a9d84d47d524548f79f435758c01faec5da2b7e551d3b8c995b7e06326ae4a OP_2 OP_CHECKMULTISIG",
+          "signatures": [
+            "304402207515cf147d201f411092e6be5a64a6006f9308fad7b2a8fdaab22cd86ce764c202200974b8aca7bf51dbf54150d3884e1ae04f675637b926ec33bf75939446f6ca2801"
+          ]
+        },
+        {
+          "exception": "Too many signatures provided",
+          "scriptPubKey": "OP_2 02359c6e3f04cefbf089cf1d6670dc47c3fb4df68e2bad1fa5a369f9ce4b42bbd1 0395a9d84d47d524548f79f435758c01faec5da2b7e551d3b8c995b7e06326ae4a OP_2 OP_CHECKMULTISIG",
+          "signatures": [
+            "304402207515cf147d201f411092e6be5a64a6006f9308fad7b2a8fdaab22cd86ce764c202200974b8aca7bf51dbf54150d3884e1ae04f675637b926ec33bf75939446f6ca2801",
+            "3045022100ef253c1faa39e65115872519e5f0a33bbecf430c0f35cf562beabbad4da24d8d02201742be8ee49812a73adea3007c9641ce6725c32cd44ddb8e3a3af460015d140501",
+            "3045022100ef253c1faa39e65115872519e5f0a33bbecf430c0f35cf562beabbad4da24d8d02201742be8ee49812a73adea3007c9641ce6725c32cd44ddb8e3a3af460015d140501"
+          ]
+        }
+      ],
+      "outputs": [
+        {
+          "description": "OP_CHECKMULTISIG not found",
+          "scriptPubKey": "OP_0 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 02b80011a883a0fd621ad46dfc405df1e74bf075cbaf700fd4aebef6e96f848340 OP_2 OP_HASH160"
+        },
+        {
+          "description": "less than 4 chunks",
+          "scriptPubKey": "OP_0 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 OP_HASH160"
+        },
+        {
+          "description": "m === 0",
+          "scriptPubKey": "OP_0 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 02b80011a883a0fd621ad46dfc405df1e74bf075cbaf700fd4aebef6e96f848340 OP_2 OP_CHECKMULTISIG"
+        },
+        {
+          "description": "m < OP_1",
+          "scriptPubKey": "OP_1NEGATE 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 02b80011a883a0fd621ad46dfc405df1e74bf075cbaf700fd4aebef6e96f848340 OP_2 OP_CHECKMULTISIG"
+        },
+        {
+          "description": "m > OP_16",
+          "scriptPubKey": "OP_NOP 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 02b80011a883a0fd621ad46dfc405df1e74bf075cbaf700fd4aebef6e96f848340 OP_2 OP_CHECKMULTISIG"
+        },
+        {
+          "description": "n === 0",
+          "scriptPubKey": "OP_1 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 02b80011a883a0fd621ad46dfc405df1e74bf075cbaf700fd4aebef6e96f848340 OP_0 OP_CHECKMULTISIG"
+        },
+        {
+          "description": "n < OP_1",
+          "scriptPubKey": "OP_1 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 02b80011a883a0fd621ad46dfc405df1e74bf075cbaf700fd4aebef6e96f848340 OP_1NEGATE OP_CHECKMULTISIG"
+        },
+        {
+          "description": "n > OP_16",
+          "scriptPubKey": "OP_1 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 02b80011a883a0fd621ad46dfc405df1e74bf075cbaf700fd4aebef6e96f848340 OP_NOP OP_CHECKMULTISIG"
+        },
+        {
+          "description": "n < m",
+          "scriptPubKey": "OP_2 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 02b80011a883a0fd621ad46dfc405df1e74bf075cbaf700fd4aebef6e96f848340 OP_1 OP_CHECKMULTISIG"
+        },
+        {
+          "description": "n < len(pubKeys)",
+          "scriptPubKey": "OP_2 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 02b80011a883a0fd621ad46dfc405df1e74bf075cbaf700fd4aebef6e96f848340 024289801366bcee6172b771cf5a7f13aaecd237a0b9a1ff9d769cabc2e6b70a34 OP_2 OP_CHECKMULTISIG"
+        },
+        {
+          "description": "n > len(pubKeys)",
+          "scriptPubKey": "OP_1 024289801366bcee6172b771cf5a7f13aaecd237a0b9a1ff9d769cabc2e6b70a34 OP_2 OP_CHECKMULTISIG"
+        },
+        {
+          "description": "m is data",
+          "scriptPubKey": "ffff 024289801366bcee6172b771cf5a7f13aaecd237a0b9a1ff9d769cabc2e6b70a34 OP_1 OP_CHECKMULTISIG"
+        },
+        {
+          "description": "n is data",
+          "scriptPubKey": "OP_1 024289801366bcee6172b771cf5a7f13aaecd237a0b9a1ff9d769cabc2e6b70a34 ffff OP_CHECKMULTISIG"
+        },
+        {
+          "description": "non-canonical pubKey (bad length)",
+          "scriptPubKey": "OP_1 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ffff OP_1 OP_CHECKMULTISIG"
+        },
+        {
+          "exception": "Not enough pubKeys provided",
+          "m": 4,
+          "pubKeys": [
+            "02ea1297665dd733d444f31ec2581020004892cdaaf3dd6c0107c615afb839785f",
+            "02fab2dea1458990793f56f42e4a47dbf35a12a351f26fa5d7e0cc7447eaafa21f",
+            "036c6802ce7e8113723dd92cdb852e492ebb157a871ca532c3cb9ed08248ff0e19"
+          ],
+          "signatures": [
+            "304402207515cf147d201f411092e6be5a64a6006f9308fad7b2a8fdaab22cd86ce764c202200974b8aca7bf51dbf54150d3884e1ae04f675637b926ec33bf75939446f6ca2801"
+          ]
+        }
+      ]
+    },
+    "witnessPubKeyHash": {
+      "inputs": [],
+      "outputs": [
+        {
+          "description": "wrong version",
+          "scriptPubKeyHex": "51149090909090909090909090909090909090909090"
+        },
+        {
+          "description": "wrong length marker",
+          "scriptPubKeyHex": "00209090909090909090909090909090909090909090"
+        },
+        {
+          "exception": "Expected Buffer\\(Length: 20\\), got Buffer\\(Length: 3\\)",
+          "hash": "ffffff"
+        }
+      ]
+    },
+    "witnessScriptHash": {
+      "inputs": [],
+      "outputs": [
+        {
+          "description": "wrong version",
+          "scriptPubKeyHex": "51209090909090909090909090909090909090909090909090909090909090909090"
+        },
+        {
+          "description": "wrong length marker",
+          "scriptPubKeyHex": "00219090909090909090909090909090909090909090909090909090909090909090"
+        },
+        {
+          "exception": "Expected Buffer\\(Length: 32\\), got Buffer\\(Length: 3\\)",
+          "hash": "ffffff"
+        }
+      ]
+    }
   }
 }
diff --git a/test/integration/advanced.js b/test/integration/advanced.js
index f778e00d..1bc612a4 100644
--- a/test/integration/advanced.js
+++ b/test/integration/advanced.js
@@ -16,7 +16,7 @@ describe('bitcoinjs-lib (advanced)', function () {
 
       var tx = new bitcoin.TransactionBuilder(network)
       var data = new Buffer('bitcoinjs-lib')
-      var dataScript = bitcoin.script.nullDataOutput(data)
+      var dataScript = bitcoin.script.nullData.output.encode(data)
 
       tx.addInput(unspent.txId, unspent.vout)
       tx.addOutput(dataScript, 1000)
diff --git a/test/integration/cltv.js b/test/integration/cltv.js
index 21eab667..b3bb4c03 100644
--- a/test/integration/cltv.js
+++ b/test/integration/cltv.js
@@ -39,7 +39,7 @@ describe('bitcoinjs-lib (CLTV)', function () {
     // three hours ago
     var timeUtc = utcNow() - (3600 * 3)
     var redeemScript = cltvCheckSigOutput(alice, bob, timeUtc)
-    var scriptPubKey = bitcoin.script.scriptHashOutput(bitcoin.crypto.hash160(redeemScript))
+    var scriptPubKey = bitcoin.script.scriptHash.output.encode(bitcoin.crypto.hash160(redeemScript))
     var address = bitcoin.address.fromOutputScript(scriptPubKey, network)
 
     // fund the P2SH(CLTV) address
@@ -55,7 +55,7 @@ describe('bitcoinjs-lib (CLTV)', function () {
       var signatureHash = txRaw.hashForSignature(0, redeemScript, hashType)
 
       // {Alice's signature} OP_TRUE
-      var redeemScriptSig = bitcoin.script.scriptHashInput([
+      var redeemScriptSig = bitcoin.script.scriptHash.input.encode([
         alice.sign(signatureHash).toScriptSignature(hashType),
         bitcoin.opcodes.OP_TRUE
       ], redeemScript)
@@ -73,7 +73,7 @@ describe('bitcoinjs-lib (CLTV)', function () {
     // two hours ago
     var timeUtc = utcNow() - (3600 * 2)
     var redeemScript = cltvCheckSigOutput(alice, bob, timeUtc)
-    var scriptPubKey = bitcoin.script.scriptHashOutput(bitcoin.crypto.hash160(redeemScript))
+    var scriptPubKey = bitcoin.script.scriptHash.output.encode(bitcoin.crypto.hash160(redeemScript))
     var address = bitcoin.address.fromOutputScript(scriptPubKey, network)
 
     // fund the P2SH(CLTV) address
@@ -86,7 +86,7 @@ describe('bitcoinjs-lib (CLTV)', function () {
 
       var txRaw = tx.buildIncomplete()
       var signatureHash = txRaw.hashForSignature(0, redeemScript, hashType)
-      var redeemScriptSig = bitcoin.script.scriptHashInput([
+      var redeemScriptSig = bitcoin.script.scriptHash.input.encode([
         alice.sign(signatureHash).toScriptSignature(hashType),
         bob.sign(signatureHash).toScriptSignature(hashType),
         bitcoin.opcodes.OP_FALSE
@@ -105,7 +105,7 @@ describe('bitcoinjs-lib (CLTV)', function () {
     // two hours from now
     var timeUtc = utcNow() + (3600 * 2)
     var redeemScript = cltvCheckSigOutput(alice, bob, timeUtc)
-    var scriptPubKey = bitcoin.script.scriptHashOutput(bitcoin.crypto.hash160(redeemScript))
+    var scriptPubKey = bitcoin.script.scriptHash.output.encode(bitcoin.crypto.hash160(redeemScript))
     var address = bitcoin.address.fromOutputScript(scriptPubKey, network)
 
     // fund the P2SH(CLTV) address
@@ -121,7 +121,7 @@ describe('bitcoinjs-lib (CLTV)', function () {
       var signatureHash = txRaw.hashForSignature(0, redeemScript, hashType)
 
       // {Alice's signature} OP_TRUE
-      var redeemScriptSig = bitcoin.script.scriptHashInput([
+      var redeemScriptSig = bitcoin.script.scriptHash.input.encode([
         alice.sign(signatureHash).toScriptSignature(hashType),
         bitcoin.opcodes.OP_TRUE
       ], redeemScript)
diff --git a/test/integration/crypto.js b/test/integration/crypto.js
index 84fc9ca2..4b3674c8 100644
--- a/test/integration/crypto.js
+++ b/test/integration/crypto.js
@@ -91,7 +91,7 @@ describe('bitcoinjs-lib (crypto)', function () {
         var script = transaction.ins[input.vout].script
         var scriptChunks = bitcoin.script.decompile(script)
 
-        assert(bitcoin.script.isPubKeyHashInput(scriptChunks), 'Expected pubKeyHash script')
+        assert(bitcoin.script.pubKeyHash.input.check(scriptChunks), 'Expected pubKeyHash script')
 
         var prevOutTxId = [].reverse.call(new Buffer(transaction.ins[input.vout].hash)).toString('hex')
         var prevVout = transaction.ins[input.vout].index
diff --git a/test/integration/multisig.js b/test/integration/multisig.js
index fbb4bd84..1d4c06f4 100644
--- a/test/integration/multisig.js
+++ b/test/integration/multisig.js
@@ -15,8 +15,8 @@ describe('bitcoinjs-lib (multisig)', function () {
       return new Buffer(hex, 'hex')
     })
 
-    var redeemScript = bitcoin.script.multisigOutput(2, pubKeys) // 2 of 3
-    var scriptPubKey = bitcoin.script.scriptHashOutput(bitcoin.crypto.hash160(redeemScript))
+    var redeemScript = bitcoin.script.multisig.output.encode(2, pubKeys) // 2 of 3
+    var scriptPubKey = bitcoin.script.scriptHash.output.encode(bitcoin.crypto.hash160(redeemScript))
     var address = bitcoin.address.fromOutputScript(scriptPubKey)
 
     assert.strictEqual(address, '36NUkt6FWUi3LAWBqWRdDmdTWbt91Yvfu7')
@@ -33,8 +33,8 @@ describe('bitcoinjs-lib (multisig)', function () {
     ].map(function (wif) { return bitcoin.ECPair.fromWIF(wif, bitcoin.networks.testnet) })
     var pubKeys = keyPairs.map(function (x) { return x.getPublicKeyBuffer() })
 
-    var redeemScript = bitcoin.script.multisigOutput(2, pubKeys) // 2 of 4
-    var scriptPubKey = bitcoin.script.scriptHashOutput(bitcoin.crypto.hash160(redeemScript))
+    var redeemScript = bitcoin.script.multisig.output.encode(2, pubKeys) // 2 of 4
+    var scriptPubKey = bitcoin.script.scriptHash.output.encode(bitcoin.crypto.hash160(redeemScript))
     var address = bitcoin.address.fromOutputScript(scriptPubKey, bitcoin.networks.testnet)
 
     // attempt to send funds to the source address
diff --git a/test/script.js b/test/script.js
index 0a2bf5be..f1fe1f68 100644
--- a/test/script.js
+++ b/test/script.js
@@ -1,10 +1,8 @@
 /* global describe, it */
 
 var assert = require('assert')
-var bcrypto = require('../src/crypto')
 var bscript = require('../src/script')
 var minimalData = require('minimaldata')
-var ops = require('../src/opcodes')
 
 var fixtures = require('./fixtures/script.json')
 
@@ -107,367 +105,6 @@ describe('script', function () {
     })
   })
 
-  describe('classifyInput', function () {
-    fixtures.valid.forEach(function (f) {
-      if (!f.scriptSig) return
-
-      it('classifies ' + f.scriptSig + ' as ' + f.type, function () {
-        var scriptSig = bscript.fromASM(f.scriptSig)
-        var type = bscript.classifyInput(scriptSig)
-
-        assert.strictEqual(type, f.type)
-      })
-    })
-
-    fixtures.valid.forEach(function (f) {
-      if (!f.scriptSig) return
-      if (!f.typeIncomplete) return
-
-      it('classifies incomplete ' + f.scriptSig + ' as ' + f.typeIncomplete, function () {
-        var scriptSig = bscript.fromASM(f.scriptSig)
-        var type = bscript.classifyInput(scriptSig, true)
-
-        assert.strictEqual(type, f.typeIncomplete)
-      })
-    })
-  })
-
-  describe('classifyOutput', function () {
-    fixtures.valid.forEach(function (f) {
-      if (!f.scriptPubKey) return
-
-      it('classifies ' + f.scriptPubKey + ' as ' + f.type, function () {
-        var scriptPubKey = bscript.fromASM(f.scriptPubKey)
-        var type = bscript.classifyOutput(scriptPubKey)
-
-        assert.strictEqual(type, f.type)
-      })
-    })
-  })
-
-  ;[
-    'PubKey',
-    'PubKeyHash',
-    'ScriptHash',
-    'WitnessPubKeyHash',
-    'WitnessScriptHash',
-    'Multisig',
-    'NullData'
-  ].forEach(function (type) {
-    var inputFnName = 'is' + type + 'Input'
-    var outputFnName = 'is' + type + 'Output'
-
-    var inputFn = bscript[inputFnName]
-    var outputFn = bscript[outputFnName]
-
-    describe('is' + type + 'Input', function () {
-      fixtures.valid.forEach(function (f) {
-        var expected = type.toLowerCase() === f.type
-
-        if (inputFn && f.scriptSig) {
-          var scriptSig = bscript.fromASM(f.scriptSig)
-
-          it('returns ' + expected + ' for ' + f.scriptSig, function () {
-            assert.strictEqual(inputFn(scriptSig), expected)
-          })
-
-          if (f.typeIncomplete) {
-            var expectedIncomplete = type.toLowerCase() === f.typeIncomplete
-
-            it('returns ' + expected + ' for ' + f.scriptSig, function () {
-              assert.strictEqual(inputFn(scriptSig, true), expectedIncomplete)
-            })
-          }
-        }
-      })
-
-      if (!(inputFnName in fixtures.invalid)) return
-
-      fixtures.invalid[inputFnName].forEach(function (f) {
-        it('returns false for ' + f.description + ' (' + (f.scriptSig || f.scriptSigHex) + ')', function () {
-          var scriptSig
-
-          if (f.scriptSig) {
-            scriptSig = bscript.fromASM(f.scriptSig)
-          } else {
-            scriptSig = new Buffer(f.scriptSigHex, 'hex')
-          }
-
-          assert.strictEqual(inputFn(scriptSig), false)
-        })
-      })
-    })
-
-    describe('is' + type + 'Output', function () {
-      fixtures.valid.forEach(function (f) {
-        var expected = type.toLowerCase() === f.type
-
-        if (outputFn && f.scriptPubKey) {
-          it('returns ' + expected + ' for ' + f.scriptPubKey, function () {
-            var scriptPubKey = bscript.fromASM(f.scriptPubKey)
-
-            assert.strictEqual(outputFn(scriptPubKey), expected)
-          })
-        }
-      })
-
-      if (!(outputFnName in fixtures.invalid)) return
-
-      fixtures.invalid[outputFnName].forEach(function (f) {
-        it('returns false for ' + f.description + ' (' + (f.scriptPubKey || f.scriptPubKeyHex) + ')', function () {
-          var scriptPubKey
-
-          if (f.scriptPubKey) {
-            scriptPubKey = bscript.fromASM(f.scriptPubKey)
-          } else {
-            scriptPubKey = new Buffer(f.scriptPubKeyHex, 'hex')
-          }
-
-          assert.strictEqual(outputFn(scriptPubKey), false)
-        })
-      })
-    })
-  })
-
-  describe('pubKeyInput', function () {
-    fixtures.valid.forEach(function (f) {
-      if (f.type !== 'pubkey') return
-
-      it('returns ' + f.scriptSig, function () {
-        var signature = new Buffer(f.signature, 'hex')
-
-        var scriptSig = bscript.pubKeyInput(signature)
-        assert.strictEqual(bscript.toASM(scriptSig), f.scriptSig)
-      })
-    })
-  })
-
-  describe('pubKeyOutput', function () {
-    fixtures.valid.forEach(function (f) {
-      if (f.type !== 'pubkey') return
-
-      it('returns ' + f.scriptPubKey, function () {
-        var pubKey = new Buffer(f.pubKey, 'hex')
-        var scriptPubKey = bscript.pubKeyOutput(pubKey)
-
-        assert.strictEqual(bscript.toASM(scriptPubKey), f.scriptPubKey)
-      })
-    })
-  })
-
-  describe('pubKeyHashInput', function () {
-    fixtures.valid.forEach(function (f) {
-      if (f.type !== 'pubkeyhash') return
-
-      var pubKey = new Buffer(f.pubKey, 'hex')
-
-      it('returns ' + f.scriptSig, function () {
-        var signature = new Buffer(f.signature, 'hex')
-
-        var scriptSig = bscript.pubKeyHashInput(signature, pubKey)
-        assert.strictEqual(bscript.toASM(scriptSig), f.scriptSig)
-      })
-    })
-  })
-
-  describe('pubKeyHashOutput', function () {
-    fixtures.valid.forEach(function (f) {
-      if (f.type !== 'pubkeyhash') return
-
-      var pubKey = new Buffer(f.pubKey, 'hex')
-      var pubKeyHash = bcrypto.hash160(pubKey)
-
-      it('returns ' + f.scriptPubKey, function () {
-        var scriptPubKey = bscript.pubKeyHashOutput(pubKeyHash)
-        assert.strictEqual(bscript.toASM(scriptPubKey), f.scriptPubKey)
-      })
-    })
-
-    fixtures.invalid.pubKeyHashOutput.forEach(function (f) {
-      var hash = new Buffer(f.hash, 'hex')
-
-      it('throws on ' + f.exception, function () {
-        assert.throws(function () {
-          bscript.pubKeyHashOutput(hash)
-        }, new RegExp(f.exception))
-      })
-    })
-  })
-
-  describe('multisigInput', function () {
-    fixtures.valid.forEach(function (f) {
-      if (f.type !== 'multisig') return
-
-      it('returns ' + f.scriptSig, function () {
-        var signatures = f.signatures.map(function (signature) {
-          return signature ? new Buffer(signature, 'hex') : ops.OP_0
-        })
-
-        var scriptSig = bscript.multisigInput(signatures)
-        assert.strictEqual(bscript.toASM(scriptSig), f.scriptSig)
-      })
-    })
-
-    fixtures.invalid.multisigInput.forEach(function (f) {
-      var scriptPubKey = bscript.fromASM(f.scriptPubKey)
-
-      it('throws on ' + f.exception, function () {
-        var signatures = f.signatures.map(function (signature) {
-          return signature ? new Buffer(signature, 'hex') : ops.OP_0
-        })
-
-        assert.throws(function () {
-          bscript.multisigInput(signatures, scriptPubKey)
-        }, new RegExp(f.exception))
-      })
-    })
-  })
-
-  describe('multisigOutput', function () {
-    fixtures.valid.forEach(function (f) {
-      if (f.type !== 'multisig') return
-
-      var pubKeys = f.pubKeys.map(function (p) { return new Buffer(p, 'hex') })
-      var scriptPubKey = bscript.multisigOutput(pubKeys.length, pubKeys)
-
-      it('returns ' + f.scriptPubKey, function () {
-        assert.strictEqual(bscript.toASM(scriptPubKey), f.scriptPubKey)
-      })
-    })
-
-    fixtures.invalid.multisigOutput.forEach(function (f) {
-      var pubKeys = f.pubKeys.map(function (p) {
-        return new Buffer(p, 'hex')
-      })
-
-      it('throws on ' + f.exception, function () {
-        assert.throws(function () {
-          bscript.multisigOutput(f.m, pubKeys)
-        }, new RegExp(f.exception))
-      })
-    })
-  })
-
-  describe('scriptHashInput', function () {
-    fixtures.valid.forEach(function (f) {
-      if (f.type !== 'scripthash') return
-
-      var redeemScript = bscript.fromASM(f.redeemScript)
-      var redeemScriptSig = bscript.fromASM(f.redeemScriptSig)
-
-      it('returns ' + f.scriptSig, function () {
-        var scriptSig = bscript.scriptHashInput(redeemScriptSig, redeemScript)
-
-        if (f.scriptSig) {
-          assert.strictEqual(bscript.toASM(scriptSig), f.scriptSig)
-        } else {
-          assert.strictEqual(scriptSig.toString('hex'), f.scriptSigHex)
-        }
-      })
-    })
-  })
-
-  describe('scriptHashOutput', function () {
-    fixtures.valid.forEach(function (f) {
-      if (f.type !== 'scripthash') return
-      if (!f.scriptPubKey) return
-
-      it('returns ' + f.scriptPubKey, function () {
-        var redeemScript = bscript.fromASM(f.redeemScript)
-        var scriptPubKey = bscript.scriptHashOutput(bcrypto.hash160(redeemScript))
-
-        assert.strictEqual(bscript.toASM(scriptPubKey), f.scriptPubKey)
-      })
-    })
-
-    fixtures.invalid.scriptHashOutput.forEach(function (f) {
-      var hash = new Buffer(f.hash, 'hex')
-
-      it('throws on ' + f.exception, function () {
-        assert.throws(function () {
-          bscript.scriptHashOutput(hash)
-        }, new RegExp(f.exception))
-      })
-    })
-  })
-
-  describe('witnessPubKeyHashOutput', function () {
-    fixtures.valid.forEach(function (f) {
-      if (f.type !== 'witnesspubkeyhash') return
-      if (!f.scriptPubKey) return
-
-      var pubKey = new Buffer(f.pubKey, 'hex')
-      var pubKeyHash = bcrypto.hash160(pubKey)
-
-      it('returns ' + f.scriptPubKey, function () {
-        var scriptPubKey = bscript.witnessPubKeyHashOutput(pubKeyHash)
-        assert.strictEqual(bscript.toASM(scriptPubKey), f.scriptPubKey)
-      })
-    })
-
-    fixtures.invalid.witnessPubKeyHashOutput.forEach(function (f) {
-      var hash = new Buffer(f.hash, 'hex')
-
-      it('throws on ' + f.exception, function () {
-        assert.throws(function () {
-          bscript.witnessPubKeyHashOutput(hash)
-        }, new RegExp(f.exception))
-      })
-    })
-  })
-
-  describe('witnessScriptHashInput', function () {
-    fixtures.valid.forEach(function (f) {
-      if (f.type !== 'witnessscripthash') return
-
-      var witnessScript = bscript.fromASM(f.witnessScriptPubKey)
-      var witnessScriptSig = bscript.fromASM(f.witnessScriptSig)
-
-      it('returns ' + f.witness, function () {
-        var witness = bscript.witnessScriptHashInput(witnessScriptSig, witnessScript)
-
-        assert.strictEqual(bscript.toASM(witness), f.witness)
-      })
-    })
-  })
-
-  describe('witnessScriptHashOutput', function () {
-    fixtures.valid.forEach(function (f) {
-      if (f.type !== 'witnessscripthash') return
-      if (!f.scriptPubKey) return
-
-      it('returns ' + f.scriptPubKey, function () {
-        var witnessScriptPubKey = bscript.fromASM(f.witnessScriptPubKey)
-        var scriptPubKey = bscript.witnessScriptHashOutput(bcrypto.hash256(witnessScriptPubKey))
-
-        assert.strictEqual(bscript.toASM(scriptPubKey), f.scriptPubKey)
-      })
-    })
-
-    fixtures.invalid.witnessScriptHashOutput.forEach(function (f) {
-      var hash = new Buffer(f.hash, 'hex')
-
-      it('throws on ' + f.exception, function () {
-        assert.throws(function () {
-          bscript.witnessScriptHashOutput(hash)
-        }, new RegExp(f.exception))
-      })
-    })
-  })
-
-  describe('nullDataOutput', function () {
-    fixtures.valid.forEach(function (f) {
-      if (f.type !== 'nulldata') return
-
-      var data = new Buffer(f.data, 'hex')
-      var scriptPubKey = bscript.nullDataOutput(data)
-
-      it('returns ' + f.scriptPubKey, function () {
-        assert.strictEqual(bscript.toASM(scriptPubKey), f.scriptPubKey)
-      })
-    })
-  })
-
   describe('SCRIPT_VERIFY_MINIMALDATA policy', function () {
     fixtures.valid.forEach(function (f) {
       if (f.scriptSigHex) {
diff --git a/test/templates.js b/test/templates.js
new file mode 100644
index 00000000..ad325583
--- /dev/null
+++ b/test/templates.js
@@ -0,0 +1,420 @@
+/* global describe, it */
+
+var assert = require('assert')
+var bcrypto = require('../src/crypto')
+var bscript = require('../src/script')
+var ops = require('../src/opcodes')
+
+var fixtures = require('./fixtures/script.json')
+
+describe('script-templates', function () {
+  describe('classifyInput', function () {
+    fixtures.valid.forEach(function (f) {
+      if (!f.scriptSig) return
+
+      it('classifies ' + f.scriptSig + ' as ' + f.type, function () {
+        var scriptSig = bscript.fromASM(f.scriptSig)
+        var type = bscript.classifyInput(scriptSig)
+
+        assert.strictEqual(type, f.type)
+      })
+    })
+
+    fixtures.valid.forEach(function (f) {
+      if (!f.scriptSig) return
+      if (!f.typeIncomplete) return
+
+      it('classifies incomplete ' + f.scriptSig + ' as ' + f.typeIncomplete, function () {
+        var scriptSig = bscript.fromASM(f.scriptSig)
+        var type = bscript.classifyInput(scriptSig, true)
+
+        assert.strictEqual(type, f.typeIncomplete)
+      })
+    })
+  })
+
+  describe('classifyOutput', function () {
+    fixtures.valid.forEach(function (f) {
+      if (!f.scriptPubKey) return
+
+      it('classifies ' + f.scriptPubKey + ' as ' + f.type, function () {
+        var scriptPubKey = bscript.fromASM(f.scriptPubKey)
+        var type = bscript.classifyOutput(scriptPubKey)
+
+        assert.strictEqual(type, f.type)
+      })
+    })
+  })
+
+  ;[
+    'pubKey',
+    'pubKeyHash',
+    'scriptHash',
+    'witnessPubKeyHash',
+    'witnessScriptHash',
+    'multisig',
+    'nullData'
+  ].forEach(function (name) {
+    var inputType = bscript[name].input
+    var outputType = bscript[name].output
+
+    describe(name + '.input.check', function () {
+      fixtures.valid.forEach(function (f) {
+        var expected = name.toLowerCase() === f.type
+
+        if (inputType && f.scriptSig) {
+          var scriptSig = bscript.fromASM(f.scriptSig)
+
+          it('returns ' + expected + ' for ' + f.scriptSig, function () {
+            assert.strictEqual(inputType.check(scriptSig), expected)
+          })
+
+          if (f.typeIncomplete) {
+            var expectedIncomplete = name.toLowerCase() === f.typeIncomplete
+
+            it('returns ' + expected + ' for ' + f.scriptSig, function () {
+              assert.strictEqual(inputType.check(scriptSig, true), expectedIncomplete)
+            })
+          }
+        }
+      })
+
+      if (!(fixtures.invalid[name])) return
+
+      fixtures.invalid[name].inputs.forEach(function (f) {
+        if (!f.scriptSig && !f.scriptSigHex) return
+
+        it('returns false for ' + f.description + ' (' + (f.scriptSig || f.scriptSigHex) + ')', function () {
+          var scriptSig
+
+          if (f.scriptSig) {
+            scriptSig = bscript.fromASM(f.scriptSig)
+          } else {
+            scriptSig = new Buffer(f.scriptSigHex, 'hex')
+          }
+
+          assert.strictEqual(inputType.check(scriptSig), false)
+        })
+      })
+    })
+
+    describe(name + '.output.check', function () {
+      fixtures.valid.forEach(function (f) {
+        var expected = name.toLowerCase() === f.type
+
+        if (outputType && f.scriptPubKey) {
+          it('returns ' + expected + ' for ' + f.scriptPubKey, function () {
+            var scriptPubKey = bscript.fromASM(f.scriptPubKey)
+
+            assert.strictEqual(outputType.check(scriptPubKey), expected)
+          })
+        }
+      })
+
+      if (!(fixtures.invalid[name])) return
+
+      fixtures.invalid[name].outputs.forEach(function (f) {
+        if (!f.scriptPubKey && !f.scriptPubKeyHex) return
+
+        it('returns false for ' + f.description + ' (' + (f.scriptPubKey || f.scriptPubKeyHex) + ')', function () {
+          var scriptPubKey
+
+          if (f.scriptPubKey) {
+            scriptPubKey = bscript.fromASM(f.scriptPubKey)
+          } else {
+            scriptPubKey = new Buffer(f.scriptPubKeyHex, 'hex')
+          }
+
+          assert.strictEqual(outputType.check(scriptPubKey), false)
+        })
+      })
+    })
+  })
+
+  describe('pubKey.input', function () {
+    fixtures.valid.forEach(function (f) {
+      if (f.type !== 'pubkey') return
+
+      var signature = new Buffer(f.signature, 'hex')
+      var scriptSig = bscript.pubKey.input.encode(signature)
+
+      it('encodes to ' + f.scriptSig, function () {
+        assert.strictEqual(bscript.toASM(scriptSig), f.scriptSig)
+      })
+
+      it('decodes to ' + f.signature, function () {
+        assert.deepEqual(bscript.pubKey.input.decode(scriptSig), signature)
+      })
+    })
+  })
+
+  describe('pubKey.output', function () {
+    fixtures.valid.forEach(function (f) {
+      if (f.type !== 'pubkey') return
+
+      var pubKey = new Buffer(f.pubKey, 'hex')
+      var scriptPubKey = bscript.pubKey.output.encode(pubKey)
+
+      it('encodes to ' + f.scriptPubKey, function () {
+        assert.strictEqual(bscript.toASM(scriptPubKey), f.scriptPubKey)
+      })
+
+      it('decodes to ' + f.pubKey, function () {
+        assert.deepEqual(bscript.pubKey.output.decode(scriptPubKey), pubKey)
+      })
+    })
+  })
+
+  describe('pubKeyHash.input', function () {
+    fixtures.valid.forEach(function (f) {
+      if (f.type !== 'pubkeyhash') return
+
+      var pubKey = new Buffer(f.pubKey, 'hex')
+      var signature = new Buffer(f.signature, 'hex')
+      var scriptSig = bscript.pubKeyHash.input.encode(signature, pubKey)
+
+      it('encodes to ' + f.scriptSig, function () {
+        assert.strictEqual(bscript.toASM(scriptSig), f.scriptSig)
+      })
+
+      it('decodes to original arguments', function () {
+        assert.deepEqual(bscript.pubKeyHash.input.decode(scriptSig), {
+          signature: signature,
+          pubKey: pubKey
+        })
+      })
+    })
+  })
+
+  describe('pubKeyHash.output', function () {
+    fixtures.valid.forEach(function (f) {
+      if (f.type !== 'pubkeyhash') return
+
+      var pubKey = new Buffer(f.pubKey, 'hex')
+      var pubKeyHash = bcrypto.hash160(pubKey)
+      var scriptPubKey = bscript.pubKeyHash.output.encode(pubKeyHash)
+
+      it('encodes to ' + f.scriptPubKey, function () {
+        assert.strictEqual(bscript.toASM(scriptPubKey), f.scriptPubKey)
+      })
+
+      it('decodes to ' + pubKeyHash.toString('hex'), function () {
+        assert.deepEqual(bscript.pubKeyHash.output.decode(scriptPubKey), pubKeyHash)
+      })
+    })
+
+    fixtures.invalid.pubKeyHash.outputs.forEach(function (f) {
+      if (!f.hash) return
+      var hash = new Buffer(f.hash, 'hex')
+
+      it('throws on ' + f.exception, function () {
+        assert.throws(function () {
+          bscript.pubKeyHash.output.encode(hash)
+        }, new RegExp(f.exception))
+      })
+    })
+  })
+
+  describe('multisig.input', function () {
+    fixtures.valid.forEach(function (f) {
+      if (f.type !== 'multisig' && f.typeIncomplete !== 'multisig') return
+      var allowIncomplete = f.typeIncomplete !== undefined
+
+      var signatures = f.signatures.map(function (signature) {
+        return signature ? new Buffer(signature, 'hex') : ops.OP_0
+      })
+
+      var scriptSig = bscript.multisig.input.encode(signatures)
+
+      it('encodes to ' + f.scriptSig, function () {
+        assert.strictEqual(bscript.toASM(scriptSig), f.scriptSig)
+      })
+
+      it('decodes to ' + signatures.map(function (x) { return x === ops.OP_0 ? 'OP_0' : x.toString('hex') }), function () {
+        assert.deepEqual(bscript.multisig.input.decode(scriptSig, allowIncomplete), signatures)
+      })
+    })
+
+    fixtures.invalid.multisig.inputs.forEach(function (f) {
+      if (!f.scriptPubKey) return
+      var scriptPubKey = bscript.fromASM(f.scriptPubKey)
+
+      it('throws on ' + f.exception, function () {
+        var signatures = f.signatures.map(function (signature) {
+          return signature ? new Buffer(signature, 'hex') : ops.OP_0
+        })
+
+        assert.throws(function () {
+          bscript.multisig.input.encode(signatures, scriptPubKey)
+        }, new RegExp(f.exception))
+      })
+    })
+  })
+
+  describe('multisig.output', function () {
+    fixtures.valid.forEach(function (f) {
+      if (f.type !== 'multisig') return
+
+      var pubKeys = f.pubKeys.map(function (p) { return new Buffer(p, 'hex') })
+      var m = pubKeys.length
+
+      var scriptPubKey = bscript.multisig.output.encode(m, pubKeys)
+
+      it('encodes ' + f.scriptPubKey, function () {
+        assert.strictEqual(bscript.toASM(scriptPubKey), f.scriptPubKey)
+      })
+
+      it('decodes to original arguments', function () {
+        assert.deepEqual(bscript.multisig.output.decode(scriptPubKey), {
+          m: m,
+          pubKeys: pubKeys
+        })
+      })
+    })
+
+    fixtures.invalid.multisig.outputs.forEach(function (f) {
+      if (!f.pubKeys) return
+      var pubKeys = f.pubKeys.map(function (p) {
+        return new Buffer(p, 'hex')
+      })
+
+      it('throws on ' + f.exception, function () {
+        assert.throws(function () {
+          bscript.multisig.output.encode(f.m, pubKeys)
+        }, new RegExp(f.exception))
+      })
+    })
+  })
+
+  describe('scriptHash.input', function () {
+    fixtures.valid.forEach(function (f) {
+      if (f.type !== 'scripthash') return
+
+      var redeemScript = bscript.fromASM(f.redeemScript)
+      var redeemScriptSig = bscript.fromASM(f.redeemScriptSig)
+      var scriptSig = bscript.scriptHash.input.encode(redeemScriptSig, redeemScript)
+
+      it('encodes to ' + f.scriptPubKey, function () {
+        if (f.scriptSig) {
+          assert.strictEqual(bscript.toASM(scriptSig), f.scriptSig)
+        } else {
+          assert.strictEqual(scriptSig.toString('hex'), f.scriptSigHex)
+        }
+      })
+
+      it('decodes to original arguments', function () {
+        assert.deepEqual(bscript.scriptHash.input.decode(scriptSig), {
+          redeemScriptSig: redeemScriptSig,
+          redeemScript: redeemScript
+        })
+      })
+    })
+  })
+
+  describe('scriptHash.output', function () {
+    fixtures.valid.forEach(function (f) {
+      if (f.type !== 'scripthash') return
+      if (!f.scriptPubKey) return
+
+      var redeemScript = bscript.fromASM(f.redeemScript)
+      var scriptHash = bcrypto.hash160(redeemScript)
+      var scriptPubKey = bscript.scriptHash.output.encode(scriptHash)
+
+      it('encodes to ' + f.scriptPubKey, function () {
+        assert.strictEqual(bscript.toASM(scriptPubKey), f.scriptPubKey)
+      })
+
+      it('decodes to ' + scriptHash.toString('hex'), function () {
+        assert.deepEqual(bscript.scriptHash.output.decode(scriptPubKey), scriptHash)
+      })
+    })
+
+    fixtures.invalid.scriptHash.outputs.forEach(function (f) {
+      if (!f.hash) return
+      var hash = new Buffer(f.hash, 'hex')
+
+      it('throws on ' + f.exception, function () {
+        assert.throws(function () {
+          bscript.scriptHash.output.encode(hash)
+        }, new RegExp(f.exception))
+      })
+    })
+  })
+
+  describe('witnessPubKeyHash.output', function () {
+    fixtures.valid.forEach(function (f) {
+      if (f.type !== 'witnesspubkeyhash') return
+      if (!f.scriptPubKey) return
+
+      var pubKey = new Buffer(f.pubKey, 'hex')
+      var pubKeyHash = bcrypto.hash160(pubKey)
+      var scriptPubKey = bscript.witnessPubKeyHash.output.encode(pubKeyHash)
+
+      it('encodes to ' + f.scriptPubKey, function () {
+        assert.strictEqual(bscript.toASM(scriptPubKey), f.scriptPubKey)
+      })
+
+      it('decodes to ' + pubKeyHash.toString('hex'), function () {
+        assert.deepEqual(bscript.witnessPubKeyHash.output.decode(scriptPubKey), pubKeyHash)
+      })
+    })
+
+    fixtures.invalid.witnessPubKeyHash.outputs.forEach(function (f) {
+      if (!f.hash) return
+      var hash = new Buffer(f.hash, 'hex')
+
+      it('throws on ' + f.exception, function () {
+        assert.throws(function () {
+          bscript.witnessPubKeyHash.output.encode(hash)
+        }, new RegExp(f.exception))
+      })
+    })
+  })
+
+  describe('witnessScriptHash.output', function () {
+    fixtures.valid.forEach(function (f) {
+      if (f.type !== 'witnessscripthash') return
+      if (!f.scriptPubKey) return
+
+      var witnessScriptPubKey = bscript.fromASM(f.witnessScriptPubKey)
+      var scriptHash = bcrypto.hash256(witnessScriptPubKey)
+      var scriptPubKey = bscript.witnessScriptHash.output.encode(scriptHash)
+
+      it('encodes to ' + f.scriptPubKey, function () {
+        assert.strictEqual(bscript.toASM(scriptPubKey), f.scriptPubKey)
+      })
+
+      it('decodes to ' + scriptHash.toString('hex'), function () {
+        assert.deepEqual(bscript.witnessScriptHash.output.decode(scriptPubKey), scriptHash)
+      })
+    })
+
+    fixtures.invalid.witnessScriptHash.outputs.forEach(function (f) {
+      if (!f.hash) return
+      var hash = new Buffer(f.hash, 'hex')
+
+      it('throws on ' + f.exception, function () {
+        assert.throws(function () {
+          bscript.witnessScriptHash.output.encode(hash)
+        }, new RegExp(f.exception))
+      })
+    })
+  })
+
+  describe('nullData.output', function () {
+    fixtures.valid.forEach(function (f) {
+      if (f.type !== 'nulldata') return
+
+      var data = new Buffer(f.data, 'hex')
+      var scriptPubKey = bscript.nullData.output.encode(data)
+
+      it('encodes to ' + f.scriptPubKey, function () {
+        assert.strictEqual(bscript.toASM(scriptPubKey), f.scriptPubKey)
+      })
+
+      it('decodes to ' + f.data, function () {
+        assert.deepEqual(bscript.nullData.output.decode(scriptPubKey), data)
+      })
+    })
+  })
+})
diff --git a/test/transaction_builder.js b/test/transaction_builder.js
index de4dc27f..97c52ca3 100644
--- a/test/transaction_builder.js
+++ b/test/transaction_builder.js
@@ -357,7 +357,7 @@ describe('TransactionBuilder', function () {
                 var signatures = bscript.decompile(scriptSig).slice(1, -1).filter(function (x) { return x !== ops.OP_0 })
 
                 // rebuild/replace the scriptSig without them
-                var replacement = bscript.scriptHashInput(bscript.multisigInput(signatures), redeemScript)
+                var replacement = bscript.scriptHash.input.encode(bscript.multisig.input.encode(signatures), redeemScript)
                 assert.strictEqual(bscript.toASM(replacement), sign.scriptSigFiltered)
 
                 tx.ins[i].script = replacement