Merge pull request #294 from bitcoinjs/scriptsclean
Scripts clean and isMultisigOutput fix
This commit is contained in:
commit
f947360468
3 changed files with 158 additions and 120 deletions
205
src/scripts.js
205
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 = {
|
||||
|
|
60
test/fixtures/scripts.json
vendored
60
test/fixtures/scripts.json
vendored
|
@ -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": [
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in a new issue