working on Transaction verification and Script tests

This commit is contained in:
Manuel Araoz 2014-02-28 16:32:10 -03:00
parent ade6f36c34
commit 40ee699453
7 changed files with 842 additions and 754 deletions

View File

@ -280,6 +280,30 @@ function spec(b) {
return this.buffer; 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'));
}
}
}
return Script.fromChunks(chunks);
};
Script.prototype.getStringContent = function (truncate, maxEl) Script.prototype.getStringContent = function (truncate, maxEl)
{ {
if (truncate === null) { if (truncate === null) {
@ -290,26 +314,26 @@ function spec(b) {
maxEl = 15; maxEl = 15;
} }
var script = ''; var s = '';
for (var i = 0, l = this.chunks.length; i < l; i++) { for (var i = 0, l = this.chunks.length; i < l; i++) {
var chunk = this.chunks[i]; var chunk = this.chunks[i];
if (i > 0) { if (i > 0) {
script += " "; s += ' ';
} }
if (Buffer.isBuffer(chunk)) { if (Buffer.isBuffer(chunk)) {
script += "0x"+util.formatBuffer(chunk, truncate ? null : 0); s += '0x'+util.formatBuffer(chunk, truncate ? null : 0);
} else { } else {
script += Opcode.reverseMap[chunk]; s += Opcode.reverseMap[chunk];
} }
if (maxEl && i > maxEl) { if (maxEl && i > maxEl) {
script += " ..."; s += ' ...';
break; break;
} }
} }
return script; return s;
}; };
Script.prototype.toString = function (truncate, maxEl) Script.prototype.toString = function (truncate, maxEl)

View File

@ -22,8 +22,7 @@ function spec(b) {
this.disableUnsafeOpcodes = true; this.disableUnsafeOpcodes = true;
}; };
ScriptInterpreter.prototype.eval = function eval(script, tx, inIndex, hashType, callback) ScriptInterpreter.prototype.eval = function eval(script, tx, inIndex, hashType, callback) {
{
if ("function" !== typeof callback) { if ("function" !== typeof callback) {
throw new Error("ScriptInterpreter.eval() requires a callback"); throw new Error("ScriptInterpreter.eval() requires a callback");
} }
@ -123,8 +122,16 @@ function spec(b) {
break; break;
case OP_NOP: case OP_NOP:
case OP_NOP1: case OP_NOP2: case OP_NOP3: case OP_NOP4: case OP_NOP5: case OP_NOP1:
case OP_NOP6: case OP_NOP7: case OP_NOP8: case OP_NOP9: case OP_NOP10: case OP_NOP2:
case OP_NOP3:
case OP_NOP4:
case OP_NOP5:
case OP_NOP6:
case OP_NOP7:
case OP_NOP8:
case OP_NOP9:
case OP_NOP10:
break; break;
case OP_IF: case OP_IF:
@ -417,14 +424,30 @@ function spec(b) {
// (in -- out) // (in -- out)
var num = castBigint(this.stackTop()); var num = castBigint(this.stackTop());
switch (opcode) { switch (opcode) {
case OP_1ADD: num = num.add(bignum(1)); break; case OP_1ADD:
case OP_1SUB: num = num.sub(bignum(1)); break; num = num.add(bignum(1));
case OP_2MUL: num = num.mul(bignum(2)); break; break;
case OP_2DIV: num = num.div(bignum(2)); break; case OP_1SUB:
case OP_NEGATE: num = num.neg(); break; num = num.sub(bignum(1));
case OP_ABS: num = num.abs(); break; break;
case OP_NOT: num = bignum(num.cmp(0) == 0 ? 1 : 0); break; case OP_2MUL:
case OP_0NOTEQUAL: num = bignum(num.cmp(0) == 0 ? 0 : 1); break; num = num.mul(bignum(2));
break;
case OP_2DIV:
num = num.div(bignum(2));
break;
case OP_NEGATE:
num = num.neg();
break;
case OP_ABS:
num = num.abs();
break;
case OP_NOT:
num = bignum(num.cmp(0) == 0 ? 1 : 0);
break;
case OP_0NOTEQUAL:
num = bignum(num.cmp(0) == 0 ? 0 : 1);
break;
} }
this.stack[this.stack.length - 1] = bigintToBuffer(num); this.stack[this.stack.length - 1] = bigintToBuffer(num);
break; break;
@ -452,11 +475,21 @@ function spec(b) {
var v2 = castBigint(this.stackTop(1)); var v2 = castBigint(this.stackTop(1));
var num; var num;
switch (opcode) { switch (opcode) {
case OP_ADD: num = v1.add(v2); break; case OP_ADD:
case OP_SUB: num = v1.sub(v2); break; num = v1.add(v2);
case OP_MUL: num = v1.mul(v2); break; break;
case OP_DIV: num = v1.div(v2); break; case OP_SUB:
case OP_MOD: num = v1.mod(v2); break; num = v1.sub(v2);
break;
case OP_MUL:
num = v1.mul(v2);
break;
case OP_DIV:
num = v1.div(v2);
break;
case OP_MOD:
num = v1.mod(v2);
break;
case OP_LSHIFT: case OP_LSHIFT:
if (v2.cmp(0) < 0 || v2.cmp(2048) > 0) { if (v2.cmp(0) < 0 || v2.cmp(2048) > 0) {
@ -485,7 +518,8 @@ function spec(b) {
num = bignum(v1.cmp(v2) == 0 ? 1 : 0); num = bignum(v1.cmp(v2) == 0 ? 1 : 0);
break; break;
case OP_NUMNOTEQUAL:; case OP_NUMNOTEQUAL:
;
num = bignum(v1.cmp(v2) != 0 ? 1 : 0); num = bignum(v1.cmp(v2) != 0 ? 1 : 0);
break; break;
@ -505,8 +539,12 @@ function spec(b) {
num = bignum(v1.lt(v2) ? 0 : 1); num = bignum(v1.lt(v2) ? 0 : 1);
break; break;
case OP_MIN: num = (v1.lt(v2) ? v1 : v2); break; case OP_MIN:
case OP_MAX: num = (v1.gt(v2) ? v1 : v2); break; num = (v1.lt(v2) ? v1 : v2);
break;
case OP_MAX:
num = (v1.gt(v2) ? v1 : v2);
break;
} }
this.stackPop(); this.stackPop();
this.stackPop(); this.stackPop();
@ -651,7 +689,9 @@ function spec(b) {
scriptCode.findAndDelete(sig); scriptCode.findAndDelete(sig);
}); });
var success = true, isig = 0, ikey = 0; var success = true,
isig = 0,
ikey = 0;
checkMultiSigStep.call(this); checkMultiSigStep.call(this);
function checkMultiSigStep() { function checkMultiSigStep() {
@ -704,6 +744,7 @@ function spec(b) {
return; return;
default: default:
console.log('opcode '+opcode);
throw new Error("Unknown opcode encountered"); throw new Error("Unknown opcode encountered");
} }
@ -729,8 +770,7 @@ function spec(b) {
}; };
ScriptInterpreter.prototype.evalTwo = ScriptInterpreter.prototype.evalTwo =
function evalTwo(scriptSig, scriptPubkey, tx, n, hashType, callback) function evalTwo(scriptSig, scriptPubkey, tx, n, hashType, callback) {
{
var self = this; var self = this;
self.eval(scriptSig, tx, n, hashType, function(e) { self.eval(scriptSig, tx, n, hashType, function(e) {
@ -760,8 +800,7 @@ function spec(b) {
return this.stack[this.stack.length - offset]; return this.stack[this.stack.length - offset];
}; };
ScriptInterpreter.prototype.stackBack = function stackBack() ScriptInterpreter.prototype.stackBack = function stackBack() {
{
return this.stack[-1]; return this.stack[-1];
}; };
@ -888,8 +927,7 @@ function spec(b) {
}; };
ScriptInterpreter.verify = ScriptInterpreter.verify =
function verify(scriptSig, scriptPubKey, txTo, n, hashType, callback) function verify(scriptSig, scriptPubKey, txTo, n, hashType, callback) {
{
if ("function" !== typeof callback) { if ("function" !== typeof callback) {
throw new Error("ScriptInterpreter.verify() requires a callback"); throw new Error("ScriptInterpreter.verify() requires a callback");
} }
@ -919,8 +957,7 @@ function spec(b) {
}; };
function verifyStep4(scriptSig, scriptPubKey, txTo, nIn, function verifyStep4(scriptSig, scriptPubKey, txTo, nIn,
hashType, opts, callback, si, siCopy) hashType, opts, callback, si, siCopy) {
{
if (siCopy.stack.length == 0) { if (siCopy.stack.length == 0) {
callback(null, false); callback(null, false);
return; return;
@ -930,8 +967,7 @@ function spec(b) {
} }
function verifyStep3(scriptSig, scriptPubKey, txTo, nIn, function verifyStep3(scriptSig, scriptPubKey, txTo, nIn,
hashType, opts, callback, si, siCopy) hashType, opts, callback, si, siCopy) {
{
if (si.stack.length == 0) { if (si.stack.length == 0) {
callback(null, false); callback(null, false);
return; return;
@ -967,8 +1003,7 @@ function spec(b) {
} }
function verifyStep2(scriptSig, scriptPubKey, txTo, nIn, function verifyStep2(scriptSig, scriptPubKey, txTo, nIn,
hashType, opts, callback, si, siCopy) hashType, opts, callback, si, siCopy) {
{
if (opts.verifyP2SH) { if (opts.verifyP2SH) {
si.stack.forEach(function(item) { si.stack.forEach(function(item) {
siCopy.stack.push(item); siCopy.stack.push(item);
@ -986,8 +1021,7 @@ function spec(b) {
ScriptInterpreter.verifyFull = ScriptInterpreter.verifyFull =
function verifyFull(scriptSig, scriptPubKey, txTo, nIn, hashType, function verifyFull(scriptSig, scriptPubKey, txTo, nIn, hashType,
opts, callback) opts, callback) {
{
var si = new ScriptInterpreter(); var si = new ScriptInterpreter();
var siCopy = new ScriptInterpreter(); var siCopy = new ScriptInterpreter();

View File

@ -63,7 +63,8 @@
"browserify-bignum": "git://github.com/maraoz/browserify-bignum.git", "browserify-bignum": "git://github.com/maraoz/browserify-bignum.git",
"browserify-buffertools": "~1.0.2", "browserify-buffertools": "~1.0.2",
"chai": "~1.9.0", "chai": "~1.9.0",
"brfs": "~1.0.0" "brfs": "~1.0.0",
"async": "~0.2.10"
}, },
"license": "MIT" "license": "MIT"
} }

View File

@ -1,8 +1,8 @@
[ [
["0x01 0x0b", "11 EQUAL", "push 1 byte"], ["0x01 0x0b", "11 EQUAL", "push 1 byte"],
["0x02 0x417a", "'Az' EQUAL"], ["0x02 0x417a", "0x417a EQUAL"],
["0x4b 0x417a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a", ["0x4b 0x417a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a",
"'Azzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz' EQUAL", "push 75 bytes"], "0x417a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a EQUAL", "push 75 bytes"],
["0x4c 0x01 0x07","7 EQUAL", "0x4c is OP_PUSHDATA1"], ["0x4c 0x01 0x07","7 EQUAL", "0x4c is OP_PUSHDATA1"],
["0x4d 0x0100 0x08","8 EQUAL", "0x4d is OP_PUSHDATA2"], ["0x4d 0x0100 0x08","8 EQUAL", "0x4d is OP_PUSHDATA2"],
@ -11,7 +11,7 @@
["0x4c 0x00","0 EQUAL"], ["0x4c 0x00","0 EQUAL"],
["0x4d 0x0000","0 EQUAL"], ["0x4d 0x0000","0 EQUAL"],
["0x4e 0x00000000","0 EQUAL"], ["0x4e 0x00000000","0 EQUAL"],
["0x4f 1000 ADD","999 EQUAL"], ["0x4f 0x03e8 ADD","0x03e7 EQUAL"],
["0", "IF 0x50 ENDIF 1", "0x50 is reserved (ok if not executed)"], ["0", "IF 0x50 ENDIF 1", "0x50 is reserved (ok if not executed)"],
["0x51", "0x5f ADD 0x60 EQUAL", "0x51 through 0x60 push 1 through 16 onto stack"], ["0x51", "0x5f ADD 0x60 EQUAL", "0x51 through 0x60 push 1 through 16 onto stack"],
["1","NOP"], ["1","NOP"],
@ -53,8 +53,8 @@
["1 1", "VERIFY"], ["1 1", "VERIFY"],
["10 0 11 TOALTSTACK DROP FROMALTSTACK", "ADD 21 EQUAL"], ["10 0 11 TOALTSTACK DROP FROMALTSTACK", "ADD 0x15 EQUAL"],
["'gavin_was_here' TOALTSTACK 11 FROMALTSTACK", "'gavin_was_here' EQUALVERIFY 11 EQUAL"], ["0x676176696e5f7761735f68657265 TOALTSTACK 11 FROMALTSTACK", "0x676176696e5f7761735f68657265 EQUALVERIFY 11 EQUAL"],
["0 IFDUP", "DEPTH 1 EQUALVERIFY 0 EQUAL"], ["0 IFDUP", "DEPTH 1 EQUALVERIFY 0 EQUAL"],
["1 IFDUP", "DEPTH 2 EQUALVERIFY 1 EQUALVERIFY 1 EQUAL"], ["1 IFDUP", "DEPTH 2 EQUALVERIFY 1 EQUALVERIFY 1 EQUAL"],

View File

@ -9,6 +9,7 @@ var ScriptModule = bitcore.Script;
var Address = bitcore.Address.class(); var Address = bitcore.Address.class();
var networks = bitcore.networks; var networks = bitcore.networks;
var Script; var Script;
var test_data = require('./testdata');
describe('Script', function() { describe('Script', function() {
it('should initialze the main object', function() { it('should initialze the main object', function() {
@ -82,4 +83,17 @@ describe('Script', function() {
script.chunks[0].should.equal(0); script.chunks[0].should.equal(0);
}); });
}); });
test_data.dataScriptValid.forEach(function(datum) {
if (datum.length < 2) throw new Error('Invalid test data');
var human = datum[1];
it('should parse script from human readable ' + human, function() {
Script.fromStringContent(human).getStringContent(false, null).should.equal(human);
});
});
}); });

View File

@ -12,6 +12,7 @@ var Out;
var Script = bitcore.Script.class(); var Script = bitcore.Script.class();
var buffertools = require('buffertools'); var buffertools = require('buffertools');
var test_data = require('./testdata'); var test_data = require('./testdata');
var async = require('async');
describe('Transaction', function() { describe('Transaction', function() {
it('should initialze the main object', function() { it('should initialze the main object', function() {
@ -37,28 +38,38 @@ describe('Transaction', function() {
// ... where all scripts are stringified scripts. // ... where all scripts are stringified scripts.
test_data.dataTxValid.forEach(function(datum) { test_data.dataTxValid.forEach(function(datum) {
if (datum.length === 3) { if (datum.length === 3) {
it('valid tx=' + datum[1], function() { it.skip('valid tx=' + datum[1], function(done) {
var inputs = datum[0]; var inputs = datum[0];
var mapprevOutScriptPubKeys = {}; var map = {};
var ins = [];
inputs.forEach(function(vin) { inputs.forEach(function(vin) {
var hash = vin[0]; var hash = vin[0];
var index = vin[1]; var index = vin[1];
var scriptPubKey = vin[2]; var scriptPubKey = new Script(new Buffer(vin[2]));
var input = new In({ map[[hash, index]] = scriptPubKey;//Script.fromStringContent(scriptPubKey);
s: scriptPubKey, console.log(scriptPubKey.getStringContent());
q: 0xffffffff, console.log('********************************');
oTxHash: hash,
oIndex: index
});
//mapprevOutScriptPubKeys[input] = new Script(scriptPubKey);
ins.push(input);
}); });
var raw = new Buffer(datum[1], 'hex'); var raw = new Buffer(datum[1], 'hex');
var tx = new Transaction(); var tx = new Transaction();
tx.parse(raw); tx.parse(raw);
buffertools.toHex(tx.serialize()).should.equal(buffertools.toHex(raw)); buffertools.toHex(tx.serialize()).should.equal(buffertools.toHex(raw));
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();
}
);
}); });
} }
}); });

View File

@ -7,9 +7,13 @@ var dataInvalid = JSON.parse(fs.readFileSync('test/data/base58_keys_invalid.json
var dataEncodeDecode = JSON.parse(fs.readFileSync('test/data/base58_encode_decode.json')); var dataEncodeDecode = JSON.parse(fs.readFileSync('test/data/base58_encode_decode.json'));
var dataTxValid = JSON.parse(fs.readFileSync('test/data/tx_valid.json')); var dataTxValid = JSON.parse(fs.readFileSync('test/data/tx_valid.json'));
var dataTxInvalid = JSON.parse(fs.readFileSync('test/data/tx_invalid.json')); var dataTxInvalid = JSON.parse(fs.readFileSync('test/data/tx_invalid.json'));
var dataScriptValid = JSON.parse(fs.readFileSync('test/data/script_valid.json'));
var dataScriptInvalid = JSON.parse(fs.readFileSync('test/data/script_invalid.json'));
module.exports.dataValid = dataValid; module.exports.dataValid = dataValid;
module.exports.dataInvalid = dataInvalid; module.exports.dataInvalid = dataInvalid;
module.exports.dataEncodeDecode = dataEncodeDecode; module.exports.dataEncodeDecode = dataEncodeDecode;
module.exports.dataTxValid = dataTxValid; module.exports.dataTxValid = dataTxValid;
module.exports.dataTxInvalid = dataTxInvalid; module.exports.dataTxInvalid = dataTxInvalid;
module.exports.dataScriptValid = dataScriptValid;
module.exports.dataScriptInvalid = dataScriptInvalid;