Skip to content
2 changes: 1 addition & 1 deletion src/opcodes.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
module.exports = {
// push value
OP_0 : 0,
OP_FALSE : 0,
OP_0 : 0,
OP_PUSHDATA1 : 76,
OP_PUSHDATA2 : 77,
OP_PUSHDATA4 : 78,
Expand Down
32 changes: 32 additions & 0 deletions src/script.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,21 @@ function Script(buffer, chunks) {
}

// Import operations
Script.fromASM = function(asm) {
var strChunks = asm.split(' ')

var chunks = strChunks.map(function(strChunk) {
if (strChunk in opcodes) {
return opcodes[strChunk]

} else {
return new Buffer(strChunk, 'hex')
}
})

return Script.fromChunks(chunks)
}

Script.fromBuffer = function(buffer) {
var chunks = []

Expand Down Expand Up @@ -90,6 +105,23 @@ Script.prototype.without = function(needle) {
}

// Export operations
var reverseOps = []
for (var op in opcodes) {
var code = opcodes[op]
reverseOps[code] = op
}

Script.prototype.toASM = function() {
return this.chunks.map(function(chunk) {
if (Buffer.isBuffer(chunk)) {
return chunk.toString('hex')

} else {
return reverseOps[chunk]
}
}).join(' ')
}

Script.prototype.toBuffer = function() {
return this.buffer
}
Expand Down
170 changes: 123 additions & 47 deletions src/scripts.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,34 @@
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'
Expand All @@ -23,65 +38,126 @@ function classifyOutput(script) {
function classifyInput(script) {
assert(script instanceof Script, 'Expected Script, got ', script)

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 (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 &&
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 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 &&
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
this.chunks[3] === opcodes.OP_EQUALVERIFY &&
this.chunks[4] === opcodes.OP_CHECKSIG
}

function isPubkey() {
function isPubKeyInput() {
return this.chunks.length === 1 &&
isCanonicalSignature(this.chunks[0])
}

function isPubKeyOutput() {
return this.chunks.length === 2 &&
Buffer.isBuffer(this.chunks[0]) &&
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) === 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
}

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 isNulldata() {
return this.chunks[0] === opcodes.OP_RETURN
this.chunks[2] === opcodes.OP_EQUAL
}

function isSmallIntOp(opcode) {
return ((opcode == opcodes.OP_0) || ((opcode >= opcodes.OP_1) && (opcode <= opcodes.OP_16)))
function isMultisigInput() {
return this.chunks[0] === opcodes.OP_0 &&
this.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

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 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 m = mOp - (opcodes.OP_1 - 1)
var n = nOp - (opcodes.OP_1 - 1)
if (n < m) return false

var pubKeys = this.chunks.slice(1, -2)
if (n < pubKeys.length) return false

return pubKeys.every(isCanonicalPubKey)
}

function isNulldataOutput() {
return this.chunks[0] === opcodes.OP_RETURN
}

// Standard Script Templates
Expand Down Expand Up @@ -160,7 +236,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)
Expand Down
22 changes: 11 additions & 11 deletions test/fixtures/script.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,31 +6,31 @@
"type": "pubkey",
"hash": "26e645ab170255f2a0a82d29e48f35b14ae7c826",
"pubKey": "031f1e68f82112b373f0fe980b3a89d212d2b5c01fb51eb25acb8b4c4b4299ce95",
"asm": "33 031f1e68f82112b373f0fe980b3a89d212d2b5c01fb51eb25acb8b4c4b4299ce95 OP_CHECKSIG",
"asm": "031f1e68f82112b373f0fe980b3a89d212d2b5c01fb51eb25acb8b4c4b4299ce95 OP_CHECKSIG",
"scriptPubKey": true
},
{
"description": "P2SH ScriptPubKey",
"hex": "a914e8c300c87986efa84c37c0519929019ef86eb5b487",
"type": "scripthash",
"hash": "0ba47b56a573bab4b430ad6ed3ec79270e04b066",
"asm": "OP_HASH160 20 e8c300c87986efa84c37c0519929019ef86eb5b4 OP_EQUAL",
"asm": "OP_HASH160 e8c300c87986efa84c37c0519929019ef86eb5b4 OP_EQUAL",
"scriptPubKey": true
},
{
"description": "PubKeyHash ScriptPubKey",
"hex": "76a9145a3acbc7bbcc97c5ff16f5909c9d7d3fadb293a888ac",
"type": "pubkeyhash",
"hash": "a5313f33d5c7b81674b35f7f3febc3522ef234db",
"asm": "OP_DUP OP_HASH160 20 5a3acbc7bbcc97c5ff16f5909c9d7d3fadb293a8 OP_EQUALVERIFY OP_CHECKSIG",
"asm": "OP_DUP OP_HASH160 5a3acbc7bbcc97c5ff16f5909c9d7d3fadb293a8 OP_EQUALVERIFY OP_CHECKSIG",
"scriptPubKey": true
},
{
"description": "pubKeyHash scriptSig",
"hex": "48304502206becda98cecf7a545d1a640221438ff8912d9b505ede67e0138485111099f696022100ccd616072501310acba10feb97cecc918e21c8e92760cd35144efec7622938f30141040cd2d2ce17a1e9b2b3b2cb294d40eecf305a25b7e7bfdafae6bb2639f4ee399b3637706c3d377ec4ab781355add443ae864b134c5e523001c442186ea60f0eb8",
"type": "pubkeyhash",
"hash": "b9bac2a5c5c29bb27c382d41fa3d179c646c78fd",
"asm": "72 304502206becda98cecf7a545d1a640221438ff8912d9b505ede67e0138485111099f696022100ccd616072501310acba10feb97cecc918e21c8e92760cd35144efec7622938f301 65 040cd2d2ce17a1e9b2b3b2cb294d40eecf305a25b7e7bfdafae6bb2639f4ee399b3637706c3d377ec4ab781355add443ae864b134c5e523001c442186ea60f0eb8",
"asm": "304502206becda98cecf7a545d1a640221438ff8912d9b505ede67e0138485111099f696022100ccd616072501310acba10feb97cecc918e21c8e92760cd35144efec7622938f301 040cd2d2ce17a1e9b2b3b2cb294d40eecf305a25b7e7bfdafae6bb2639f4ee399b3637706c3d377ec4ab781355add443ae864b134c5e523001c442186ea60f0eb8",
"scriptPubKey": false
},
{
Expand All @@ -39,47 +39,47 @@
"type": "pubkey",
"hash": "44d9982c3e79452e02ef5816976a0e20a0ec1cba",
"signature": "304502206becda98cecf7a545d1a640221438ff8912d9b505ede67e0138485111099f696022100ccd616072501310acba10feb97cecc918e21c8e92760cd35144efec7622938f301",
"asm": "72 304502206becda98cecf7a545d1a640221438ff8912d9b505ede67e0138485111099f696022100ccd616072501310acba10feb97cecc918e21c8e92760cd35144efec7622938f301",
"asm": "304502206becda98cecf7a545d1a640221438ff8912d9b505ede67e0138485111099f696022100ccd616072501310acba10feb97cecc918e21c8e92760cd35144efec7622938f301",
"scriptPubKey": false
},
{
"description": "Valid multisig script",
"hex": "5121032487c2a32f7c8d57d2a93906a6457afd00697925b0e6e145d89af6d3bca330162102308673d16987eaa010e540901cc6fe3695e758c19f46ce604e174dac315e685a52ae",
"type": "multisig",
"hash": "f1c98f0b74ecabcf78ae20dfa224bb6666051fbe",
"asm": "OP_TRUE 33 032487c2a32f7c8d57d2a93906a6457afd00697925b0e6e145d89af6d3bca33016 33 02308673d16987eaa010e540901cc6fe3695e758c19f46ce604e174dac315e685a OP_2 OP_CHECKMULTISIG",
"asm": "OP_TRUE 032487c2a32f7c8d57d2a93906a6457afd00697925b0e6e145d89af6d3bca33016 02308673d16987eaa010e540901cc6fe3695e758c19f46ce604e174dac315e685a OP_2 OP_CHECKMULTISIG",
"scriptPubKey": true
},
{
"description": "mutisig scriptSig",
"hex": "0047304402202b29881db1b4cc128442d955e906d41c77365ed9a8392b584be12d980b236459022009da4bc60d09280aa26f4f981bfbed94eb7263d92920961e48a7f3f0991895b101483045022100871708a7597c1dbebff2a5527a56a1f2b49d73e35cd825a07285f5f29f5766d8022003bd7ac25334e9a6d6020cc8ba1be67a8c70dca8e7063ea0547d79c45b9bc12601",
"type": "multisig",
"hash": "b1ef3ae2c77b356eff81049aad7dfd2eeb34c6f5",
"asm": "00 47 304402202b29881db1b4cc128442d955e906d41c77365ed9a8392b584be12d980b236459022009da4bc60d09280aa26f4f981bfbed94eb7263d92920961e48a7f3f0991895b101 48 3045022100871708a7597c1dbebff2a5527a56a1f2b49d73e35cd825a07285f5f29f5766d8022003bd7ac25334e9a6d6020cc8ba1be67a8c70dca8e7063ea0547d79c45b9bc12601",
"asm": "OP_0 304402202b29881db1b4cc128442d955e906d41c77365ed9a8392b584be12d980b236459022009da4bc60d09280aa26f4f981bfbed94eb7263d92920961e48a7f3f0991895b101 3045022100871708a7597c1dbebff2a5527a56a1f2b49d73e35cd825a07285f5f29f5766d8022003bd7ac25334e9a6d6020cc8ba1be67a8c70dca8e7063ea0547d79c45b9bc12601",
"scriptPubKey": false
},
{
"description": "OP_RETURN script",
"hex":"6a2606deadbeef03f895a2ad89fb6d696497af486cb7c644a27aa568c7a18dd06113401115185474",
"hex": "6a2606deadbeef03f895a2ad89fb6d696497af486cb7c644a27aa568c7a18dd06113401115185474",
"type": "nulldata",
"hash": "ec88f016655477663455fe6a8e83508c348ea145",
"asm": "OP_RETURN 38 06deadbeef03f895a2ad89fb6d696497af486cb7c644a27aa568c7a18dd06113401115185474",
"asm": "OP_RETURN 06deadbeef03f895a2ad89fb6d696497af486cb7c644a27aa568c7a18dd06113401115185474",
"scriptPubKey": true
},
{
"description": "Non standard script",
"hex": "aa206fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d619000000000087",
"type": "nonstandard",
"hash": "3823382e70d1930989813d3459988e0d7c2861d8",
"asm": "OP_HASH256 32 6fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000 OP_EQUAL",
"asm": "OP_HASH256 6fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000 OP_EQUAL",
"scriptPubKey": true
},
{
"description": "Invalid multisig script",
"hex": "000000ae",
"type": "nonstandard",
"hash": "62ede8963f9387544935f168745262f703dab1fb",
"asm": "0 0 0 OP_CHECKMULTISIG",
"asm": "OP_0 OP_0 OP_0 OP_CHECKMULTISIG",
"scriptPubKey": true
}
]
Expand Down
Loading