script parser code added and tested

This commit is contained in:
Manuel Araoz 2014-03-06 17:23:00 -03:00
parent 40ee699453
commit 05c5538162
5 changed files with 179 additions and 174 deletions

286
Script.js
View File

@ -31,7 +31,7 @@ function spec(b) {
];
function Script(buffer) {
if(buffer) {
if (buffer) {
this.buffer = buffer;
} else {
this.buffer = util.EMPTY_BUFFER;
@ -41,13 +41,13 @@ function spec(b) {
};
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.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 () {
Script.prototype.parse = function() {
this.chunks = [];
var parser = new Parser(this.buffer);
@ -73,8 +73,7 @@ function spec(b) {
}
};
Script.prototype.isPushOnly = function ()
{
Script.prototype.isPushOnly = function() {
for (var i = 0; i < this.chunks.length; i++)
if (!Buffer.isBuffer(this.chunks[i]))
return false;
@ -82,8 +81,7 @@ function spec(b) {
return true;
};
Script.prototype.isP2SH = function ()
{
Script.prototype.isP2SH = function() {
return (this.chunks.length == 3 &&
this.chunks[0] == OP_HASH160 &&
Buffer.isBuffer(this.chunks[1]) &&
@ -91,46 +89,41 @@ function spec(b) {
this.chunks[2] == OP_EQUAL);
};
Script.prototype.isPubkey = function ()
{
Script.prototype.isPubkey = function() {
return (this.chunks.length == 2 &&
Buffer.isBuffer(this.chunks[0]) &&
this.chunks[1] == OP_CHECKSIG);
Buffer.isBuffer(this.chunks[0]) &&
this.chunks[1] == OP_CHECKSIG);
};
Script.prototype.isPubkeyHash = function ()
{
Script.prototype.isPubkeyHash = function() {
return (this.chunks.length == 5 &&
this.chunks[0] == OP_DUP &&
this.chunks[1] == OP_HASH160 &&
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);
this.chunks[3] == OP_EQUALVERIFY &&
this.chunks[4] == OP_CHECKSIG);
};
function isSmallIntOp(opcode)
{
function isSmallIntOp(opcode) {
return ((opcode == OP_0) ||
((opcode >= OP_1) && (opcode <= OP_16)));
((opcode >= OP_1) && (opcode <= OP_16)));
};
Script.prototype.isMultiSig = function ()
{
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);
isSmallIntOp(this.chunks[0]) &&
isSmallIntOp(this.chunks[this.chunks.length - 2]) &&
this.chunks[this.chunks.length - 1] == OP_CHECKMULTISIG);
};
Script.prototype.finishedMultiSig = function()
{
Script.prototype.finishedMultiSig = function() {
var nsigs = 0;
for (var i = 0; i < this.chunks.length-1; i++)
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 serializedScript = this.chunks[this.chunks.length - 1];
var script = new Script(serializedScript);
var nreq = script.chunks[0] - 80; //see OP_2-OP_16
@ -140,11 +133,9 @@ function spec(b) {
return false;
}
Script.prototype.removePlaceHolders = function()
{
Script.prototype.removePlaceHolders = function() {
var chunks = [];
for (var i in this.chunks)
{
for (var i in this.chunks) {
if (this.chunks.hasOwnProperty(i)) {
var chunk = this.chunks[i];
if (chunk != 0)
@ -156,8 +147,7 @@ function spec(b) {
return this;
}
Script.prototype.prependOp0 = function()
{
Script.prototype.prependOp0 = function() {
var chunks = [0];
for (i in this.chunks) {
if (this.chunks.hasOwnProperty(i)) {
@ -170,62 +160,61 @@ function spec(b) {
}
// is this a script form we know?
Script.prototype.classify = function ()
{
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;
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 ()
{
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;
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;
}
case TX_UNKNOWN:
default:
// do nothing
break;
}
return res;
return res;
};
// return first extracted data item from script
Script.prototype.captureOne = function ()
{
Script.prototype.captureOne = function() {
var arr = this.capture();
return arr[0];
return arr[0];
};
Script.prototype.getOutType = function ()
{
Script.prototype.getOutType = function() {
var txType = this.classify();
switch (txType) {
case TX_PUBKEY: return 'Pubkey';
case TX_PUBKEYHASH: return 'Address';
default: return 'Strange';
case TX_PUBKEY:
return 'Pubkey';
case TX_PUBKEYHASH:
return 'Address';
default:
return 'Strange';
}
};
@ -233,84 +222,79 @@ function spec(b) {
return TX_TYPES[this.classify()];
};
Script.prototype.simpleOutHash = function ()
{
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;
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 ()
{
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])) {
Buffer.isBuffer(this.chunks[0]) &&
Buffer.isBuffer(this.chunks[1])) {
return 'Address';
} else {
return 'Strange';
}
};
Script.prototype.simpleInPubKey = function ()
{
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;
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 ()
{
Script.prototype.getBuffer = function() {
return this.buffer;
};
Script.fromStringContent = function(s) {
var chunks = [];
var split = s.split(' ');
console.log(split);
for (var i=0; i<split.length; i++) {
var word = split[i];
if (word.length > 2 && word.substring(0,2) === '0x') {
chunks.push(new Buffer(word.substring(2,word.length), 'hex'));
} else {
var integer = parseInt(word);
if (isNaN(integer)) {
chunks.push(Opcode.map['OP_'+word]);
} else {
var hexi = integer.toString(16);
if (hexi.length %2 === 1) hexi = '0'+hexi;
console.log(hexi);
chunks.push(new Buffer(hexi,'hex'));
}
}
for (var i = 0; i < split.length; i++) {
var word = split[i];
if (word.length > 2 && word.substring(0, 2) === '0x') {
chunks.push(new Buffer(word.substring(2, word.length), 'hex'));
} else {
var opcode = Opcode.map['OP_' + word];
if (opcode) {
chunks.push(opcode);
} else {
var integer = parseInt(word);
if (!isNaN(integer)) {
//console.log(integer+' bits=\t'+integer.toString(2).replace('-','').length);
var data = util.intToBuffer(integer);
chunks.push(data);
}
}
}
}
return Script.fromChunks(chunks);
};
Script.prototype.getStringContent = function (truncate, maxEl)
{
Script.prototype.getStringContent = function(truncate, maxEl) {
if (truncate === null) {
truncate = true;
}
if ("undefined" === typeof maxEl) {
if ('undefined' === typeof maxEl) {
maxEl = 15;
}
@ -323,7 +307,7 @@ function spec(b) {
}
if (Buffer.isBuffer(chunk)) {
s += '0x'+util.formatBuffer(chunk, truncate ? null : 0);
s += '0x' + util.formatBuffer(chunk, truncate ? null : 0);
} else {
s += Opcode.reverseMap[chunk];
}
@ -336,8 +320,7 @@ function spec(b) {
return s;
};
Script.prototype.toString = function (truncate, maxEl)
{
Script.prototype.toString = function(truncate, maxEl) {
var script = "<Script ";
script += this.getStringContent(truncate, maxEl);
script += ">";
@ -345,8 +328,7 @@ function spec(b) {
};
Script.prototype.writeOp = function (opcode)
{
Script.prototype.writeOp = function(opcode) {
var buf = Buffer(this.buffer.length + 1);
this.buffer.copy(buf);
buf.writeUInt8(opcode, this.buffer.length);
@ -356,8 +338,7 @@ function spec(b) {
this.chunks.push(opcode);
};
Script.prototype.writeN = function (n)
{
Script.prototype.writeN = function(n) {
if (n < 0 || n > 16)
throw new Error("writeN: out of range value " + n);
@ -367,8 +348,7 @@ function spec(b) {
this.writeOp(OP_1 + n - 1);
};
function prefixSize(data_length)
{
function prefixSize(data_length) {
if (data_length < OP_PUSHDATA1) {
return 1;
} else if (data_length <= 0xff) {
@ -385,21 +365,15 @@ function spec(b) {
if (data_length < OP_PUSHDATA1) {
buf = new Buffer(1);
buf.writeUInt8(data_length, 0);
}
else if (data_length <= 0xff) {
} else if (data_length <= 0xff) {
buf = new Buffer(1 + 1);
buf.writeUInt8(OP_PUSHDATA1, 0);
buf.writeUInt8(data_length, 1);
}
else if (data_length <= 0xffff) {
} else if (data_length <= 0xffff) {
buf = new Buffer(1 + 2);
buf.writeUInt8(OP_PUSHDATA2, 0);
buf.writeUInt16LE(data_length, 1);
}
else {
} else {
buf = new Buffer(1 + 4);
buf.writeUInt8(OP_PUSHDATA4, 0);
buf.writeUInt32LE(data_length, 1);
@ -408,25 +382,22 @@ function spec(b) {
return buf;
};
Script.prototype.writeBytes = function (data)
{
Script.prototype.writeBytes = function(data) {
var newSize = this.buffer.length + prefixSize(data.length) + data.length;
this.buffer = Buffer.concat([this.buffer, encodeLen(data.length), data]);
this.chunks.push(data);
};
Script.prototype.updateBuffer = function ()
{
Script.prototype.updateBuffer = function() {
this.buffer = Script.chunksToBuffer(this.chunks);
};
Script.prototype.findAndDelete = function (chunk)
{
Script.prototype.findAndDelete = function(chunk) {
var dirty = false;
if (Buffer.isBuffer(chunk)) {
for (var i = 0, l = this.chunks.length; i < l; i++) {
if (Buffer.isBuffer(this.chunks[i]) &&
buffertools.compare(this.chunks[i], chunk) === 0) {
buffertools.compare(this.chunks[i], chunk) === 0) {
this.chunks.splice(i, 1);
dirty = true;
}
@ -452,7 +423,7 @@ function spec(b) {
* These are used for coinbase transactions and at some point were used for
* IP-based transactions as well.
*/
Script.createPubKeyOut = function (pubkey) {
Script.createPubKeyOut = function(pubkey) {
var script = new Script();
script.writeBytes(pubkey);
script.writeOp(OP_CHECKSIG);
@ -462,7 +433,7 @@ function spec(b) {
/**
* Creates a standard txout script.
*/
Script.createPubKeyHashOut = function (pubKeyHash) {
Script.createPubKeyHashOut = function(pubKeyHash) {
var script = new Script();
script.writeOp(OP_DUP);
script.writeOp(OP_HASH160);
@ -491,8 +462,8 @@ function spec(b) {
return script;
};
Script.fromTestData = function (testData) {
testData = testData.map(function (chunk) {
Script.fromTestData = function(testData) {
testData = testData.map(function(chunk) {
if ("string" === typeof chunk) {
return new Buffer(chunk, 'hex');
} else {
@ -506,14 +477,14 @@ function spec(b) {
return script;
};
Script.fromChunks = function (chunks) {
Script.fromChunks = function(chunks) {
var script = new Script();
script.chunks = chunks;
script.updateBuffer();
return script;
};
Script.chunksToBuffer = function (chunks) {
Script.chunksToBuffer = function(chunks) {
var buf = new Put();
for (var i = 0, l = chunks.length; i < l; i++) {
@ -544,4 +515,3 @@ function spec(b) {
return Script;
};
module.defineClass(spec);

View File

@ -88,9 +88,10 @@ describe('Script', function() {
test_data.dataScriptValid.forEach(function(datum) {
if (datum.length < 2) throw new Error('Invalid test data');
var human = datum[1];
var human = datum[0] + ' ' + datum[1];
it('should parse script from human readable ' + human, function() {
Script.fromStringContent(human).getStringContent(false, null).should.equal(human);
var h2 = Script.fromStringContent(human).getStringContent(false, null);
Script.fromStringContent(h2).getStringContent(false, null).should.equal(h2);
});

View File

@ -12,7 +12,6 @@ var Out;
var Script = bitcore.Script.class();
var buffertools = require('buffertools');
var test_data = require('./testdata');
var async = require('async');
describe('Transaction', function() {
it('should initialze the main object', function() {
@ -45,7 +44,7 @@ describe('Transaction', function() {
var hash = vin[0];
var index = vin[1];
var scriptPubKey = new Script(new Buffer(vin[2]));
map[[hash, index]] = scriptPubKey;//Script.fromStringContent(scriptPubKey);
map[[hash, index]] = scriptPubKey; //Script.fromStringContent(scriptPubKey);
console.log(scriptPubKey.getStringContent());
console.log('********************************');
@ -58,18 +57,10 @@ describe('Transaction', function() {
var i = 0;
var stx = tx.getStandardizedObject();
async.eachSeries(tx.ins,
function(txin, next) {
var scriptPubKey = map[[stx.in[i].prev_out.hash, stx.in[i].prev_out.n]];
i += 1;
next();
},
function(err) {
should.not.exist(err);
done();
}
);
tx.ins.forEach(function(txin) {
var scriptPubKey = map[[stx. in [i].prev_out.hash, stx. in [i].prev_out.n]];
i += 1;
});
});
}
});

View File

@ -46,4 +46,23 @@ describe('util', function() {
});
});
});
describe('#intToBuffer', function() {
var data = [
[0, '00000000'],
[-0, '00000000'],
[-1, 'ffffffff'],
[1, '01000000'],
[18, '12000000'],
[878082192, '90785634'],
[0x01234567890, '1200000090785634'],
[-4294967297, 'feffffffffffffff'],
];
data.forEach(function(datum) {
var integer = datum[0];
var result = datum[1];
it('should work for ' + integer, function() {
buffertools.toHex(coinUtil.intToBuffer(integer)).should.equal(result);
});
});
});
});

View File

@ -105,6 +105,30 @@ var bigIntToValue = exports.bigIntToValue = function (valueBigInt) {
}
};
var intTo64Bits = function(integer) {
return {
hi: Math.floor(integer / 4294967296),
lo: (integer & 0xFFFFFFFF) >>> 0
};
};
var fitsIn32Bits = function(integer) {
// TODO: make this efficient!!!
return integer.toString(2).replace('-','').length < 32;
}
exports.intToBuffer = function(integer) {
if (fitsIn32Bits(integer)) {
var data = new Buffer(4);
data.writeInt32LE(integer, 0);
return data;
} else {
var x = intTo64Bits(integer);
var data = new Buffer(8);
data.writeInt32LE(x.hi, 0); // high part contains sign information (signed)
data.writeUInt32LE(x.lo, 4); // low part encoded as unsigned integer
return data;
}
};
var formatValue = exports.formatValue = function (valueBuffer) {
var value = valueToBigInt(valueBuffer).toString();
var integerPart = value.length > 8 ? value.substr(0, value.length-8) : '0';