Repository: bitcoinjs/bitcoinjs-lib
Pull request #602 (2016-07-11 15:40:12)

SegWit - witness script templates
A derivative of the preparatory work done by @rubensayshi in https://github.com/bitcoinjs/bitcoinjs-lib/pull/520.
This purely encompasses the `script` changes and adds relevant tests for them.

This change should be atomic enough such that it can be merged easily.
~~Before merge, an integration test showing the full process of steps to inter-operate with SegWit should be added.~~

rubensayshi: are you gonna have more for the transaction de/seralializing and signing? or should I clean up my PR and rebase on this?
dcousens: > are you gonna have more for the transaction de/seralializing and signing?

I was going to keep it atomic,  but I guess for the proposed integration test, I'll need to.

No `TransactionBuilder` changes at this time though.
dcousens: @afk11 resolved your comments in b02761d.  Great suggestions :)

Patch:
diff --git a/src/script.js b/src/script.js
index 917a8e51..07f85511 100644
--- a/src/script.js
+++ b/src/script.js
@@ -206,6 +206,22 @@ function isScriptHashOutput (script) {
     buffer[22] === OPS.OP_EQUAL
 }
 
+function isWitnessPubKeyHashOutput (script) {
+  var buffer = compile(script)
+
+  return buffer.length === 22 &&
+    buffer[0] === OPS.OP_0 &&
+    buffer[1] === 0x14
+}
+
+function isWitnessScriptHashOutput (script) {
+  var buffer = compile(script)
+
+  return buffer.length === 34 &&
+    buffer[0] === OPS.OP_0 &&
+    buffer[1] === 0x20
+}
+
 // 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) {
@@ -253,7 +269,11 @@ function isNullDataOutput (script) {
 function classifyOutput (script) {
   var chunks = decompile(script)
 
-  if (isPubKeyHashOutput(chunks)) {
+  if (isWitnessPubKeyHashOutput(chunks)) {
+    return 'witnesspubkeyhash'
+  } else if (isWitnessScriptHashOutput(chunks)) {
+    return 'witnessscripthash'
+  } else if (isPubKeyHashOutput(chunks)) {
     return 'pubkeyhash'
   } else if (isScriptHashOutput(chunks)) {
     return 'scripthash'
@@ -319,6 +339,20 @@ function multisigOutput (m, pubKeys) {
   ))
 }
 
+// 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)
@@ -344,6 +378,11 @@ function scriptHashInput (scriptSig, scriptPubKey) {
   ))
 }
 
