var imports = require('soop').imports(); var config = imports.config || require('./config'); var log = imports.log || require('./util/log'); var Opcode = imports.Opcode || require('./Opcode'); var buffertools = imports.buffertools || require('buffertools'); // Make opcodes available as pseudo-constants for (var i in Opcode.map) { eval(i + " = " + Opcode.map[i] + ";"); } var util = imports.util || require('./util/util'); var Parser = imports.Parser || require('./util/BinaryParser'); var Put = imports.Put || require('bufferput'); var TX_UNKNOWN = 0; var TX_PUBKEY = 1; var TX_PUBKEYHASH = 2; var TX_MULTISIG = 3; var TX_SCRIPTHASH = 4; var TX_TYPES = [ 'unknown', 'pubkey', 'pubkeyhash', 'multisig', 'scripthash' ]; function Script(buffer) { if (buffer) { this.buffer = buffer; } else { this.buffer = util.EMPTY_BUFFER; } this.chunks = []; this.parse(); } this.class = Script; Script.TX_UNKNOWN = TX_UNKNOWN; Script.TX_PUBKEY = TX_PUBKEY; Script.TX_PUBKEYHASH = TX_PUBKEYHASH; Script.TX_MULTISIG = TX_MULTISIG; Script.TX_SCRIPTHASH = TX_SCRIPTHASH; Script.prototype.parse = function() { this.chunks = []; var parser = new Parser(this.buffer); while (!parser.eof()) { var opcode = parser.word8(); var len, chunk; if (opcode > 0 && opcode < OP_PUSHDATA1) { // Read some bytes of data, opcode value is the length of data this.chunks.push(parser.buffer(opcode)); } else if (opcode === OP_PUSHDATA1) { len = parser.word8(); chunk = parser.buffer(len); this.chunks.push(chunk); } else if (opcode === OP_PUSHDATA2) { len = parser.word16le(); chunk = parser.buffer(len); this.chunks.push(chunk); } else if (opcode === OP_PUSHDATA4) { len = parser.word32le(); chunk = parser.buffer(len); this.chunks.push(chunk); } else { this.chunks.push(opcode); } } }; Script.prototype.isPushOnly = function() { for (var i = 0; i < this.chunks.length; i++) { var op = this.chunks[i]; if (!Buffer.isBuffer(op) && op > OP_16) { return false; } } return true; }; Script.prototype.isP2SH = function() { return (this.chunks.length == 3 && this.chunks[0] == OP_HASH160 && Buffer.isBuffer(this.chunks[1]) && this.chunks[1].length == 20 && this.chunks[2] == OP_EQUAL); }; Script.prototype.isPubkey = function() { return (this.chunks.length == 2 && Buffer.isBuffer(this.chunks[0]) && this.chunks[1] == OP_CHECKSIG); }; Script.prototype.isPubkeyHash = function() { return (this.chunks.length == 5 && this.chunks[0] == OP_DUP && this.chunks[1] == OP_HASH160 && Buffer.isBuffer(this.chunks[2]) && this.chunks[2].length == 20 && this.chunks[3] == OP_EQUALVERIFY && this.chunks[4] == OP_CHECKSIG); }; function isSmallIntOp(opcode) { return ((opcode == OP_0) || ((opcode >= OP_1) && (opcode <= OP_16))); }; Script.prototype.isMultiSig = function() { return (this.chunks.length > 3 && isSmallIntOp(this.chunks[0]) && isSmallIntOp(this.chunks[this.chunks.length - 2]) && this.chunks[this.chunks.length - 1] == OP_CHECKMULTISIG); }; Script.prototype.finishedMultiSig = function() { var nsigs = 0; for (var i = 0; i < this.chunks.length - 1; i++) if (this.chunks[i] !== 0) nsigs++; var serializedScript = this.chunks[this.chunks.length - 1]; var script = new Script(serializedScript); var nreq = script.chunks[0] - 80; //see OP_2-OP_16 if (nsigs == nreq) return true; else return false; }; Script.prototype.removePlaceHolders = function() { var chunks = []; for (var i in this.chunks) { if (this.chunks.hasOwnProperty(i)) { var chunk = this.chunks[i]; if (chunk != 0) chunks.push(chunk); } } this.chunks = chunks; this.updateBuffer(); return this; }; Script.prototype.prependOp0 = function() { var chunks = [0]; for (i in this.chunks) { if (this.chunks.hasOwnProperty(i)) { chunks.push(this.chunks[i]); } } this.chunks = chunks; this.updateBuffer(); return this; }; // is this a script form we know? Script.prototype.classify = function() { if (this.isPubkeyHash()) return TX_PUBKEYHASH; if (this.isP2SH()) return TX_SCRIPTHASH; if (this.isMultiSig()) return TX_MULTISIG; if (this.isPubkey()) return TX_PUBKEY; return TX_UNKNOWN; }; // extract useful data items from known scripts Script.prototype.capture = function() { var txType = this.classify(); var res = []; switch (txType) { case TX_PUBKEY: res.push(this.chunks[0]); break; case TX_PUBKEYHASH: res.push(this.chunks[2]); break; case TX_MULTISIG: for (var i = 1; i < (this.chunks.length - 2); i++) res.push(this.chunks[i]); break; case TX_SCRIPTHASH: res.push(this.chunks[1]); break; case TX_UNKNOWN: default: // do nothing break; } return res; }; // return first extracted data item from script Script.prototype.captureOne = function() { var arr = this.capture(); return arr[0]; }; Script.prototype.getOutType = function() { var txType = this.classify(); switch (txType) { case TX_PUBKEY: return 'Pubkey'; case TX_PUBKEYHASH: return 'Address'; default: return 'Strange'; } }; Script.prototype.getRawOutType = function() { return TX_TYPES[this.classify()]; }; Script.prototype.simpleOutHash = function() { switch (this.getOutType()) { case 'Address': return this.chunks[2]; case 'Pubkey': return util.sha256ripe160(this.chunks[0]); default: log.debug("Encountered non-standard scriptPubKey"); log.debug("Strange script was: " + this.toString()); return null; } }; Script.prototype.getInType = function() { if (this.chunks.length == 1) { // Direct IP to IP transactions only have the public key in their scriptSig. return 'Pubkey'; } else if (this.chunks.length == 2 && Buffer.isBuffer(this.chunks[0]) && Buffer.isBuffer(this.chunks[1])) { return 'Address'; } else { return 'Strange'; } }; Script.prototype.simpleInPubKey = function() { switch (this.getInType()) { case 'Address': return this.chunks[1]; case 'Pubkey': return null; default: log.debug("Encountered non-standard scriptSig"); log.debug("Strange script was: " + this.toString()); return null; } }; Script.prototype.getBuffer = function() { return this.buffer; }; Script.prototype.serialize = Script.prototype.getBuffer; Script.prototype.getStringContent = function(truncate, maxEl) { if (truncate === null) { truncate = true; } if ('undefined' === typeof maxEl) { maxEl = 15; } var s = ''; for (var i = 0, l = this.chunks.length; i < l; i++) { var chunk = this.chunks[i]; if (i > 0) { s += ' '; } if (Buffer.isBuffer(chunk)) { s += '0x' + util.formatBuffer(chunk, truncate ? null : 0); } else { s += Opcode.reverseMap[chunk]; } if (maxEl && i > maxEl) { s += ' ...'; break; } } return s; }; Script.prototype.toString = function(truncate, maxEl) { var script = "