diff --git a/src/scripts.js b/src/scripts.js index 911fd1c..b7c4dd5 100644 --- a/src/scripts.js +++ b/src/scripts.js @@ -1,62 +1,17 @@ var assert = require('assert') var enforceType = require('./types') -var opcodes = require('./opcodes') +var ops = 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) { - enforceType(Script, script) - - if (isPubKeyHashOutput.call(script)) { - return 'pubkeyhash' - } else if (isScriptHashOutput.call(script)) { - return 'scripthash' - } else if (isMultisigOutput.call(script)) { - return 'multisig' - } else if (isPubKeyOutput.call(script)) { - return 'pubkey' - } else if (isNulldataOutput.call(script)) { - return 'nulldata' - } else { - return 'nonstandard' - } -} - -function classifyInput(script) { - enforceType(Script, script) - - if (isPubKeyHashInput.call(script)) { - return 'pubkeyhash' - } else if (isScriptHashInput.call(script)) { - return 'scripthash' - } else if (isMultisigInput.call(script)) { - return 'multisig' - } else if (isPubKeyInput.call(script)) { - return 'pubkey' - } else { - return 'nonstandard' - } -} - 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 @@ -81,84 +36,118 @@ function isCanonicalSignature(buffer) { return true } -function isPubKeyHashInput() { - return this.chunks.length === 2 && - isCanonicalSignature(this.chunks[0]) && - isCanonicalPubKey(this.chunks[1]) +function isPubKeyHashInput(script) { + return script.chunks.length === 2 && + isCanonicalSignature(script.chunks[0]) && + isCanonicalPubKey(script.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 isPubKeyHashOutput(script) { + return script.chunks.length === 5 && + script.chunks[0] === ops.OP_DUP && + script.chunks[1] === ops.OP_HASH160 && + Buffer.isBuffer(script.chunks[2]) && + script.chunks[2].length === 20 && + script.chunks[3] === ops.OP_EQUALVERIFY && + script.chunks[4] === ops.OP_CHECKSIG } -function isPubKeyInput() { - return this.chunks.length === 1 && - isCanonicalSignature(this.chunks[0]) +function isPubKeyInput(script) { + return script.chunks.length === 1 && + isCanonicalSignature(script.chunks[0]) } -function isPubKeyOutput() { - return this.chunks.length === 2 && - isCanonicalPubKey(this.chunks[0]) && - this.chunks[1] === opcodes.OP_CHECKSIG +function isPubKeyOutput(script) { + return script.chunks.length === 2 && + isCanonicalPubKey(script.chunks[0]) && + script.chunks[1] === ops.OP_CHECKSIG } -function isScriptHashInput() { - if (this.chunks.length < 2) return false - var lastChunk = this.chunks[this.chunks.length - 1] +function isScriptHashInput(script) { + if (script.chunks.length < 2) return false + var lastChunk = script.chunks[script.chunks.length - 1] if (!Buffer.isBuffer(lastChunk)) return false - var scriptSig = Script.fromChunks(this.chunks.slice(0, -1)) + var scriptSig = Script.fromChunks(script.chunks.slice(0, -1)) var scriptPubKey = Script.fromBuffer(lastChunk) return classifyInput(scriptSig) === 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[2] === opcodes.OP_EQUAL +function isScriptHashOutput(script) { + return script.chunks.length === 3 && + script.chunks[0] === ops.OP_HASH160 && + Buffer.isBuffer(script.chunks[1]) && + script.chunks[1].length === 20 && + script.chunks[2] === ops.OP_EQUAL } -function isMultisigInput() { - return this.chunks[0] === opcodes.OP_0 && - this.chunks.slice(1).every(isCanonicalSignature) +function isMultisigInput(script) { + return script.chunks[0] === ops.OP_0 && + script.chunks.slice(1).every(isCanonicalSignature) } -function isMultisigOutput() { - if (this.chunks < 4) return false - if (this.chunks[this.chunks.length - 1] !== opcodes.OP_CHECKMULTISIG) return false +function isMultisigOutput(script) { + if (script.chunks.length < 4) return false + if (script.chunks[script.chunks.length - 1] !== ops.OP_CHECKMULTISIG) return false - var mOp = this.chunks[0] - if (mOp === opcodes.OP_0) return false - if (mOp < opcodes.OP_1) return false - if (mOp > opcodes.OP_16) return false + var mOp = script.chunks[0] + if (mOp === ops.OP_0) return false + if (mOp < ops.OP_1) return false + if (mOp > ops.OP_16) return false - var nOp = this.chunks[this.chunks.length - 2] - if (nOp === opcodes.OP_0) return false - if (nOp < opcodes.OP_1) return false - if (nOp > opcodes.OP_16) return false + var nOp = script.chunks[script.chunks.length - 2] + if (nOp === ops.OP_0) return false + if (nOp < ops.OP_1) return false + if (nOp > ops.OP_16) return false - var m = mOp - (opcodes.OP_1 - 1) - var n = nOp - (opcodes.OP_1 - 1) + var m = mOp - (ops.OP_1 - 1) + var n = nOp - (ops.OP_1 - 1) if (n < m) return false - var pubKeys = this.chunks.slice(1, -2) + var pubKeys = script.chunks.slice(1, -2) if (n < pubKeys.length) return false return pubKeys.every(isCanonicalPubKey) } -function isNulldataOutput() { - return this.chunks[0] === opcodes.OP_RETURN +function isNulldataOutput(script) { + return script.chunks[0] === ops.OP_RETURN +} + +function classifyOutput(script) { + enforceType(Script, script) + + if (isPubKeyHashOutput(script)) { + return 'pubkeyhash' + } else if (isScriptHashOutput(script)) { + return 'scripthash' + } else if (isMultisigOutput(script)) { + return 'multisig' + } else if (isPubKeyOutput(script)) { + return 'pubkey' + } else if (isNulldataOutput(script)) { + return 'nulldata' + } + + return 'nonstandard' +} + +function classifyInput(script) { + enforceType(Script, script) + + if (isPubKeyHashInput(script)) { + return 'pubkeyhash' + } else if (isScriptHashInput(script)) { + return 'scripthash' + } else if (isMultisigInput(script)) { + return 'multisig' + } else if (isPubKeyInput(script)) { + return 'pubkey' + } + + return 'nonstandard' } // Standard Script Templates @@ -166,7 +155,7 @@ function isNulldataOutput() { function pubKeyOutput(pubKey) { return Script.fromChunks([ pubKey.toBuffer(), - opcodes.OP_CHECKSIG + ops.OP_CHECKSIG ]) } @@ -175,11 +164,11 @@ function pubKeyHashOutput(hash) { enforceType('Buffer', hash) return Script.fromChunks([ - opcodes.OP_DUP, - opcodes.OP_HASH160, + ops.OP_DUP, + ops.OP_HASH160, hash, - opcodes.OP_EQUALVERIFY, - opcodes.OP_CHECKSIG + ops.OP_EQUALVERIFY, + ops.OP_CHECKSIG ]) } @@ -188,9 +177,9 @@ function scriptHashOutput(hash) { enforceType('Buffer', hash) return Script.fromChunks([ - opcodes.OP_HASH160, + ops.OP_HASH160, hash, - opcodes.OP_EQUAL + ops.OP_EQUAL ]) } @@ -206,10 +195,10 @@ function multisigOutput(m, pubKeys) { var n = pubKeys.length return Script.fromChunks([].concat( - (opcodes.OP_1 - 1) + m, + (ops.OP_1 - 1) + m, pubKeyBuffers, - (opcodes.OP_1 - 1) + n, - opcodes.OP_CHECKMULTISIG + (ops.OP_1 - 1) + n, + ops.OP_CHECKMULTISIG )) } @@ -238,18 +227,18 @@ function scriptHashInput(scriptSig, scriptPubKey) { // OP_0 [signatures ...] function multisigInput(signatures, scriptPubKey) { if (scriptPubKey) { - assert(isMultisigOutput.call(scriptPubKey)) + assert(isMultisigOutput(scriptPubKey)) var mOp = scriptPubKey.chunks[0] var nOp = scriptPubKey.chunks[scriptPubKey.chunks.length - 2] - var m = mOp - (opcodes.OP_1 - 1) - var n = nOp - (opcodes.OP_1 - 1) + var m = mOp - (ops.OP_1 - 1) + var n = nOp - (ops.OP_1 - 1) assert(signatures.length >= m, 'Not enough signatures provided') assert(signatures.length <= n, 'Too many signatures provided') } - return Script.fromChunks([].concat(opcodes.OP_0, signatures)) + return Script.fromChunks([].concat(ops.OP_0, signatures)) } module.exports = { diff --git a/test/fixtures/scripts.json b/test/fixtures/scripts.json index e264a4a..4afdc97 100644 --- a/test/fixtures/scripts.json +++ b/test/fixtures/scripts.json @@ -48,33 +48,69 @@ "redeemScriptSig": "OP_0 304402207515cf147d201f411092e6be5a64a6006f9308fad7b2a8fdaab22cd86ce764c202200974b8aca7bf51dbf54150d3884e1ae04f675637b926ec33bf75939446f6ca2801 3045022100ef253c1faa39e65115872519e5f0a33bbecf430c0f35cf562beabbad4da24d8d02201742be8ee49812a73adea3007c9641ce6725c32cd44ddb8e3a3af460015d140501", "scriptSig": "OP_0 304402207515cf147d201f411092e6be5a64a6006f9308fad7b2a8fdaab22cd86ce764c202200974b8aca7bf51dbf54150d3884e1ae04f675637b926ec33bf75939446f6ca2801 3045022100ef253c1faa39e65115872519e5f0a33bbecf430c0f35cf562beabbad4da24d8d02201742be8ee49812a73adea3007c9641ce6725c32cd44ddb8e3a3af460015d140501 522102359c6e3f04cefbf089cf1d6670dc47c3fb4df68e2bad1fa5a369f9ce4b42bbd1210395a9d84d47d524548f79f435758c01faec5da2b7e551d3b8c995b7e06326ae4a52ae", "scriptPubKey": "OP_HASH160 722ff0bc2c3f47b35c20df646c395594da24e90e OP_EQUAL" + }, + { + "type": "nulldata", + "scriptPubKey": "OP_RETURN ffffffffffffffffffffffffffffffffffffffff" } ], "invalid": { "classify": [ { - "description": "multisig output : m > n", - "scriptPubKey": "OP_2 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 02b80011a883a0fd621ad46dfc405df1e74bf075cbaf700fd4aebef6e96f848340 OP_1 OP_CHECKMULTISIG" + "description": "multisig output : OP_CHECKMULTISIG not found", + "scriptPubKey": "OP_0 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 02b80011a883a0fd621ad46dfc405df1e74bf075cbaf700fd4aebef6e96f848340 OP_2 OP_HASH160" + }, + { + "description": "multisig output : less than 4 chunks", + "scriptPubKey": "OP_0 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 OP_HASH160" + }, + { + "description": "multisig output : m === 0", + "scriptPubKey": "OP_0 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 02b80011a883a0fd621ad46dfc405df1e74bf075cbaf700fd4aebef6e96f848340 OP_2 OP_CHECKMULTISIG" + }, + { + "description": "multisig output : m < OP_1", + "scriptPubKey": "OP_1NEGATE 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 02b80011a883a0fd621ad46dfc405df1e74bf075cbaf700fd4aebef6e96f848340 OP_2 OP_CHECKMULTISIG" + }, + { + "description": "multisig output : m > OP_16", + "scriptPubKey": "OP_NOP 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 02b80011a883a0fd621ad46dfc405df1e74bf075cbaf700fd4aebef6e96f848340 OP_2 OP_CHECKMULTISIG" }, { "description": "multisig output : n === 0", - "scriptPubKey": "OP_0 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 02b80011a883a0fd621ad46dfc405df1e74bf075cbaf700fd4aebef6e96f848340 OP_0 OP_CHECKMULTISIG" + "scriptPubKey": "OP_1 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 02b80011a883a0fd621ad46dfc405df1e74bf075cbaf700fd4aebef6e96f848340 OP_0 OP_CHECKMULTISIG" }, { - "description": "multisig output : not (m <= len(pubKeys) <= n)", + "description": "multisig output : n < OP_1", + "scriptPubKey": "OP_1 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 02b80011a883a0fd621ad46dfc405df1e74bf075cbaf700fd4aebef6e96f848340 OP_1NEGATE OP_CHECKMULTISIG" + }, + { + "description": "multisig output : n > OP_16", + "scriptPubKey": "OP_1 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 02b80011a883a0fd621ad46dfc405df1e74bf075cbaf700fd4aebef6e96f848340 OP_NOP OP_CHECKMULTISIG" + }, + { + "description": "multisig output : n < m", + "scriptPubKey": "OP_2 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 02b80011a883a0fd621ad46dfc405df1e74bf075cbaf700fd4aebef6e96f848340 OP_1 OP_CHECKMULTISIG" + }, + { + "description": "multisig output : n < len(pubKeys)", "scriptPubKey": "OP_2 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 02b80011a883a0fd621ad46dfc405df1e74bf075cbaf700fd4aebef6e96f848340 024289801366bcee6172b771cf5a7f13aaecd237a0b9a1ff9d769cabc2e6b70a34 OP_2 OP_CHECKMULTISIG" }, - { - "description": "multisig output : m not a small int", - "scriptPubKey": "OP_HASH160 024289801366bcee6172b771cf5a7f13aaecd237a0b9a1ff9d769cabc2e6b70a34 OP_1 OP_CHECKMULTISIG" - }, - { - "description": "multisig output : n not a small int", - "scriptPubKey": "OP_1 024289801366bcee6172b771cf5a7f13aaecd237a0b9a1ff9d769cabc2e6b70a34 OP_HASH160 OP_CHECKMULTISIG" - }, { "description": "multisig output : non-canonical pubKey (bad length)", "scriptPubKey": "OP_1 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ffff OP_1 OP_CHECKMULTISIG" + }, + { + "description": "pubKeyHash input : extraneous data", + "scriptSig": "304402207515cf147d201f411092e6be5a64a6006f9308fad7b2a8fdaab22cd86ce764c202200974b8aca7bf51dbf54150d3884e1ae04f675637b926ec33bf75939446f6ca2801 02359c6e3f04cefbf089cf1d6670dc47c3fb4df68e2bad1fa5a369f9ce4b42bbd1 ffffffff" + }, + { + "description": "scriptHash input : redeemScript not data", + "scriptSig": "OP_0 304402207515cf147d201f411092e6be5a64a6006f9308fad7b2a8fdaab22cd86ce764c202200974b8aca7bf51dbf54150d3884e1ae04f675637b926ec33bf75939446f6ca2801 3045022100ef253c1faa39e65115872519e5f0a33bbecf430c0f35cf562beabbad4da24d8d02201742be8ee49812a73adea3007c9641ce6725c32cd44ddb8e3a3af460015d140501 OP_RESERVED" + }, + { + "description": "pubKey input : non-canonical signature", + "scriptSig": "304402207515cf147d201f411092e6be5a64a6006f9308fad7b2a8fdaab22cd86ce764c202200974b8aca7bf51dbf54150d3884e1ae04f675637b926ec33bf7593ffffffffffffffff" } ], "multisig": [ diff --git a/test/scripts.js b/test/scripts.js index b50ec3a..9709ec9 100644 --- a/test/scripts.js +++ b/test/scripts.js @@ -19,6 +19,17 @@ describe('Scripts', function() { assert.equal(type, f.type) }) }) + + fixtures.invalid.classify.forEach(function(f) { + if (!f.scriptSig) return + + it('returns nonstandard for ' + f.description, function() { + var script = Script.fromASM(f.scriptSig) + var type = scripts.classifyInput(script) + + assert.equal(type, 'nonstandard') + }) + }) }) describe('classifyOutput', function() { @@ -34,6 +45,8 @@ describe('Scripts', function() { }) fixtures.invalid.classify.forEach(function(f) { + if (!f.scriptPubKey) return + it('returns nonstandard for ' + f.description, function() { var script = Script.fromASM(f.scriptPubKey) var type = scripts.classifyOutput(script)