You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
144 lines
3.1 KiB
144 lines
3.1 KiB
var assert = require('assert')
|
|
var bufferutils = require('./bufferutils')
|
|
var crypto = require('./crypto')
|
|
var typeForce = require('typeforce')
|
|
var opcodes = require('./opcodes')
|
|
|
|
function Script (buffer, chunks) {
|
|
typeForce('Buffer', buffer)
|
|
typeForce('Array', chunks)
|
|
|
|
this.buffer = buffer
|
|
this.chunks = chunks
|
|
}
|
|
|
|
Script.fromASM = function (asm) {
|
|
var strChunks = asm.split(' ')
|
|
var chunks = strChunks.map(function (strChunk) {
|
|
// opcode
|
|
if (strChunk in opcodes) {
|
|
return opcodes[strChunk]
|
|
|
|
// data chunk
|
|
} else {
|
|
return new Buffer(strChunk, 'hex')
|
|
}
|
|
})
|
|
|
|
return Script.fromChunks(chunks)
|
|
}
|
|
|
|
Script.fromBuffer = function (buffer) {
|
|
var chunks = []
|
|
var i = 0
|
|
|
|
while (i < buffer.length) {
|
|
var opcode = buffer.readUInt8(i)
|
|
|
|
// data chunk
|
|
if ((opcode > opcodes.OP_0) && (opcode <= opcodes.OP_PUSHDATA4)) {
|
|
var d = bufferutils.readPushDataInt(buffer, i)
|
|
|
|
// did reading a pushDataInt fail? return non-chunked script
|
|
if (d === null) return new Script(buffer, [])
|
|
i += d.size
|
|
|
|
// attempt to read too much data?
|
|
if (i + d.number > buffer.length) return new Script(buffer, [])
|
|
|
|
var data = buffer.slice(i, i + d.number)
|
|
i += d.number
|
|
|
|
chunks.push(data)
|
|
|
|
// opcode
|
|
} else {
|
|
chunks.push(opcode)
|
|
|
|
i += 1
|
|
}
|
|
}
|
|
|
|
return new Script(buffer, chunks)
|
|
}
|
|
|
|
Script.fromChunks = function (chunks) {
|
|
typeForce('Array', chunks)
|
|
|
|
var bufferSize = chunks.reduce(function (accum, chunk) {
|
|
// data chunk
|
|
if (Buffer.isBuffer(chunk)) {
|
|
return accum + bufferutils.pushDataSize(chunk.length) + chunk.length
|
|
}
|
|
|
|
// opcode
|
|
return accum + 1
|
|
}, 0.0)
|
|
|
|
var buffer = new Buffer(bufferSize)
|
|
var offset = 0
|
|
|
|
chunks.forEach(function (chunk) {
|
|
// data chunk
|
|
if (Buffer.isBuffer(chunk)) {
|
|
offset += bufferutils.writePushDataInt(buffer, chunk.length, offset)
|
|
|
|
chunk.copy(buffer, offset)
|
|
offset += chunk.length
|
|
|
|
// opcode
|
|
} else {
|
|
buffer.writeUInt8(chunk, offset)
|
|
offset += 1
|
|
}
|
|
})
|
|
|
|
assert.equal(offset, buffer.length, 'Could not decode chunks')
|
|
return new Script(buffer, chunks)
|
|
}
|
|
|
|
Script.fromHex = function (hex) {
|
|
return Script.fromBuffer(new Buffer(hex, 'hex'))
|
|
}
|
|
|
|
Script.EMPTY = Script.fromChunks([])
|
|
|
|
Script.prototype.getHash = function () {
|
|
return crypto.hash160(this.buffer)
|
|
}
|
|
|
|
// FIXME: doesn't work for data chunks, maybe time to use buffertools.compare...
|
|
Script.prototype.without = function (needle) {
|
|
return Script.fromChunks(this.chunks.filter(function (op) {
|
|
return op !== needle
|
|
}))
|
|
}
|
|
|
|
var reverseOps = []
|
|
for (var op in opcodes) {
|
|
var code = opcodes[op]
|
|
reverseOps[code] = op
|
|
}
|
|
|
|
Script.prototype.toASM = function () {
|
|
return this.chunks.map(function (chunk) {
|
|
// data chunk
|
|
if (Buffer.isBuffer(chunk)) {
|
|
return chunk.toString('hex')
|
|
|
|
// opcode
|
|
} else {
|
|
return reverseOps[chunk]
|
|
}
|
|
}).join(' ')
|
|
}
|
|
|
|
Script.prototype.toBuffer = function () {
|
|
return this.buffer
|
|
}
|
|
|
|
Script.prototype.toHex = function () {
|
|
return this.toBuffer().toString('hex')
|
|
}
|
|
|
|
module.exports = Script
|
|
|