'use strict'; var BufferReader = require('./protocol/bufferreader'); var BufferWriter = require('./protocol/bufferwriter'); var Opcode = require('./opcode'); var Script = function Script(buf) { if (!(this instanceof Script)) return new Script(buf); this.chunks = []; if (Buffer.isBuffer(buf)) { this.fromBuffer(buf); } else if (typeof buf === 'string') { var str = buf; this.fromString(str); } else if (typeof buf !== 'undefined') { var obj = buf; this.set(obj); } }; Script.prototype.set = function(obj) { this.chunks = obj.chunks || this.chunks; return this; }; Script.prototype.fromJSON = function(json) { return this.fromString(json); }; Script.prototype.toJSON = function() { return this.toString(); }; Script.prototype.fromBuffer = function(buf) { this.chunks = []; var br = new BufferReader(buf); while (!br.eof()) { var opcodenum = br.readUInt8(); var len, buf; if (opcodenum > 0 && opcodenum < Opcode.map.OP_PUSHDATA1) { len = opcodenum; this.chunks.push({ buf: br.read(len), len: len, opcodenum: opcodenum }); } else if (opcodenum === Opcode.map.OP_PUSHDATA1) { len = br.readUInt8(); var buf = br.read(len); this.chunks.push({ buf: buf, len: len, opcodenum: opcodenum }); } else if (opcodenum === Opcode.map.OP_PUSHDATA2) { len = br.readUInt16LE(); buf = br.read(len); this.chunks.push({ buf: buf, len: len, opcodenum: opcodenum }); } else if (opcodenum === Opcode.map.OP_PUSHDATA4) { len = br.readUInt32LE(); buf = br.read(len); this.chunks.push({ buf: buf, len: len, opcodenum: opcodenum }); } else { this.chunks.push(opcodenum); } } return this; }; Script.prototype.toBuffer = function() { var bw = new BufferWriter(); for (var i = 0; i < this.chunks.length; i++) { var chunk = this.chunks[i]; if (typeof chunk === 'number') { var opcodenum = chunk; bw.writeUInt8(opcodenum); } else { var opcodenum = chunk.opcodenum; bw.writeUInt8(chunk.opcodenum); if (opcodenum < Opcode.map.OP_PUSHDATA1) { bw.write(chunk.buf); } else if (opcodenum === Opcode.map.OP_PUSHDATA1) { bw.writeUInt8(chunk.len); bw.write(chunk.buf); } else if (opcodenum === Opcode.map.OP_PUSHDATA2) { bw.writeUInt16LE(chunk.len); bw.write(chunk.buf); } else if (opcodenum === Opcode.map.OP_PUSHDATA4) { bw.writeUInt32LE(chunk.len); bw.write(chunk.buf); } } } return bw.concat(); }; Script.prototype.fromString = function(str) { this.chunks = []; var tokens = str.split(' '); var i = 0; while (i < tokens.length) { var token = tokens[i]; var opcode = Opcode(token); var opcodenum = opcode.toNumber(); if (typeof opcodenum === 'undefined') { opcodenum = parseInt(token); if (opcodenum > 0 && opcodenum < Opcode.map.OP_PUSHDATA1) { this.chunks.push({ buf: new Buffer(tokens[i + 1].slice(2), 'hex'), len: opcodenum, opcodenum: opcodenum }); i = i + 2; } else { throw new Error('Invalid script'); } } else if (opcodenum === Opcode.map.OP_PUSHDATA1 || opcodenum === Opcode.map.OP_PUSHDATA2 || opcodenum === Opcode.map.OP_PUSHDATA4) { if (tokens[i + 2].slice(0, 2) != '0x') throw new Error('Pushdata data must start with 0x'); this.chunks.push({ buf: new Buffer(tokens[i + 2].slice(2), 'hex'), len: parseInt(tokens[i + 1]), opcodenum: opcodenum }); i = i + 3; } else { this.chunks.push(opcodenum); i = i + 1; } } return this; }; Script.prototype.toString = function() { var str = ""; for (var i = 0; i < this.chunks.length; i++) { var chunk = this.chunks[i]; if (typeof chunk === 'number') { var opcodenum = chunk; str = str + Opcode(opcodenum).toString() + " "; } else { var opcodenum = chunk.opcodenum; if (opcodenum === Opcode.map.OP_PUSHDATA1 || opcodenum === Opcode.map.OP_PUSHDATA2 || opcodenum === Opcode.map.OP_PUSHDATA4) str = str + Opcode(opcodenum).toString() + " " ; str = str + chunk.len + " " ; str = str + "0x" + chunk.buf.toString('hex') + " "; } } return str.substr(0, str.length - 1); }; Script.prototype.isOpReturn = function() { if (this.chunks[0] === Opcode('OP_RETURN').toNumber() && (this.chunks.length === 1 || (this.chunks.length === 2 && this.chunks[1].buf && this.chunks[1].buf.length <= 40 && this.chunks[1].length === this.chunks.len))) { return true; } else { return false; } }; Script.prototype.isPubkeyhashOut = function() { if (this.chunks[0] === Opcode('OP_DUP').toNumber() && this.chunks[1] === Opcode('OP_HASH160').toNumber() && this.chunks[2].buf && this.chunks[3] === Opcode('OP_EQUALVERIFY').toNumber() && this.chunks[4] === Opcode('OP_CHECKSIG').toNumber()) { return true; } else { return false; } }; Script.prototype.isPubkeyhashIn = function() { if (this.chunks.length === 2 && this.chunks[0].buf && this.chunks[1].buf) { return true; } else { return false; } }; Script.prototype.isScripthashOut = function() { if (this.chunks.length === 3 && this.chunks[0] === Opcode('OP_HASH160').toNumber() && this.chunks[1].buf && this.chunks[1].buf.length === 20 && this.chunks[2] === Opcode('OP_EQUAL').toNumber()) { return true; } else { return false; } }; //note that these are frequently indistinguishable from pubkeyhashin Script.prototype.isScripthashIn = function() { var allpush = this.chunks.every(function(chunk) { return Buffer.isBuffer(chunk.buf); }); if (allpush) { return true; } else { return false; } }; Script.prototype.write = function(obj) { if (typeof obj === 'string') this.writeOp(obj); else if (typeof obj === 'number') this.writeOp(obj); else if (Buffer.isBuffer(obj)) this.writeBuffer(obj); else if (typeof obj === 'object') this.chunks.push(obj); else throw new Error('Invalid script chunk'); return this; }; Script.prototype.writeOp = function(str) { if (typeof str === 'number') this.chunks.push(str); else this.chunks.push(Opcode(str).toNumber()); return this; }; Script.prototype.writeBuffer = function(buf) { var opcodenum; var len = buf.length; if (buf.length > 0 && buf.length < Opcode.map.OP_PUSHDATA1) { opcodenum = buf.length; } else if (buf.length < Math.pow(2, 8)) { opcodenum = Opcode.map.OP_PUSHDATA1; } else if (buf.length < Math.pow(2, 16)) { opcodenum = Opcode.map.OP_PUSHDATA2; } else if (buf.length < Math.pow(2, 32)) { opcodenum = Opcode.map.OP_PUSHDATA4; } else { throw new Error("You can't push that much data"); } this.chunks.push({ buf: buf, len: len, opcodenum: opcodenum }); return this; }; module.exports = Script;