From e83590f5285d67fb1897cd3d475431ab6d79dc38 Mon Sep 17 00:00:00 2001 From: Manuel Araoz Date: Fri, 7 Mar 2014 14:43:29 -0300 Subject: [PATCH 1/6] fix Script parsing for some cases, setup ScriptInterpreter tests --- Script.js | 24 ++++++- ScriptInterpreter.js | 120 ++++++++++++++++----------------- test/data/script_valid.json | 10 +-- test/test.Script.js | 4 -- test/test.ScriptInterpreter.js | 22 ++++++ test/testdata.js | 1 + 6 files changed, 110 insertions(+), 71 deletions(-) diff --git a/Script.js b/Script.js index 322b4e6b9..01e5da634 100644 --- a/Script.js +++ b/Script.js @@ -271,17 +271,33 @@ function spec(b) { for (var i = 0; i < split.length; i++) { var word = split[i]; if (word.length > 2 && word.substring(0, 2) === '0x') { + // raw hex value + //console.log('hex value'); chunks.push(new Buffer(word.substring(2, word.length), 'hex')); } else { var opcode = Opcode.map['OP_' + word]; if (opcode) { + // op code in string form + //console.log('opcode'); chunks.push(opcode); } else { var integer = parseInt(word); if (!isNaN(integer)) { - //console.log(integer+' bits=\t'+integer.toString(2).replace('-','').length); + // integer + //console.log('integer'); var data = util.intToBuffer(integer); chunks.push(data); + } else if (word[0] === '\'' && word[word.length-1] === '\'') { + // string + //console.log('string'); + word = word.substring(1,word.length-1); + var hex = ''; + for(var c=0;c keysCount) { - success = false; - } + // If there are more signatures than keys left, then too many + // signatures have failed + if (sigsCount > keysCount) { + success = false; + } } checkMultiSigStep.call(this); - } catch (e) { + } catch (e) { cb(e); - } - }.bind(this)); + } + }.bind(this)); } else { this.stack.push(new Buffer([success ? 1 : 0])); if (opcode === OP_CHECKMULTISIGVERIFY) { @@ -763,7 +763,7 @@ function spec(b) { } } catch (e) { log.debug("Script aborted: " + - (e.message ? e : e)); + (e.message ? e.message : e)); cb(e); } } @@ -774,14 +774,14 @@ function spec(b) { var self = this; self.eval(scriptSig, tx, n, hashType, function(e) { - if (e) { + if (e) { callback(e) return; - } + } - self.eval(scriptPubkey, tx, n, hashType, callback); - }); - }; + self.eval(scriptPubkey, tx, n, hashType, callback); + }); + }; /** * Get the top element of the stack. @@ -821,7 +821,7 @@ function spec(b) { } var s = this.stack, - l = s.length; + l = s.length; var tmp = s[l - a]; s[l - a] = s[l - b]; @@ -836,16 +836,16 @@ function spec(b) { */ ScriptInterpreter.prototype.getPrimitiveStack = function getPrimitiveStack() { return this.stack.map(function(entry) { - if (entry.length > 2) { + if (entry.length > 2) { return buffertools.toHex(entry.slice(0)); - } - var num = castBigint(entry); - if (num.cmp(-128) >= 0 && num.cmp(127) <= 0) { + } + var num = castBigint(entry); + if (num.cmp(-128) >= 0 && num.cmp(127) <= 0) { return num.toNumber(); - } else { + } else { return buffertools.toHex(entry.slice(0)); - } - }); + } + }); }; var castBool = ScriptInterpreter.castBool = function castBool(v) { diff --git a/test/data/script_valid.json b/test/data/script_valid.json index ae3390f3a..e4c181cae 100644 --- a/test/data/script_valid.json +++ b/test/data/script_valid.json @@ -1,8 +1,8 @@ [ ["0x01 0x0b", "11 EQUAL", "push 1 byte"], -["0x02 0x417a", "0x417a EQUAL"], +["0x02 0x417a", "'Az' EQUAL"], ["0x4b 0x417a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a", - "0x417a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a EQUAL", "push 75 bytes"], + "'Azzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz' EQUAL", "push 75 bytes"], ["0x4c 0x01 0x07","7 EQUAL", "0x4c is OP_PUSHDATA1"], ["0x4d 0x0100 0x08","8 EQUAL", "0x4d is OP_PUSHDATA2"], @@ -11,7 +11,7 @@ ["0x4c 0x00","0 EQUAL"], ["0x4d 0x0000","0 EQUAL"], ["0x4e 0x00000000","0 EQUAL"], -["0x4f 0x03e8 ADD","0x03e7 EQUAL"], +["0x4f 1000 ADD","999 EQUAL"], ["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"], ["1","NOP"], @@ -53,8 +53,8 @@ ["1 1", "VERIFY"], -["10 0 11 TOALTSTACK DROP FROMALTSTACK", "ADD 0x15 EQUAL"], -["0x676176696e5f7761735f68657265 TOALTSTACK 11 FROMALTSTACK", "0x676176696e5f7761735f68657265 EQUALVERIFY 11 EQUAL"], +["10 0 11 TOALTSTACK DROP FROMALTSTACK", "ADD 21 EQUAL"], +["'gavin_was_here' TOALTSTACK 11 FROMALTSTACK", "'gavin_was_here' EQUALVERIFY 11 EQUAL"], ["0 IFDUP", "DEPTH 1 EQUALVERIFY 0 EQUAL"], ["1 IFDUP", "DEPTH 2 EQUALVERIFY 1 EQUALVERIFY 1 EQUAL"], diff --git a/test/test.Script.js b/test/test.Script.js index 52294fd79..00a2187a2 100644 --- a/test/test.Script.js +++ b/test/test.Script.js @@ -84,8 +84,6 @@ describe('Script', function() { }); }); - - test_data.dataScriptValid.forEach(function(datum) { if (datum.length < 2) throw new Error('Invalid test data'); var human = datum[0] + ' ' + datum[1]; @@ -93,8 +91,6 @@ describe('Script', function() { var h2 = Script.fromStringContent(human).getStringContent(false, null); Script.fromStringContent(h2).getStringContent(false, null).should.equal(h2); }); - - }); }); diff --git a/test/test.ScriptInterpreter.js b/test/test.ScriptInterpreter.js index 909a4270f..3a27e3c5a 100644 --- a/test/test.ScriptInterpreter.js +++ b/test/test.ScriptInterpreter.js @@ -4,8 +4,10 @@ var chai = require('chai'); var bitcore = require('../bitcore'); var should = chai.should(); +var test_data = require('./testdata'); var ScriptInterpreterModule = bitcore.ScriptInterpreter; +var Script = bitcore.Script.class(); var ScriptInterpreter; describe('ScriptInterpreter', function() { @@ -20,6 +22,26 @@ describe('ScriptInterpreter', function() { var si = new ScriptInterpreter(); should.exist(si); }); + var i = 0; + test_data.dataScriptValid.forEach(function(datum) { + if (datum.length < 2) throw new Error('Invalid test data'); + var scriptSig = datum[0]; // script inputs + var scriptPubKey = datum[1]; // output script + var human = scriptSig + ' ' + scriptPubKey; + it('should validate script ' + human, function(done) { + i++; + console.log(i + ' ' + human); + ScriptInterpreter.verify(Script.fromStringContent(scriptSig), + Script.fromStringContent(scriptPubKey), + null, 0, 0, // tx, output index, and hashtype + function (err, result) { + should.not.exist(err); + result.should.equal(true); + done(); + } + ); + }); + }); }); diff --git a/test/testdata.js b/test/testdata.js index 9d62e9d85..66700dc09 100644 --- a/test/testdata.js +++ b/test/testdata.js @@ -17,3 +17,4 @@ module.exports.dataTxValid = dataTxValid; module.exports.dataTxInvalid = dataTxInvalid; module.exports.dataScriptValid = dataScriptValid; module.exports.dataScriptInvalid = dataScriptInvalid; +module.exports.dataScriptAll = dataScriptValid.concat(dataScriptInvalid); From dc56cb8d45ef2f770f0d6de5723662f9986b2efc Mon Sep 17 00:00:00 2001 From: Manuel Araoz Date: Fri, 7 Mar 2014 15:41:27 -0300 Subject: [PATCH 2/6] fixed Script parse tests for all scripts (valid and invalid) --- Script.js | 5 +++-- ScriptInterpreter.js | 2 ++ test/test.Script.js | 2 +- util/util.js | 19 ++++++++++++++----- 4 files changed, 20 insertions(+), 8 deletions(-) diff --git a/Script.js b/Script.js index 01e5da634..ffc4ace5f 100644 --- a/Script.js +++ b/Script.js @@ -270,13 +270,14 @@ function spec(b) { var split = s.split(' '); for (var i = 0; i < split.length; i++) { var word = split[i]; + if (word === '') continue; if (word.length > 2 && word.substring(0, 2) === '0x') { // raw hex value //console.log('hex value'); chunks.push(new Buffer(word.substring(2, word.length), 'hex')); } else { var opcode = Opcode.map['OP_' + word]; - if (opcode) { + if (typeof opcode !== 'undefined') { // op code in string form //console.log('opcode'); chunks.push(opcode); @@ -297,7 +298,7 @@ function spec(b) { } chunks.push(new Buffer(hex,'hex')); } else { - throw new Error('Could not parse word from script: ' +word); + throw new Error('Could not parse word "' +word+'" from script "'+s+'"'); } } } diff --git a/ScriptInterpreter.js b/ScriptInterpreter.js index 6e2628f4b..809d7f73f 100644 --- a/ScriptInterpreter.js +++ b/ScriptInterpreter.js @@ -393,6 +393,8 @@ function spec(b) { // (x1 x2 - bool) var v1 = this.stackTop(2); var v2 = this.stackTop(1); + console.log(v1); + console.log(v2); var value = buffertools.compare(v1, v2) === 0; // OP_NOTEQUAL is disabled because it would be too easy to say diff --git a/test/test.Script.js b/test/test.Script.js index 00a2187a2..48a4a7da5 100644 --- a/test/test.Script.js +++ b/test/test.Script.js @@ -84,7 +84,7 @@ describe('Script', function() { }); }); - test_data.dataScriptValid.forEach(function(datum) { + test_data.dataScriptAll.forEach(function(datum) { if (datum.length < 2) throw new Error('Invalid test data'); var human = datum[0] + ' ' + datum[1]; it('should parse script from human readable ' + human, function() { diff --git a/util/util.js b/util/util.js index c66bca4c3..8a012928f 100644 --- a/util/util.js +++ b/util/util.js @@ -111,18 +111,27 @@ var intTo64Bits = function(integer) { lo: (integer & 0xFFFFFFFF) >>> 0 }; }; -var fitsIn32Bits = function(integer) { +var fitsInNBits = function(integer, n) { // TODO: make this efficient!!! - return integer.toString(2).replace('-','').length < 32; + return integer.toString(2).replace('-','').length < n; } exports.intToBuffer = function(integer) { - if (fitsIn32Bits(integer)) { - var data = new Buffer(4); + var data = null; + if (fitsInNBits(integer, 8)) { + data = new Buffer(1); + data.writeInt8(integer, 0); + return data; + } else if (fitsInNBits(integer, 16)) { + data = new Buffer(2); + data.writeInt16LE(integer, 0); + return data; + } else if (fitsInNBits(integer, 32)) { + data = new Buffer(4); data.writeInt32LE(integer, 0); return data; } else { var x = intTo64Bits(integer); - var data = new Buffer(8); + 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; From 1d72154c54a15e7f5b742cd950dd762343037522 Mon Sep 17 00:00:00 2001 From: Manuel Araoz Date: Fri, 7 Mar 2014 18:04:23 -0300 Subject: [PATCH 3/6] separate getStringContent and getHumanReadable --- Script.js | 118 +++++++++++++++++++++------------ ScriptInterpreter.js | 2 + test/test.Script.js | 9 ++- test/test.ScriptInterpreter.js | 4 +- 4 files changed, 86 insertions(+), 47 deletions(-) diff --git a/Script.js b/Script.js index ffc4ace5f..0a218c5d5 100644 --- a/Script.js +++ b/Script.js @@ -38,7 +38,7 @@ function spec(b) { } this.chunks = []; this.parse(); - }; + } this.class = Script; Script.TX_UNKNOWN = TX_UNKNOWN; @@ -265,47 +265,6 @@ function spec(b) { return this.buffer; }; - Script.fromStringContent = function(s) { - var chunks = []; - var split = s.split(' '); - for (var i = 0; i < split.length; i++) { - var word = split[i]; - if (word === '') continue; - if (word.length > 2 && word.substring(0, 2) === '0x') { - // raw hex value - //console.log('hex value'); - chunks.push(new Buffer(word.substring(2, word.length), 'hex')); - } else { - var opcode = Opcode.map['OP_' + word]; - if (typeof opcode !== 'undefined') { - // op code in string form - //console.log('opcode'); - chunks.push(opcode); - } else { - var integer = parseInt(word); - if (!isNaN(integer)) { - // integer - //console.log('integer'); - var data = util.intToBuffer(integer); - chunks.push(data); - } else if (word[0] === '\'' && word[word.length-1] === '\'') { - // string - //console.log('string'); - word = word.substring(1,word.length-1); - var hex = ''; - for(var c=0;c 0) { + s += ' '; + } + + if (Buffer.isBuffer(chunk)) { + if (chunk.length === 0) { + s += '0'; + } else { + s += '0x' + util.formatBuffer(encodeLen(chunk.length), 0) + ' '; + s += '0x' + util.formatBuffer(chunk, 0); + } + } else { + var opcode = Opcode.reverseMap[chunk]; + if (typeof opcode === 'undefined') { + opcode = '0x'+chunk.toString(16); + } + s += opcode; + } + } + return s; + + }; + + Script.stringToBuffer = function(s) { + var buf = new Put(); + var split = s.split(' '); + for (var i = 0; i < split.length; i++) { + var word = split[i]; + if (word === '') continue; + if (word.length > 2 && word.substring(0, 2) === '0x') { + // raw hex value + //console.log('hex value'); + buf.put(new Buffer(word.substring(2, word.length), 'hex')); + } else { + var opcode = Opcode.map['OP_' + word]; + if (typeof opcode !== 'undefined') { + // op code in string form + //console.log('opcode'); + buf.word8(opcode); + } else { + var integer = parseInt(word); + if (!isNaN(integer)) { + // integer + //console.log('integer'); + var data = util.intToBuffer(integer); + buf.put(Script.chunksToBuffer([data])); + } else if (word[0] === '\'' && word[word.length-1] === '\'') { + // string + //console.log('string'); + word = word.substring(1,word.length-1); + var hexString = ''; + for(var c=0;c Date: Fri, 7 Mar 2014 18:06:54 -0300 Subject: [PATCH 4/6] small fix --- Script.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Script.js b/Script.js index 0a218c5d5..001e1ab5a 100644 --- a/Script.js +++ b/Script.js @@ -284,7 +284,7 @@ function spec(b) { if (Buffer.isBuffer(chunk)) { if (chunk.length === 0) { - s += '\'\''; + s += '0'; } else { s += '0x' + util.formatBuffer(chunk, truncate ? null : 0); } From 9a64cb0ea1c76a2cb4af12d70b54ce5db289059b Mon Sep 17 00:00:00 2001 From: Manuel Araoz Date: Fri, 7 Mar 2014 18:08:24 -0300 Subject: [PATCH 5/6] fix format in ScriptInterpreter --- ScriptInterpreter.js | 124 +++++++++++++++++++++---------------------- 1 file changed, 62 insertions(+), 62 deletions(-) diff --git a/ScriptInterpreter.js b/ScriptInterpreter.js index ebc57289e..ee9f07b13 100644 --- a/ScriptInterpreter.js +++ b/ScriptInterpreter.js @@ -20,7 +20,7 @@ function spec(b) { function ScriptInterpreter() { this.stack = []; this.disableUnsafeOpcodes = true; - }; + } ScriptInterpreter.prototype.eval = function eval(script, tx, inIndex, hashType, callback) { if ("function" !== typeof callback) { @@ -76,22 +76,22 @@ function spec(b) { } if (this.disableUnsafeOpcodes && - "number" === typeof opcode && - (opcode === OP_CAT || - opcode === OP_SUBSTR || - opcode === OP_LEFT || - opcode === OP_RIGHT || - opcode === OP_INVERT || - opcode === OP_AND || - opcode === OP_OR || - opcode === OP_XOR || - opcode === OP_2MUL || - opcode === OP_2DIV || - opcode === OP_MUL || - opcode === OP_DIV || - opcode === OP_MOD || - opcode === OP_LSHIFT || - opcode === OP_RSHIFT)) { + "number" === typeof opcode && + (opcode === OP_CAT || + opcode === OP_SUBSTR || + opcode === OP_LEFT || + opcode === OP_RIGHT || + opcode === OP_INVERT || + opcode === OP_AND || + opcode === OP_OR || + opcode === OP_XOR || + opcode === OP_2MUL || + opcode === OP_2DIV || + opcode === OP_MUL || + opcode === OP_DIV || + opcode === OP_MOD || + opcode === OP_LSHIFT || + opcode === OP_RSHIFT)) { throw new Error("Encountered a disabled opcode"); } @@ -619,15 +619,15 @@ function spec(b) { // Verify signature checkSig(sig, pubkey, scriptCode, tx, inIndex, hashType, function(e, result) { - try { + try { var success; if (e) { - // We intentionally ignore errors during signature verification and - // treat these cases as an invalid signature. - success = false; + // We intentionally ignore errors during signature verification and + // treat these cases as an invalid signature. + success = false; } else { - success = result; + success = result; } // Update stack @@ -635,18 +635,18 @@ function spec(b) { this.stackPop(); this.stack.push(new Buffer([success ? 1 : 0])); if (opcode === OP_CHECKSIGVERIFY) { - if (success) { - this.stackPop(); - } else { - throw new Error("OP_CHECKSIGVERIFY negative"); - } + if (success) { + this.stackPop(); + } else { + throw new Error("OP_CHECKSIGVERIFY negative"); + } } // Run next step executeStep.call(this, cb); - } catch (e) { - cb(e); - } + } catch (e) { + cb(e); + } }.bind(this)); // Note that for asynchronous opcodes we have to return here to prevent @@ -690,12 +690,12 @@ function spec(b) { // Drop the signatures, since a signature can't sign itself sigs.forEach(function(sig) { - scriptCode.findAndDelete(sig); - }); + scriptCode.findAndDelete(sig); + }); var success = true, - isig = 0, - ikey = 0; + isig = 0, + ikey = 0; checkMultiSigStep.call(this); function checkMultiSigStep() { @@ -705,26 +705,26 @@ function spec(b) { var key = keys[ikey]; checkSig(sig, key, scriptCode, tx, inIndex, hashType, function(e, result) { - try { + try { if (!e && result) { - isig++; - sigsCount--; + isig++; + sigsCount--; } else { - ikey++; - keysCount--; + ikey++; + keysCount--; - // If there are more signatures than keys left, then too many - // signatures have failed - if (sigsCount > keysCount) { - success = false; - } + // If there are more signatures than keys left, then too many + // signatures have failed + if (sigsCount > keysCount) { + success = false; + } } checkMultiSigStep.call(this); - } catch (e) { + } catch (e) { cb(e); - } - }.bind(this)); + } + }.bind(this)); } else { this.stack.push(new Buffer([success ? 1 : 0])); if (opcode === OP_CHECKMULTISIGVERIFY) { @@ -748,7 +748,7 @@ function spec(b) { return; default: - console.log('opcode '+opcode); + console.log('opcode ' + opcode); throw new Error("Unknown opcode encountered"); } @@ -767,7 +767,7 @@ function spec(b) { } } catch (e) { log.debug("Script aborted: " + - (e.message ? e.message : e)); + (e.message ? e.message : e)); cb(e); } } @@ -778,14 +778,14 @@ function spec(b) { var self = this; self.eval(scriptSig, tx, n, hashType, function(e) { - if (e) { + if (e) { callback(e) return; - } + } - self.eval(scriptPubkey, tx, n, hashType, callback); - }); - }; + self.eval(scriptPubkey, tx, n, hashType, callback); + }); + }; /** * Get the top element of the stack. @@ -825,7 +825,7 @@ function spec(b) { } var s = this.stack, - l = s.length; + l = s.length; var tmp = s[l - a]; s[l - a] = s[l - b]; @@ -840,16 +840,16 @@ function spec(b) { */ ScriptInterpreter.prototype.getPrimitiveStack = function getPrimitiveStack() { return this.stack.map(function(entry) { - if (entry.length > 2) { + if (entry.length > 2) { return buffertools.toHex(entry.slice(0)); - } - var num = castBigint(entry); - if (num.cmp(-128) >= 0 && num.cmp(127) <= 0) { + } + var num = castBigint(entry); + if (num.cmp(-128) >= 0 && num.cmp(127) <= 0) { return num.toNumber(); - } else { + } else { return buffertools.toHex(entry.slice(0)); - } - }); + } + }); }; var castBool = ScriptInterpreter.castBool = function castBool(v) { From 3284bfcb2fd31d342b5d282bd11b5d073ac28c89 Mon Sep 17 00:00:00 2001 From: Manuel Araoz Date: Fri, 7 Mar 2014 18:09:06 -0300 Subject: [PATCH 6/6] remove console.logs --- ScriptInterpreter.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/ScriptInterpreter.js b/ScriptInterpreter.js index ee9f07b13..58514eadd 100644 --- a/ScriptInterpreter.js +++ b/ScriptInterpreter.js @@ -27,7 +27,6 @@ function spec(b) { throw new Error("ScriptInterpreter.eval() requires a callback"); } - console.log(script); var pc = 0; @@ -395,8 +394,6 @@ function spec(b) { // (x1 x2 - bool) var v1 = this.stackTop(2); var v2 = this.stackTop(1); - console.log(v1); - console.log(v2); var value = buffertools.compare(v1, v2) === 0; // OP_NOTEQUAL is disabled because it would be too easy to say