Merge pull request #294 from bitcoinjs/scriptsclean

Scripts clean and isMultisigOutput fix
This commit is contained in:
Daniel Cousens 2014-10-14 21:05:40 +11:00
commit f947360468
3 changed files with 158 additions and 120 deletions

View file

@ -1,62 +1,17 @@
var assert = require('assert') var assert = require('assert')
var enforceType = require('./types') 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 ecurve = require('ecurve')
var curve = ecurve.getCurveByName('secp256k1') var curve = ecurve.getCurveByName('secp256k1')
var ECSignature = require('./ecsignature') var ECSignature = require('./ecsignature')
var Script = require('./script') 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) { function isCanonicalPubKey(buffer) {
if (!Buffer.isBuffer(buffer)) return false if (!Buffer.isBuffer(buffer)) return false
try { try {
// FIXME: boo
ecurve.Point.decodeFrom(curve, buffer) ecurve.Point.decodeFrom(curve, buffer)
} catch (e) { } catch (e) {
if (!(e.message.match(/Invalid sequence (length|tag)/))) throw e if (!(e.message.match(/Invalid sequence (length|tag)/))) throw e
@ -81,84 +36,118 @@ function isCanonicalSignature(buffer) {
return true return true
} }
function isPubKeyHashInput() { function isPubKeyHashInput(script) {
return this.chunks.length === 2 && return script.chunks.length === 2 &&
isCanonicalSignature(this.chunks[0]) && isCanonicalSignature(script.chunks[0]) &&
isCanonicalPubKey(this.chunks[1]) isCanonicalPubKey(script.chunks[1])
} }
function isPubKeyHashOutput() { function isPubKeyHashOutput(script) {
return this.chunks.length === 5 && return script.chunks.length === 5 &&
this.chunks[0] === opcodes.OP_DUP && script.chunks[0] === ops.OP_DUP &&
this.chunks[1] === opcodes.OP_HASH160 && script.chunks[1] === ops.OP_HASH160 &&
Buffer.isBuffer(this.chunks[2]) && Buffer.isBuffer(script.chunks[2]) &&
this.chunks[2].length === 20 && script.chunks[2].length === 20 &&
this.chunks[3] === opcodes.OP_EQUALVERIFY && script.chunks[3] === ops.OP_EQUALVERIFY &&
this.chunks[4] === opcodes.OP_CHECKSIG script.chunks[4] === ops.OP_CHECKSIG
} }
function isPubKeyInput() { function isPubKeyInput(script) {
return this.chunks.length === 1 && return script.chunks.length === 1 &&
isCanonicalSignature(this.chunks[0]) isCanonicalSignature(script.chunks[0])
} }
function isPubKeyOutput() { function isPubKeyOutput(script) {
return this.chunks.length === 2 && return script.chunks.length === 2 &&
isCanonicalPubKey(this.chunks[0]) && isCanonicalPubKey(script.chunks[0]) &&
this.chunks[1] === opcodes.OP_CHECKSIG script.chunks[1] === ops.OP_CHECKSIG
} }
function isScriptHashInput() { function isScriptHashInput(script) {
if (this.chunks.length < 2) return false if (script.chunks.length < 2) return false
var lastChunk = this.chunks[this.chunks.length - 1] var lastChunk = script.chunks[script.chunks.length - 1]
if (!Buffer.isBuffer(lastChunk)) return false 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) var scriptPubKey = Script.fromBuffer(lastChunk)
return classifyInput(scriptSig) === classifyOutput(scriptPubKey) return classifyInput(scriptSig) === classifyOutput(scriptPubKey)
} }
function isScriptHashOutput() { function isScriptHashOutput(script) {
return this.chunks.length === 3 && return script.chunks.length === 3 &&
this.chunks[0] === opcodes.OP_HASH160 && script.chunks[0] === ops.OP_HASH160 &&
Buffer.isBuffer(this.chunks[1]) && Buffer.isBuffer(script.chunks[1]) &&
this.chunks[1].length === 20 && script.chunks[1].length === 20 &&
this.chunks[2] === opcodes.OP_EQUAL script.chunks[2] === ops.OP_EQUAL
} }
function isMultisigInput() { function isMultisigInput(script) {
return this.chunks[0] === opcodes.OP_0 && return script.chunks[0] === ops.OP_0 &&
this.chunks.slice(1).every(isCanonicalSignature) script.chunks.slice(1).every(isCanonicalSignature)
} }
function isMultisigOutput() { function isMultisigOutput(script) {
if (this.chunks < 4) return false if (script.chunks.length < 4) return false
if (this.chunks[this.chunks.length - 1] !== opcodes.OP_CHECKMULTISIG) return false if (script.chunks[script.chunks.length - 1] !== ops.OP_CHECKMULTISIG) return false
var mOp = this.chunks[0] var mOp = script.chunks[0]
if (mOp === opcodes.OP_0) return false if (mOp === ops.OP_0) return false
if (mOp < opcodes.OP_1) return false if (mOp < ops.OP_1) return false
if (mOp > opcodes.OP_16) return false if (mOp > ops.OP_16) return false
var nOp = this.chunks[this.chunks.length - 2] var nOp = script.chunks[script.chunks.length - 2]
if (nOp === opcodes.OP_0) return false if (nOp === ops.OP_0) return false
if (nOp < opcodes.OP_1) return false if (nOp < ops.OP_1) return false
if (nOp > opcodes.OP_16) return false if (nOp > ops.OP_16) return false
var m = mOp - (opcodes.OP_1 - 1) var m = mOp - (ops.OP_1 - 1)
var n = nOp - (opcodes.OP_1 - 1) var n = nOp - (ops.OP_1 - 1)
if (n < m) return false 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 if (n < pubKeys.length) return false
return pubKeys.every(isCanonicalPubKey) return pubKeys.every(isCanonicalPubKey)
} }
function isNulldataOutput() { function isNulldataOutput(script) {
return this.chunks[0] === opcodes.OP_RETURN 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 // Standard Script Templates
@ -166,7 +155,7 @@ function isNulldataOutput() {
function pubKeyOutput(pubKey) { function pubKeyOutput(pubKey) {
return Script.fromChunks([ return Script.fromChunks([
pubKey.toBuffer(), pubKey.toBuffer(),
opcodes.OP_CHECKSIG ops.OP_CHECKSIG
]) ])
} }
@ -175,11 +164,11 @@ function pubKeyHashOutput(hash) {
enforceType('Buffer', hash) enforceType('Buffer', hash)
return Script.fromChunks([ return Script.fromChunks([
opcodes.OP_DUP, ops.OP_DUP,
opcodes.OP_HASH160, ops.OP_HASH160,
hash, hash,
opcodes.OP_EQUALVERIFY, ops.OP_EQUALVERIFY,
opcodes.OP_CHECKSIG ops.OP_CHECKSIG
]) ])
} }
@ -188,9 +177,9 @@ function scriptHashOutput(hash) {
enforceType('Buffer', hash) enforceType('Buffer', hash)
return Script.fromChunks([ return Script.fromChunks([
opcodes.OP_HASH160, ops.OP_HASH160,
hash, hash,
opcodes.OP_EQUAL ops.OP_EQUAL
]) ])
} }
@ -206,10 +195,10 @@ function multisigOutput(m, pubKeys) {
var n = pubKeys.length var n = pubKeys.length
return Script.fromChunks([].concat( return Script.fromChunks([].concat(
(opcodes.OP_1 - 1) + m, (ops.OP_1 - 1) + m,
pubKeyBuffers, pubKeyBuffers,
(opcodes.OP_1 - 1) + n, (ops.OP_1 - 1) + n,
opcodes.OP_CHECKMULTISIG ops.OP_CHECKMULTISIG
)) ))
} }
@ -238,18 +227,18 @@ function scriptHashInput(scriptSig, scriptPubKey) {
// OP_0 [signatures ...] // OP_0 [signatures ...]
function multisigInput(signatures, scriptPubKey) { function multisigInput(signatures, scriptPubKey) {
if (scriptPubKey) { if (scriptPubKey) {
assert(isMultisigOutput.call(scriptPubKey)) assert(isMultisigOutput(scriptPubKey))
var mOp = scriptPubKey.chunks[0] var mOp = scriptPubKey.chunks[0]
var nOp = scriptPubKey.chunks[scriptPubKey.chunks.length - 2] var nOp = scriptPubKey.chunks[scriptPubKey.chunks.length - 2]
var m = mOp - (opcodes.OP_1 - 1) var m = mOp - (ops.OP_1 - 1)
var n = nOp - (opcodes.OP_1 - 1) var n = nOp - (ops.OP_1 - 1)
assert(signatures.length >= m, 'Not enough signatures provided') assert(signatures.length >= m, 'Not enough signatures provided')
assert(signatures.length <= n, 'Too many 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 = { module.exports = {

View file

@ -48,33 +48,69 @@
"redeemScriptSig": "OP_0 304402207515cf147d201f411092e6be5a64a6006f9308fad7b2a8fdaab22cd86ce764c202200974b8aca7bf51dbf54150d3884e1ae04f675637b926ec33bf75939446f6ca2801 3045022100ef253c1faa39e65115872519e5f0a33bbecf430c0f35cf562beabbad4da24d8d02201742be8ee49812a73adea3007c9641ce6725c32cd44ddb8e3a3af460015d140501", "redeemScriptSig": "OP_0 304402207515cf147d201f411092e6be5a64a6006f9308fad7b2a8fdaab22cd86ce764c202200974b8aca7bf51dbf54150d3884e1ae04f675637b926ec33bf75939446f6ca2801 3045022100ef253c1faa39e65115872519e5f0a33bbecf430c0f35cf562beabbad4da24d8d02201742be8ee49812a73adea3007c9641ce6725c32cd44ddb8e3a3af460015d140501",
"scriptSig": "OP_0 304402207515cf147d201f411092e6be5a64a6006f9308fad7b2a8fdaab22cd86ce764c202200974b8aca7bf51dbf54150d3884e1ae04f675637b926ec33bf75939446f6ca2801 3045022100ef253c1faa39e65115872519e5f0a33bbecf430c0f35cf562beabbad4da24d8d02201742be8ee49812a73adea3007c9641ce6725c32cd44ddb8e3a3af460015d140501 522102359c6e3f04cefbf089cf1d6670dc47c3fb4df68e2bad1fa5a369f9ce4b42bbd1210395a9d84d47d524548f79f435758c01faec5da2b7e551d3b8c995b7e06326ae4a52ae", "scriptSig": "OP_0 304402207515cf147d201f411092e6be5a64a6006f9308fad7b2a8fdaab22cd86ce764c202200974b8aca7bf51dbf54150d3884e1ae04f675637b926ec33bf75939446f6ca2801 3045022100ef253c1faa39e65115872519e5f0a33bbecf430c0f35cf562beabbad4da24d8d02201742be8ee49812a73adea3007c9641ce6725c32cd44ddb8e3a3af460015d140501 522102359c6e3f04cefbf089cf1d6670dc47c3fb4df68e2bad1fa5a369f9ce4b42bbd1210395a9d84d47d524548f79f435758c01faec5da2b7e551d3b8c995b7e06326ae4a52ae",
"scriptPubKey": "OP_HASH160 722ff0bc2c3f47b35c20df646c395594da24e90e OP_EQUAL" "scriptPubKey": "OP_HASH160 722ff0bc2c3f47b35c20df646c395594da24e90e OP_EQUAL"
},
{
"type": "nulldata",
"scriptPubKey": "OP_RETURN ffffffffffffffffffffffffffffffffffffffff"
} }
], ],
"invalid": { "invalid": {
"classify": [ "classify": [
{ {
"description": "multisig output : m > n", "description": "multisig output : OP_CHECKMULTISIG not found",
"scriptPubKey": "OP_2 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 02b80011a883a0fd621ad46dfc405df1e74bf075cbaf700fd4aebef6e96f848340 OP_1 OP_CHECKMULTISIG" "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", "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" "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)", "description": "multisig output : non-canonical pubKey (bad length)",
"scriptPubKey": "OP_1 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ffff OP_1 OP_CHECKMULTISIG" "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": [ "multisig": [

View file

@ -19,6 +19,17 @@ describe('Scripts', function() {
assert.equal(type, f.type) 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() { describe('classifyOutput', function() {
@ -34,6 +45,8 @@ describe('Scripts', function() {
}) })
fixtures.invalid.classify.forEach(function(f) { fixtures.invalid.classify.forEach(function(f) {
if (!f.scriptPubKey) return
it('returns nonstandard for ' + f.description, function() { it('returns nonstandard for ' + f.description, function() {
var script = Script.fromASM(f.scriptPubKey) var script = Script.fromASM(f.scriptPubKey)
var type = scripts.classifyOutput(script) var type = scripts.classifyOutput(script)