scripts: fix classification logic
This commit is contained in:
parent
576fbbfff5
commit
daa2cb7daa
1 changed files with 122 additions and 44 deletions
166
src/scripts.js
166
src/scripts.js
|
@ -1,87 +1,165 @@
|
||||||
var assert = require('assert')
|
var assert = require('assert')
|
||||||
var opcodes = require('./opcodes')
|
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')
|
var Script = require('./script')
|
||||||
|
|
||||||
function classifyOutput(script) {
|
function classifyOutput(script) {
|
||||||
assert(script instanceof Script, 'Expected Script, got ', script)
|
assert(script instanceof Script, 'Expected Script, got ', script)
|
||||||
|
|
||||||
if (isPubkeyhash.call(script)) {
|
if (isPubKeyHashOutput.call(script)) {
|
||||||
return 'pubkeyhash'
|
return 'pubkeyhash'
|
||||||
} else if (isPubkey.call(script)) {
|
} else if (isScriptHashOutput.call(script)) {
|
||||||
return 'pubkey'
|
|
||||||
} else if (isScripthash.call(script)) {
|
|
||||||
return 'scripthash'
|
return 'scripthash'
|
||||||
} else if (isMultisig.call(script)) {
|
} else if (isMultisigOutput.call(script)) {
|
||||||
return 'multisig'
|
return 'multisig'
|
||||||
} else if (isNulldata.call(script)) {
|
} else if (isPubKeyOutput.call(script)) {
|
||||||
|
return 'pubkey'
|
||||||
|
} else if (isNulldataOutput.call(script)) {
|
||||||
return 'nulldata'
|
return 'nulldata'
|
||||||
} else {
|
} else {
|
||||||
return 'nonstandard'
|
return 'nonstandard'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function classifyInput(script) {
|
function classifyInput(script, checkScriptHash) {
|
||||||
assert(script instanceof Script, 'Expected Script, got ', script)
|
assert(script instanceof Script, 'Expected Script, got ', script)
|
||||||
|
if (checkScriptHash === undefined) checkScriptHash = true
|
||||||
|
|
||||||
if (script.chunks.length == 1 && Buffer.isBuffer(script.chunks[0])) {
|
if (isPubKeyHashInput.call(script)) {
|
||||||
return 'pubkey'
|
|
||||||
} else if (script.chunks.length == 2 && Buffer.isBuffer(script.chunks[0]) && Buffer.isBuffer(script.chunks[1])) {
|
|
||||||
return 'pubkeyhash'
|
return 'pubkeyhash'
|
||||||
} else if (script.chunks[0] == opcodes.OP_0 && script.chunks.slice(1).reduce(function(t, chunk, i) {
|
} else if (checkScriptHash && isScriptHashInput.call(script)) {
|
||||||
return t && Buffer.isBuffer(chunk) && (chunk[0] == 48 || i == script.chunks.length - 1)
|
return 'scripthash'
|
||||||
}, true)) {
|
} else if (isMultisigInput.call(script)) {
|
||||||
return 'multisig'
|
return 'multisig'
|
||||||
|
} else if (isPubKeyInput.call(script)) {
|
||||||
|
return 'pubkey'
|
||||||
} else {
|
} else {
|
||||||
return 'nonstandard'
|
return 'nonstandard'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function isPubkeyhash() {
|
function isCanonicalPubKey(buffer) {
|
||||||
return this.chunks.length == 5 &&
|
if (!Buffer.isBuffer(buffer)) return false
|
||||||
this.chunks[0] == opcodes.OP_DUP &&
|
|
||||||
this.chunks[1] == opcodes.OP_HASH160 &&
|
try {
|
||||||
Buffer.isBuffer(this.chunks[2]) &&
|
// FIXME: boo
|
||||||
this.chunks[2].length === 20 &&
|
ecurve.Point.decodeFrom(curve, buffer)
|
||||||
this.chunks[3] == opcodes.OP_EQUALVERIFY &&
|
} catch (e) {
|
||||||
this.chunks[4] == opcodes.OP_CHECKSIG
|
if (!(e.message.match(/Invalid sequence (length|tag)/))) throw e
|
||||||
|
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
function isPubkey() {
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
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 &&
|
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
|
this.chunks[1] === opcodes.OP_CHECKSIG
|
||||||
}
|
}
|
||||||
|
|
||||||
function isScripthash() {
|
function isScriptHashInput() {
|
||||||
return this.chunks[this.chunks.length - 1] == opcodes.OP_EQUAL &&
|
if (this.chunks.length < 2) return false
|
||||||
this.chunks[0] == opcodes.OP_HASH160 &&
|
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]) &&
|
Buffer.isBuffer(this.chunks[1]) &&
|
||||||
this.chunks[1].length === 20 &&
|
this.chunks[1].length === 20 &&
|
||||||
this.chunks.length == 3
|
this.chunks[2] === opcodes.OP_EQUAL
|
||||||
}
|
}
|
||||||
|
|
||||||
function isMultisig() {
|
function isMultisigInput() {
|
||||||
return this.chunks.length > 3 &&
|
return this.chunks[0] === opcodes.OP_0 &&
|
||||||
// m is a smallint
|
this.chunks.slice(1).every(isCanonicalSignature)
|
||||||
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 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
|
return this.chunks[0] === opcodes.OP_RETURN
|
||||||
}
|
}
|
||||||
|
|
||||||
function isSmallIntOp(opcode) {
|
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
|
// Standard Script Templates
|
||||||
|
@ -160,7 +238,7 @@ function scriptHashInput(scriptSig, scriptPubKey) {
|
||||||
// OP_0 [signatures ...]
|
// OP_0 [signatures ...]
|
||||||
function multisigInput(signatures, scriptPubKey) {
|
function multisigInput(signatures, scriptPubKey) {
|
||||||
if (scriptPubKey) {
|
if (scriptPubKey) {
|
||||||
assert(isMultisig.call(scriptPubKey))
|
assert(isMultisigOutput.call(scriptPubKey))
|
||||||
|
|
||||||
var m = scriptPubKey.chunks[0]
|
var m = scriptPubKey.chunks[0]
|
||||||
var k = m - (opcodes.OP_1 - 1)
|
var k = m - (opcodes.OP_1 - 1)
|
||||||
|
|
Loading…
Reference in a new issue