+// <scriptSig> {serialized scriptPubKey script}
+function witnessScriptHashInput (scriptSig, scriptPubKey) {
+  return scriptHashInput(scriptSig, scriptPubKey)
+}
+
 // OP_0 [signatures ...]
 function multisigInput (signatures, scriptPubKey) {
   if (scriptPubKey) {
@@ -383,14 +422,21 @@ module.exports = {
   isPubKeyOutput: isPubKeyOutput,
   isScriptHashInput: isScriptHashInput,
   isScriptHashOutput: isScriptHashOutput,
+  isWitnessPubKeyHashOutput: isWitnessPubKeyHashOutput,
+  isWitnessScriptHashOutput: isWitnessScriptHashOutput,
   isMultisigInput: isMultisigInput,
   isMultisigOutput: isMultisigOutput,
   isNullDataOutput: isNullDataOutput,
+
   classifyOutput: classifyOutput,
   classifyInput: classifyInput,
   pubKeyOutput: pubKeyOutput,
   pubKeyHashOutput: pubKeyHashOutput,
   scriptHashOutput: scriptHashOutput,
+  witnessPubKeyHashOutput: witnessPubKeyHashOutput,
+  witnessScriptHashInput: witnessScriptHashInput,
+  witnessScriptHashOutput: witnessScriptHashOutput,
+
   multisigOutput: multisigOutput,
   pubKeyInput: pubKeyInput,
   pubKeyHashInput: pubKeyHashInput,


Test patch:
diff --git a/test/fixtures/script.json b/test/fixtures/script.json
index 797186f6..a7649edb 100644
--- a/test/fixtures/script.json
+++ b/test/fixtures/script.json
@@ -59,6 +59,20 @@
       "scriptSigHex": "0047304402207515cf147d201f411092e6be5a64a6006f9308fad7b2a8fdaab22cd86ce764c202200974b8aca7bf51dbf54150d3884e1ae04f675637b926ec33bf75939446f6ca2801483045022100ef253c1faa39e65115872519e5f0a33bbecf430c0f35cf562beabbad4da24d8d02201742be8ee49812a73adea3007c9641ce6725c32cd44ddb8e3a3af460015d14050147522102359c6e3f04cefbf089cf1d6670dc47c3fb4df68e2bad1fa5a369f9ce4b42bbd1210395a9d84d47d524548f79f435758c01faec5da2b7e551d3b8c995b7e06326ae4a52ae",
       "scriptPubKeyHex": "a914722ff0bc2c3f47b35c20df646c395594da24e90e87"
     },
+    {
+      "type": "witnesspubkeyhash",
+      "pubKey": "02359c6e3f04cefbf089cf1d6670dc47c3fb4df68e2bad1fa5a369f9ce4b42bbd1",
+      "scriptPubKey": "OP_0 aa4d7985c57e011a8b3dd8e0e5a73aaef41629c5",
+      "scriptPubKeyHex": "0014aa4d7985c57e011a8b3dd8e0e5a73aaef41629c5"
+    },
+    {
+      "type": "witnessscripthash",
+      "witnessScriptPubKey": "OP_2 02359c6e3f04cefbf089cf1d6670dc47c3fb4df68e2bad1fa5a369f9ce4b42bbd1 0395a9d84d47d524548f79f435758c01faec5da2b7e551d3b8c995b7e06326ae4a OP_2 OP_CHECKMULTISIG",
+      "witnessScriptSig": "OP_0 304402207515cf147d201f411092e6be5a64a6006f9308fad7b2a8fdaab22cd86ce764c202200974b8aca7bf51dbf54150d3884e1ae04f675637b926ec33bf75939446f6ca2801 3045022100ef253c1faa39e65115872519e5f0a33bbecf430c0f35cf562beabbad4da24d8d02201742be8ee49812a73adea3007c9641ce6725c32cd44ddb8e3a3af460015d140501",
+      "scriptPubKey": "OP_0 32447752937d355ca2defddcd1f6b4fc53d182f8901cebbcff42f5e381bf0b80",
+      "scriptPubKeyHex": "002032447752937d355ca2defddcd1f6b4fc53d182f8901cebbcff42f5e381bf0b80",
+      "witness": "OP_0 304402207515cf147d201f411092e6be5a64a6006f9308fad7b2a8fdaab22cd86ce764c202200974b8aca7bf51dbf54150d3884e1ae04f675637b926ec33bf75939446f6ca2801 3045022100ef253c1faa39e65115872519e5f0a33bbecf430c0f35cf562beabbad4da24d8d02201742be8ee49812a73adea3007c9641ce6725c32cd44ddb8e3a3af460015d140501 522102359c6e3f04cefbf089cf1d6670dc47c3fb4df68e2bad1fa5a369f9ce4b42bbd1210395a9d84d47d524548f79f435758c01faec5da2b7e551d3b8c995b7e06326ae4a52ae"
+    },
     {
       "type": "nulldata",
       "data": "06deadbeef03f895a2ad89fb6d696497af486cb7c644a27aa568c7a18dd06113401115185474",
@@ -319,6 +333,18 @@
         "exception": "Expected 160-bit Buffer, got 24-bit Buffer",
         "hash": "ffffff"
       }
+    ],
+    "witnessPubKeyHashOutput": [
+      {
+        "exception": "Expected 160-bit Buffer, got 24-bit Buffer",
+        "hash": "ffffff"
+      }
+    ],
+    "witnessScriptHashOutput": [
+      {
+        "exception": "Expected 256-bit Buffer, got 24-bit Buffer",
+        "hash": "ffffff"
+      }
     ]
   }
 }
diff --git a/test/script.js b/test/script.js
index ccb009ff..e1598cd3 100644
--- a/test/script.js
+++ b/test/script.js
@@ -35,7 +35,7 @@ describe('script', function () {
   describe('compile', function () {
     fixtures.valid.forEach(function (f) {
       if (f.scriptSig) {
-        it('compiles ' + f.scriptSig, function () {
+        it('(' + f.type + ') compiles ' + f.scriptSig, function () {
           var scriptSig = bscript.fromASM(f.scriptSig)
 
           assert.strictEqual(bscript.compile(scriptSig).toString('hex'), f.scriptSigHex)
@@ -43,7 +43,7 @@ describe('script', function () {
       }
 
       if (f.scriptPubKey) {
-        it('compiles ' + f.scriptPubKey, function () {
+        it('(' + f.type + ') compiles ' + f.scriptPubKey, function () {
           var scriptPubKey = bscript.fromASM(f.scriptPubKey)
 
           assert.strictEqual(bscript.compile(scriptPubKey).toString('hex'), f.scriptPubKeyHex)
@@ -118,7 +118,15 @@ describe('script', function () {
     })
   })
 
-  ;['PubKey', 'PubKeyHash', 'ScriptHash', 'Multisig', 'NullData'].forEach(function (type) {
+  ;[
+    'PubKey',
+    'PubKeyHash',
+    'ScriptHash',
+    'WitnessPubKeyHash',
+    'WitnessScriptHash',
+    'Multisig',
+    'NullData'
+  ].forEach(function (type) {
     var inputFnName = 'is' + type + 'Input'
     var outputFnName = 'is' + type + 'Output'
 
@@ -356,6 +364,70 @@ describe('script', function () {
     })
   })
 
+  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
diff --git a/test/types.js b/test/types.js
index 5bbf7e32..358f7dda 100644
--- a/test/types.js
+++ b/test/types.js
@@ -18,8 +18,8 @@ describe('types', function () {
   })
 
   describe('Buffer Hash160/Hash256', function () {
-    var buffer20byte = new Buffer((new Array(20 + 1)).join('00'), 'hex')
-    var buffer32byte = new Buffer((new Array(32 + 1)).join('00'), 'hex')
+    var buffer20byte = new Buffer(20)
+    var buffer32byte = new Buffer(32)
 
     it('return true for valid size', function () {
       assert(types.Hash160bit(buffer20byte))