From daa2cb7daabc0b8a2368c02021de512d76ebe259 Mon Sep 17 00:00:00 2001
From: Daniel Cousens <github@dcousens.com>
Date: Tue, 24 Jun 2014 17:32:23 +1000
Subject: [PATCH] scripts: fix classification logic

---
 src/scripts.js | 166 ++++++++++++++++++++++++++++++++++++-------------
 1 file changed, 122 insertions(+), 44 deletions(-)

diff --git a/src/scripts.js b/src/scripts.js
index fc5d1e1..09098c9 100644
--- a/src/scripts.js
+++ b/src/scripts.js
@@ -1,87 +1,165 @@
 var assert = require('assert')
 var opcodes = require('./opcodes')
+
+// FIXME: use ECPubKey, currently the circular dependency breaks everything.
+//
+// Solutions:
+//  * Remove ECPubKey.getAddress
+//    - Minimal change, but likely unpopular
+//  * Move all script related functionality out of Address
+//    - Means a lot of changes to Transaction/Wallet
+//  * Ignore it (existing solution)
+//  * Some form of hackery with commonjs
+//
+var ecurve = require('ecurve')
+var curve = ecurve.getCurveByName('secp256k1')
+
+var ECSignature = require('./ecsignature')
 var Script = require('./script')
 
 function classifyOutput(script) {
   assert(script instanceof Script, 'Expected Script, got ', script)
 
-  if (isPubkeyhash.call(script)) {
+  if (isPubKeyHashOutput.call(script)) {
     return 'pubkeyhash'
-  } else if (isPubkey.call(script)) {
-    return 'pubkey'
-  } else if (isScripthash.call(script)) {
+  } else if (isScriptHashOutput.call(script)) {
     return 'scripthash'
-  } else if (isMultisig.call(script)) {
+  } else if (isMultisigOutput.call(script)) {
     return 'multisig'
-  } else if (isNulldata.call(script)) {
+  } else if (isPubKeyOutput.call(script)) {
+    return 'pubkey'
+  } else if (isNulldataOutput.call(script)) {
     return 'nulldata'
   } else {
     return 'nonstandard'
   }
 }
 
-function classifyInput(script) {
+function classifyInput(script, checkScriptHash) {
   assert(script instanceof Script, 'Expected Script, got ', script)
+  if (checkScriptHash === undefined) checkScriptHash = true
 
-  if (script.chunks.length == 1 && Buffer.isBuffer(script.chunks[0])) {
-    return 'pubkey'
-  } else if (script.chunks.length == 2 && Buffer.isBuffer(script.chunks[0]) && Buffer.isBuffer(script.chunks[1])) {
+  if (isPubKeyHashInput.call(script)) {
     return 'pubkeyhash'
-  } else if (script.chunks[0] == opcodes.OP_0 && script.chunks.slice(1).reduce(function(t, chunk, i) {
-      return t && Buffer.isBuffer(chunk) && (chunk[0] == 48 || i == script.chunks.length - 1)
-    }, true)) {
+  } else if (checkScriptHash && isScriptHashInput.call(script)) {
+    return 'scripthash'
+  } else if (isMultisigInput.call(script)) {
     return 'multisig'
+  } else if (isPubKeyInput.call(script)) {
+    return 'pubkey'
   } else {
     return 'nonstandard'
   }
 }
 
-function isPubkeyhash() {
-  return this.chunks.length == 5 &&
-    this.chunks[0] == opcodes.OP_DUP &&
-    this.chunks[1] == opcodes.OP_HASH160 &&
-    Buffer.isBuffer(this.chunks[2]) &&
-    this.chunks[2].length === 20 &&
-    this.chunks[3] == opcodes.OP_EQUALVERIFY &&
-    this.chunks[4] == opcodes.OP_CHECKSIG
+function isCanonicalPubKey(buffer) {
+  if (!Buffer.isBuffer(buffer)) return false
+
+  try {
+    // FIXME: boo
+    ecurve.Point.decodeFrom(curve, buffer)
+  } catch (e) {
+    if (!(e.message.match(/Invalid sequence (length|tag)/))) throw e
+
+    return false
+  }
+
+  return true
 }
 
-function isPubkey() {
+function isCanonicalSignature(buffer) {
+  if (!Buffer.isBuffer(buffer)) return false
+
+  try {
+    ECSignature.parseScriptSignature(buffer)
+  } catch(e) {
+    if (!(e.message.match(/Not a DER sequence|Invalid sequence length|Expected a DER integer|R length is zero|S length is zero|R value excessively padded|S value excessively padded|R value is negative|S value is negative|Invalid hashType/))) throw e
+
+    return false
+  }
+
+  return true
+}
+
+function isPubKeyHashInput() {
   return this.chunks.length === 2 &&
-    Buffer.isBuffer(this.chunks[0]) &&
+    isCanonicalSignature(this.chunks[0]) &&
+    isCanonicalPubKey(this.chunks[1])
+}
+
+function isPubKeyHashOutput() {
+  return this.chunks.length === 5 &&
+    this.chunks[0] === opcodes.OP_DUP &&
+    this.chunks[1] === opcodes.OP_HASH160 &&
+    Buffer.isBuffer(this.chunks[2]) &&
+    this.chunks[2].length === 20 &&
+    this.chunks[3] === opcodes.OP_EQUALVERIFY &&
+    this.chunks[4] === opcodes.OP_CHECKSIG
+}
+
+function isPubKeyInput() {
+  return this.chunks.length === 1 &&
+    isCanonicalSignature(this.chunks[0])
+}
+
+function isPubKeyOutput() {
+  return this.chunks.length === 2 &&
+    isCanonicalPubKey(this.chunks[0]) &&
     this.chunks[1] === opcodes.OP_CHECKSIG
 }
 
-function isScripthash() {
-  return this.chunks[this.chunks.length - 1] == opcodes.OP_EQUAL &&
-    this.chunks[0] == opcodes.OP_HASH160 &&
+function isScriptHashInput() {
+  if (this.chunks.length < 2) return false
+  var lastChunk = this.chunks[this.chunks.length - 1]
+
+  if (!Buffer.isBuffer(lastChunk)) return false
+
+  var scriptSig = Script.fromChunks(this.chunks.slice(0, -1))
+  var scriptPubKey = Script.fromBuffer(lastChunk)
+
+  return classifyInput(scriptSig, false) === classifyOutput(scriptPubKey)
+}
+
+function isScriptHashOutput() {
+  return this.chunks.length === 3 &&
+    this.chunks[0] === opcodes.OP_HASH160 &&
     Buffer.isBuffer(this.chunks[1]) &&
     this.chunks[1].length === 20 &&
-    this.chunks.length == 3
+    this.chunks[2] === opcodes.OP_EQUAL
 }
 
-function isMultisig() {
-  return this.chunks.length > 3 &&
-    // m is a smallint
-    isSmallIntOp(this.chunks[0]) &&
-    // n is a smallint
-    isSmallIntOp(this.chunks[this.chunks.length - 2]) &&
-    // n greater or equal to m
-    this.chunks[0] <= this.chunks[this.chunks.length - 2] &&
-    // n cannot be 0
-    this.chunks[this.chunks.length - 2] !== opcodes.OP_0 &&
-    // n is the size of chunk length minus 3 (m, n, OP_CHECKMULTISIG)
-    this.chunks.length - 3 === this.chunks[this.chunks.length - 2] - opcodes.OP_RESERVED &&
-    // last chunk is OP_CHECKMULTISIG
-    this.chunks[this.chunks.length - 1] == opcodes.OP_CHECKMULTISIG
+function isMultisigInput() {
+  return this.chunks[0] === opcodes.OP_0 &&
+    this.chunks.slice(1).every(isCanonicalSignature)
 }
 
-function isNulldata() {
+function isMultisigOutput() {
+  if (this.chunks < 4) return false
+  if (this.chunks[this.chunks.length - 1] !== opcodes.OP_CHECKMULTISIG) return false
+
+  var mS = this.chunks[0]
+  if (!isSmallIntOp(mS)) return false
+
+  var nS = this.chunks[this.chunks.length - 2]
+  if (!isSmallIntOp(nS)) return false
+
+  var m = mS - (opcodes.OP_1 - 1)
+  var n = nS - (opcodes.OP_1 - 1)
+  if (n < m) return false
+  if (n === 0) return false
+  if (m > (this.chunks.length - 3)) return false
+
+  return this.chunks.slice(1, -2).every(isCanonicalPubKey)
+}
+
+function isNulldataOutput() {
   return this.chunks[0] === opcodes.OP_RETURN
 }
 
 function isSmallIntOp(opcode) {
-  return ((opcode == opcodes.OP_0) || ((opcode >= opcodes.OP_1) && (opcode <= opcodes.OP_16)))
+  if (Buffer.isBuffer(opcode)) return false
+
+  return ((opcode === opcodes.OP_0) || ((opcode >= opcodes.OP_1) && (opcode <= opcodes.OP_16)))
 }
 
 // Standard Script Templates
@@ -160,7 +238,7 @@ function scriptHashInput(scriptSig, scriptPubKey) {
 // OP_0 [signatures ...]
 function multisigInput(signatures, scriptPubKey) {
   if (scriptPubKey) {
-    assert(isMultisig.call(scriptPubKey))
+    assert(isMultisigOutput.call(scriptPubKey))
 
     var m = scriptPubKey.chunks[0]
     var k = m - (opcodes.OP_1 - 1)