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)