require('classtool');
function spec(b) {
var config = b.config || require('./config');
var log = b.log || require('./util/log');
var Opcode = require('./Opcode').class();
// Make opcodes available as pseudo-constants
for (var i in Opcode.map) {
eval(i + " = " + Opcode.map[i] + ";");
}
var util = b.util || require('./util/util');
var Parser = b.Parser || require('./util/BinaryParser').class();
var Put = b.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;
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();
this.chunks.push(parser.buffer(len));
} else if (opcode == OP_PUSHDATA2) {
len = parser.word16le();
this.chunks.push(parser.buffer(len));
} else if (opcode == OP_PUSHDATA4) {
len = parser.word32le();
this.chunks.push(parser.buffer(len));
} else {
this.chunks.push(opcode);
}
}
};
Script.prototype.isPushOnly = function ()
{
for (var i = 0; i < this.chunks.length; i++)
if (!Buffer.isBuffer(this.chunks[i]))
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 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)
{
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)
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.getStringContent = function (truncate, maxEl)
{
if (truncate === null) {
truncate = true;
}
if ("undefined" === typeof maxEl) {
maxEl = 15;
}
var script = '';
for (var i = 0, l = this.chunks.length; i < l; i++) {
var chunk = this.chunks[i];
if (i > 0) {
script += " ";
}
if (Buffer.isBuffer(chunk)) {
script += "0x"+util.formatBuffer(chunk, truncate ? null : 0);
} else {
script += Opcode.reverseMap[chunk];
}
if (maxEl && i > maxEl) {
script += " ...";
break;
}
}
return script;
};
Script.prototype.toString = function (truncate, maxEl)
{
var script = "";
return script;
};
Script.prototype.writeOp = function (opcode)
{
var buf = Buffer(this.buffer.length + 1);
this.buffer.copy(buf);
buf.writeUInt8(opcode, this.buffer.length);
this.buffer = buf;
this.chunks.push(opcode);
};
Script.prototype.writeN = function (n)
{
if (n < 0 || n > 16)
throw new Error("writeN: out of range value " + n);
if (n == 0)
this.writeOp(OP_0);
else
this.writeOp(OP_1 + n - 1);
};
function prefixSize(data_length)
{
if (data_length < OP_PUSHDATA1) {
return 1;
} else if (data_length